3
\$\begingroup\$

I have the following model:

# == Schema Information
#
# Table name: clients
#
# id :integer not null, primary key
# first_name :string(255)
# last_name :string(255)
# email :string(255)
#
class Client < ActiveRecord::Base
 def full_name
 if first_name.blank? && last_name.blank?
 'Name: unknown'
 else
 first_name + ' ' + last_name
 end
 end
 def email_address
 email.blank? ? 'Email: unknown' : email
 end
end

Which has the following spec:

RSpec.describe Client, type: :model do
 describe 'full_name' do
 it 'should return first_name + last_name if not blank' do
 @client = create(:client, first_name: 'John', last_name: 'Doe')
 @client.full_name.should == 'John Doe'
 end
 it "should return 'Name: unkonwn' if name is blank" do
 @client = create(:client, first_name: '', last_name: '')
 @client.full_name.should == 'Name: unknown'
 end
 end
 describe 'email_address' do
 it 'should return email if email is not blank' do
 @client = create(:client, email: '[email protected]')
 @client.email_address.should == '[email protected]'
 end
 it "should return 'Email: unkonwn' if email is blank" do
 @client = create(:client, email: '')
 @client.email_address.should == 'Email: unknown'
 end
 end
end

As you can see I am creating methods (email_address, full_name) to display attributes (or a combination of attributes) in views. If the attribute is blank I want to return a string saying the value is unknown.

I have many more attributes that I would like to display this way, but it seems inefficient to come up with alternative method names for methods that simply return the attribute or the "blank alternative".

As an example, these are some of the other attributes: sex, age, source. Rather than calling client.sex or client.age I would create a method and call client.name_of_sex or client.age_string -- it starts to get awkward (I went from email to email_address in the above example).

The alternative I know is to put logic in my views:

<% if client.email.blank? %>
 Email: unknown
<% else %>
 <%= client.email %>
<% end %>

This is inappropriate for a number of reasons, but my current alternative (the example above) doesn't seem to be optimal.

Is this a point where I should get into making a view-model? Is there some other way I could elegantly perform the actions I want? Or is the way I'm doing it a reasonable thing to do (I'm going to have a lot of attributes I want to display this way)?

asked Dec 11, 2014 at 13:24
\$\endgroup\$

4 Answers 4

1
\$\begingroup\$

I want to offer an alternative to helpers because I believe in this particular case using a presenter to isolate view logic is better than using helpers. As stated yourself you have many more attributes, so having a presenter class avoids having to write generic methods and gives you a scalable way to handle view complexity.

You can use a gem like Draper, or roll your own, which I prefer.

class UserPresenter
 attr_reader :user, :template
 def initialize(user, template)
 @user, @template = user, template
 end
 def full_name
 handle_none user.name, "Name not provided" do
 user.first_name + ' ' + user.last_name
 end
 end
 def email
 handle_none user.email, "Email not provided"
 end
 def gender
 handle_none user.gender
 end
private
 def handle_none(attribute, message = nil, &block)
 if attribute.present?
 block_given? ? yield : attribute
 else
 message || "Not Available"
 end
 end
end

Then, using a helper method, you can instantiate this in the view:

 def present(user)
 presenter = UserPresenter.new(user, self)
 yield presenter if block_given?
 presenter
 end

And in the view:

- present @user do |presenter|
 = link_to presenter.full_name, @user, class: 'whatever'
answered Dec 21, 2014 at 20:17
\$\endgroup\$
3
\$\begingroup\$

Definitely, this does not belong in the model. The orthodox way is to create a helper. Note how you can use the pattern Object#presence:

module ApplicationHelper
 def show_field(field, value)
 value.presence || "#{field}: unknown"
 end
end

And use it in the views:

<%= show_field("Name", user.full_name) %>

You can go a step further and use OOP view presenters if you like the idea (personally I don't use them).

answered Dec 11, 2014 at 13:47
\$\endgroup\$
0
\$\begingroup\$

I think what you are searching for is an Option type. For more information and an implementation see here and here.

Explaination: Your model seems to have to deal not just with user names only but rather with two cases: there is a user name and there is none. To handle this, you should change your model in that it does not save a user name (as string) and using a blank name as as special case but rather to save a datatype that either contains a user name (as string) or that doesn't.

You can then create a new type that uses an Option type and prints its value if it is there or "unknown" if it is not.

answered Dec 11, 2014 at 14:38
\$\endgroup\$
0
0
\$\begingroup\$

Instead of:

if first_name.blank? && last_name.blank?
 'Name: unknown'
else
 first_name + ' ' + last_name
end

One who prefers one-liners could do:

"#{first_name} #{last_name}".strip.empty? ? "Name: unknown" : "#{first_name} #{last_name}"

Furthermore, it's better to move such code to a helper method or a decorator using https://github.com/drapergem/draper

janos
113k15 gold badges154 silver badges396 bronze badges
answered Feb 14, 2015 at 11:50
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.