Rails convention - placing logic in view vs controller vs partial - ruby-on-rails

Messages are displayed green if sent by the current user, and blue otherwise. Following Rails convention, where does that logic belong?
Introdution
The user will visit /group/:id to see the list of messages, so the corresponding view is views/groups/show.html.erb and the corresponding controller is controllers/groups_controller.rb.
The message we want to display are in an array in #group, as #group.messages. The array is sorted by timestamp.
The code to style the color of the message is not important, but for simplicity purposes we will say there are two class selectors (one for from and one for to) and we can simply add a class attribute to the div that a message is within to change its color.
Both the user's sent and received messages are held in the array #group.messages.
If we have an individual message stored in message, we can test if it was sent by the current user with:
if session[:user_id] == message.user_id
Problem
Messages are ordered by timestamp and will need to be displayed in that order. For this reason, I can't see any clean way of handling the logic in the controller.
I would like to keep as much logic as possible out of the views and especially out of the partials, but after considering the options for rendering sent and received messages in different ways, the cleanest option I've found is to put the logic in the message partial.
Handling the logic in the message partial:
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> <%= message.body %> </p>
</div>
<% else %>
<div class="from">
<p> <%= message.body %> </p>
</div>
<% end %>
Pros:
This method handles the logic with one if statement that is clean and simple
It allows us to make the code DRY because we won't have to use the logic anywhere else if we want it on other pages
Since every message only has a body, we don't have to make another partial to display messages without this formatting
Cons:
The logic is in the partial! I think people I'm working with or other programmers or even myself would first look in the controller then in the view then in the partial to make any changes or see the code
This doesn't feel like normal Rails convention
Handling the logic in the view:
Possibly two clean solutions -
1) Style the messages inside the logic or
2) Render a different partial for sent/received messages
Styling inside the logic:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<div class="to">
<p> message.body </p>
</div>
<% else %>
<div class="from">
<p> message.body </p>
</div>
<% end %>
<% end %>
Rendering different partials:
<% #group.messages.each do |message| %>
<% if message.user.id == session[:user_id] %>
<%= render :partial => '/messages/sent_message', :message => message %>
<% else %>
<%= render :partial => '/messages/received_message', :message => message %>
<% end %>
<% end %>
Pros:
Either view solution keeps the logic out of the partial
It makes sense that showing something as one color or another is decided in the view
The view solution using two partials is clean and allows us to avoid styling within logic which also means that we can change the style within the partials and affect the look of messages everywhere.
Cons:
Both view options mean that our code is no longer DRY. Using these methods will mean that if we want the same functionality on 3 other pages, we will have to write the same code 3 more times
It makes sense that a view shouldn't be deciding anything
The view solution using two partials means that we will crowd the views/messages folder with partials, and still not have a default partial for rendering messages
Both of the view solutions just feel dirty in my opinion
My main points about my solutions -
No option allows for the logic to be held within the controller
Placing the logic inside the view means that to provide the same functionality on multiple pages, the same code will be written in more than one place
The option that looks the cleanest and makes the most sense to me means putting logic inside a partial, and there must be a better way.. right?
None of the solutions seem like they follow Rails convention
Which of the three options I coded best follow Rails convention?
Is it possible to place the logic in the controller?
Is there a better way to design this so that there is a clear solution following Rails convention?

What you probably have realized is that each of the three versions you described is either not DRY or not scalable. You've done a great job analyzing pros and cons of each option, so there is very little for me to add there. :)
To add presentation functionality to your models, Rails community uses Presenters. There is a great article on Presenters here that explains more about them.
Basically, you'll want to have one partial for message:
<div class=<%=#presenter.css_class%>>
<p> <%= message.body %> </p>
</div>
Then Presenter:
class MessagesPresenter
def initialize(message, current_user)
#message = message
#current_user = current_user
end
def css_class
message.user == current_user ? 'to' : 'from'
end
private
attr_reader :message, :current_user
end
And controller:
#presenter = MessagesPresenter.new(#message, current_user)
Voila! The presenter is available in both views and partials and is a great place to stash all presentation logic.

Since the only difference in these examples in the CSS class, you're repeating yourself quite a bit. Can't you add or remove a class on the tag depending on whether the tag belongs to the current_user or not?
This is really a presentation issue, and you can handle this simple logic for displaying the correct CSS tag using decorators (http://johnotander.com/rails/2014/03/07/decorators-on-rails/). I recommend using Draper (https://github.com/drapergem/draper).
First, for simplicity, add a current_user helper method to application_controller.rb to return the authenticated user.
Add a Decorator:
MessageDecorator.rb
def recipient_class
user_id == current_user.id ? "to" : "from" # (user_id delegates to message object)
end
Now your views can have much cleaner logic
Views
Message Partial:
<div class="<%= message.recipient_class %>">
<p><%= message.body %></p>
</div>
collection partial in the main view:
<%= render partial: "message", collection: #messages, as: :message %>
Finally, call decorate on messages in your controller action:
#messages = #group.messages.decorate
EDIT
You can also use a simple helper method rather than a decorator:
def css_class_for_message(message)
message.user_id == current_user.id ? "to" : "from"
end

Related

Move logic like this to the controller or model rather than the view?

I have this logic currently in my view
<% tools_count = #job.tools.count - 1 %>
<% count = 0 %>
<% #job.tools.each do |u|%>
<%= u.name %>
<% if count != tools_count %>
<% count += 1 %>
<%= "," %>
<%end%>
<% end %>
Which just loops through some users relations and puts in a , unless it is the end of the list.
My question: This kind of logic looks really messy and clogs up my views I know there must be a better way of doing this by moving it into the controller or maybe model, does anyone know the correct way to do this kind of logic?
You can add a method like this to your Job model:
def tool_names
tools.map(&:name).join(',')
end
And use it in your view like this:
<%= #job.tool_names %>
There are couple of ways to avoid putting this kind of logic in the view layer:
Create an instance method in the model class (as spickermann suggested)
This will work for simple logic and simple projects. However, when you will want to use some helpers from ActionView::Helpers such as jobs_path or number_to_currency, a model is not a good place for it.
Create a helper method in helper modules eq. JobHelpers
Generally you can put any helper methods related to view layer in helpers. For example to share common methods for building a view components.
Use the decorator/presenter pattern and put there the view logic so model won't be polluted. Here is some more explanation about the pattern and sample implementation using draper gem: http://johnotander.com/rails/2014/03/07/decorators-on-rails/
You can do it in a single line like
<%= #job.tools.map(&:name).join(',') %>

would it be ok to create an instantiate an object in another controller?

I have a controller for customer. In the new action, I redirect to another page, which belong to the pages controller
class CustomersController < ApplicationController
def new
redirect_to register_path
end
Would it be possible to create the object in the registration action like this?
class PagesController < ApplicationController
def registration
#customer = Customer.new
end
end
I believe the setup is something like this: you have models in your application for a Customer and, say, an Agent. Your website users register as either and the desire is to have a single HTML page (URL) with both options available. They choose which one they are and submit some fields, say name/email/password. To keep it simple, without bothering with JavaScript to hide things behind tabs, you have something like:
**Customer**
Name: ___________
Email: __________
Password: _______
[Submit]
**Agent**
Name: ___________
Email: __________
Password: _______
[Submit]
You have a few options here to avoid your guilty feeling in the Rails controllers:
Go heavy client-side JavaScript. Don't have the new actions on the controllers. The JavaScript creates the page elements. The create action becomes a JSON API endpoint, thereby avoiding the problem in the Ruby application. This is obviously a significant architectural deviation from where I think you are today.
Use a little bit of JavaScript to dynamically load the correct 'partial' into the DOM when the user switches between the options. Avoids the underlying problem in the question by effectively separating the 'pages' out to the two controllers. The Pages→registration action does not need to set any instance variables for the view. The JavaScript deals with the partial loading. (see 'link_to' and the 'remote' option)
Don't include both forms in the same HTML page, just default to one, say the Customer one, and provide a link to navigate to the Agent one, e.g. a link in a tab, or a plain link like "Not a Customer? Register as an Agent." In this scenario, you have a neat mapping to the Ruby MVC design, each of the pages are just the new action of its relevant controller. The downside is a page load to change between the two options. This is the simplest, plainest choice … if you can get the boss to agree to the UX. PS: if you are using turbolinks, then the 'feel' of this option in the browser will be not far from option (2).
Stick to your design
Keep in mind that you will have difficultly dealing with error conditions and messages with option (4). You can do it, but the code won't be simple or easy to maintain.
If option (4) is a must, one simplification can be the create actions on each of the controllers rendering their own new in case of an error. If you submit the 'Agent' form from your starting page, with errors, to the Agents→create action, that action finishes with a render 'new' to show the user the Agents→new page. No 'customer' form is visible. You could then add a sprinkle of option (3) in there with a "Not an Agent? Register as a Customer." link under the form. Doing this greatly simplifies your error handling.
Which then leads to a suggestion for your original problem. Cheat. Don't have an #customer instance variable for the new actions (or the registration action). Use partials for the customer and agent forms, and pass in a new object to form_for, e.g.
pages/registration.html.erb
<%= render 'customers/new_form' %>
<%= render 'agents/new_form' %>
customers/new.html.erb
<%= render 'customers/new_form' %>
customers/_new_form.html.erb
<% form_for Customer.new do |f| %>
<%# include the inputs shared with the edit action %>
<%= render 'fields', f %>
<%= f.submit %>
<% end %>
customers/_fields.html.erb
<%# 'f' is one of the locals passed to the partial %>
<% f.input_field :name %>
<% f.email_field :email %>
<% f.password_field :password %>
customers/edit.html.erb
<% form_form #customer do |f| %>
<%= render 'fields', f %>
<%= f.submit %>
<% end %>
… then you would follow the same pattern for:
agents/new.html.erb
agents/_new_form.html.erb
agents/_fields.html.erb
agents/edit.html.erb

Rails, pulling complicated logic out of views

I have a view that is getting complicated, and I'm wondering I should be doing this different? Picture (or code) is worth a 1000 words, so heres the view...
<% #orientation_by_date[date].each do |orientation| %>
<% if current_user %>
<% if orientation.active? %>
<li><%= link_to orientation.class_time, new_orientation_registration_path(orientation) %>
(<%= orientation.current_number_seats %>/<%= orientation.seats %>)</li>
<% else %>
<li><%= orientation.class_time %>(Class full)</li>
<% end %>
<%= link_to "VIEW", orientation_registrations_path(orientation) %></li>
<% else %>
<% if orientation.active? %>
<li><%= link_to orientation.class_time, new_orientation_registration_path(orientation) %>
(<%= orientation.current_number_seats %>/<%= orientation.seats %>)</li>
<% elsif orientation.class_date.before Date.today %>
<li><%= orientation.class_time %>(Class Closed)</li>
<% end %>
<% else %>
<li><%= orientation.class_time %>(Class full)</li>
<% end %>
<% end %>
<% end %>
What you are looking at is a the front end calendar view of a scheduling application. Based on differnt states, you see different information in each day on the calendar, ie, the number of seats remaining, vs. 'Class Full' vs. something else for Admins. Should I be pulling this logic into my model or controller somehow?
There are lots of ways to skin the cat. Which is 'right' is as much about personal preferences as anything else. That said, here are a few ideas that you might want to consider.
Use partials for each type of user
This may or may not be your driving concern but the outermost layer of decision making is based on user type so it may make sense to build a partial for each type of user. In this case you might have 'active_user_orientation_view' and 'guest_orientation_view'. Doing that reduces (this section) of your view down to a single if-then-else statement with pretty clear indication of your intent -- registered users see one thing and guests see something else.
Wrap-up repeating code into helper methods
Two of the list items are generated using the exact same code. Make it DRY! As an example, I'd probably drop down into the OrientationsHelper (app/helpers/orientations_helper.rb) and add a #orientation_full_item helper like this
def orientation_full_item(orientation)
content_tag(:li) do
"#{orientation.class_time} (Class full)"
end
end
With that helper in place, the two lines rendering the "Class full" message could be reduced to <%= orientation_full_item(orientation) %>. You could do the same for the list item that provides a link to the registration form. For consistency, you might do it for all of the list items. That would give you a view that very clearly declares its intentions.
Consider using a Presenter
Rather than litter your model (business logic) with view-oriented convenience methods, a better choice would be to create a new class that accepts an instance of the class and provides the same convenience methods. This is what the Presenter pattern is all about. The advantage of it is that you very clearly organize your code along the lines of it's intention -- biz logic stays together and stays untangled from view logic. In this case you might provide an ActiveUserOrientationPresenter and a GuestOrientationPresenter class, each of which provides a #list_item convenience method capable of rendering out the list item with its appropriate contents.
The PragProg guys have a title written by Bruce Williams with some great suggestions on how to build robust view code that is probably worth the money and time invested. One of the available code snippets deals specifically with presenters. You can read it http://media.pragprog.com/titles/warv/present.pdf.
Write unit tests that nail down the contents of all those <li> items with XPath.
Grab Nokogiri, and use Nokogiri::HTML::Builder to write all that in Ruby:
builder = Nokogiri::HTML::Builder.new do |doc|
doc.ul {
doc.li('data 1')
doc.li('data 2') if oodles_of_poodles?
doc.li('data 3')
}
end
puts builder.to_html
Now that it's all in one language, you can refactor it freely without constantly tripping over the escape tokens needed to mix two languages together.

If else statements in .html.erb in views

In rails, I often run into the situation where inside the views I'll do something like
<% if #some_condition_previusly_established_in_a_controller %>
<div class="one">123</div>
<% else %>
<div class="two">something else</div>
<% end %>
It looks a bit cluttery. Is this an acceptable way of working with views or not?
Unless you can think of a way to re-write this as a helper method, you're basically stuck with it looking kind of ugly. That's just how ERB is, as it was intended to be a minimal way of injecting Ruby into an otherwise plain-text template, not as something necessarily streamlined or elegant.
The good news is a syntax-highlighting editor will usually make your <% ... %> ERB blocks look visually different from your HTML so that can dramatically improve readability.
It's also why other representations like HAML have been created where that syntax is a lot less cluttered:
- if some_condition_previusly_established_in_a_controller
.one 123
- else
.two something else
For one or two such conditional logic in your views, I guess its fine but when your code gets bigger and you have multiple if..else..end and looks "cluttery", I think you should look at implementing "Presenter Pattern" which greatly cleans up your views by separating your logic to Presenters.
Here is a great tutorial I followed from Ryan Bates in his Rails Casts series on "Presenter Patterns from scratch". http://railscasts.com/episodes/287-presenters-from-scratch.
Have you tried?
<% #some_condition_previusly_established_in_a_controller ? <div class="one">123</div> : <div class="two">something else</div> %>
If your view contains lots of tags and HTML elements, you can put them into partials and logic into model
View:
<%= render :partial => #model.status %>
<%= render :partial => "file/path/#{#model.status}" %> # if your partial is in some different folder
If your status is one, then it would render the file _one.html.erb
If it is two, then it would render the file _two.html.erb automatically.
Model:
def status
if #some_condition
"one"
else
"two"
end
end
Yes, that is the standard (and yes, it looks cluttery).
If you're looking for a possibly cleaner alternative, check out: Conditional tag wrapping in Rails / ERB
You can always move the logic to the controller and leave the view clean(er).
Controller:
if #some_condition
#div_class = :one
#div_content = 123
else
#div_class = :two
#div_content = 'something else'
end
View:
<div class="<%= #div_class %>"><%= #div_content %></div>
Or using a helper:
<%= content_tag :div, #div_content, class: #div_class %>

Complex form with Rails

I have a form where I'd like to create a parent record and a child record at the same time. For a simple example let's say its a Company with the first Employee.
in my controller I do something like:
def new
#company = Company.new
#company.employees.new
end
and in my view this:
<%= form_for(#company) do |form| %>
<div>
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<%= form.fields_for :employees do |employee_form| %>
<div>
<%= employee_form.label :name %>
<%= employee_form.text_field :name %>
</div>
<% end %>
<% end %>
and back in my controller again:
def create
#company = Company.new(params[:company])
#company.employees << Employee.new(params[:company][:employees_attributes]["0"])
# save stuff
end
Question 1:
I couldn't get the employee collection on the company to be populated with the single employee created in the form. When I looked at the params I found the [:employees_attributes]["0"] stuff.
What I have works, but is there a cleaner way to do this?
Question 2:
If the validation doesn't pass for the employee I get a generic "Employees is invalid" instead of the Name required validator message. I get I am calling save on the collection and rails is doing its best to bubble a validation error up, but is there a cleaner way to do this so I can get the errors specific to the employee?
In Short
How can I clean this up so the related models are created automatically from the params, and so that I get the validation messages for a single employee.
Thanks for looking.
1) fields_for arranges for the child objects attributes to be nested inside the parent objects attributes in the params hash that gets sent back to the controller action. To get Rails to automatically update the child objects tell the parent model to accept nested attributes using the accepts_nested_attributes_for declaration.
2) There is an errors object for every ActiveRecord object. Loop through the errors list and display the messages.
Best way to achieve this is to create a partial and a view helper method that will take render the errors for you. then replace the generated errors messages in the forms with a call to your render_error_messages method. You have all the code to do this already in the generated forms. You just need to refactor that code into a partial, create the helper - which should accept an array of model names as a parameter then do what you want with the info. Wither render a partial for each model or render a partial that will deal with child objects as well as the parent object. Totally your call.
3) Change your new action to build rather that create a new child object so instead of
def new
#company = Company.new
#company.employees.new
end
do this
def new
#company = Company.new
#company.employees.build
end
4) Watch those Railscasts to see how accepts_nested_attributes works
http://railscasts.com/episodes/196-nested-model-form-part-1
and
http://railscasts.com/episodes/197-nested-model-form-part-2
Update
So how does the above information leave you in relation to your questions.
1) What I have works, but is there a cleaner way to do this?
You've fixed the new action as per point 3 above right? Now your create action can look like this
def create
#company = Company.new(params[:company])
# save stuff
end
Which is much cleaner as it has reverted to the original generated create action.
You may not think that's much of an update and therefore not that much cleaner. Well in isolation you'd be right. But consider that you could add as many relationships as you like ad add as many fields_for declarations as you like nd you could turn the user -> employee relationship into a has_many (I know that you wouldn't). You could do all that and your create and update actions stay EXACTLY the same and that's why it's cleaner.
2) is there a cleaner way to do this so I can get the errors specific to the employee?
Given my response in point 2 above you know that there is an errors object on the employee object as well as on the user object right? You also know now that you can loop through that errors object to get the messages right?
So you could do this
<% if #user.employee.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.employee.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.employee.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
At the risk of repeating myself I'll just say that you should refactor your error messages view code into a partial that will take any object as a parameter then you can call it from any view thus enabling you to change the styling and the functionality for all your forms.
Hope that's clearer

Resources