Putting presentation logic in controller is a good practice in Ruby?
-
12-12-2019 - |
Question
Some recommendation [1] suggest you to use
<%= current_user.welcome_message %>
instead of
<% if current_user.admin? %>
<%= current_user.admin_welcome_message %>
<% else %>
<%= current_user.user_welcome_message %>
<% end %>
But the problem is you must have the decision logic somewhere in your code.
My understanding is putting the decision in template
is better than controller
as it make your controller more clean. Is it correct?
Are there better way to handle this?
Solution
In my opinion, if the text is the only thing that changes, it doesn't belong in a view. If you needed to restructure the page, that's presentation logic. This, this is just data being different.
OTHER TIPS
You are not the first to wonder this. If views and controllers should have little to no logic, and the model should be presentation agnostic, where does presentation logic belong?
Turns out we can use an old technique called the decorator pattern. The idea is to wrap your model object with another class that contains your presentation logic. This wrapper class is called the decorator. The decorator abstracts away logic from your view, while keeping your models isolated from their presentation.
Draper is an excellent gem that helps define decorators.
The sample code you gave could be abstracted like so:
Pass a decorator to the view with @user = UserDecorator.new current_user
in your controller.
Your decorator could look as below.
class UserDecorator
decorates :user
def welcome_message
if user.admin?
"Welcome back, boss"
else
"Welcome, #{user.first_name}"
end
end
end
And your view would simply contain @user.welcome_message
Notice that the model itself doesn't contain the logic to create the messages. Instead, the decorator wraps the model and translates model data into a presentable form.
Hope this helps!
I would use a helper for this. Suppose you have to translate the welcome-message, based on some locale.
In the app/helper/user_helper.rb
write
module UserHelper
def welcome_message(user)
if user.admin?
I18n.t("admin_welcome_message", :name => user.name)
else
I18n.t("user_welcome_message", :name => user.name)
end
end
end
and in your view you can then just write
<%= welcome_message(user) %>
Note that the decorator/presenter offers a really clean object-oriented approach, but imho using a helper is much simpler and sufficient.
No, you don't want any conditionals at all in the user class nor the controller. The point of that example on the blog post is to make reference to polymorphism, just good old fashioned OO design.
# in application_controller for example
def current_user
if signed_in?
User.find(session[:user_id])
else
Guest.new
end
end
#app/models/user.rb
class User
def welcome_message
"hello #{name}"
end
end
#app/models/guest.rb
class Guest
def welcome_message
"welcome newcomer"
end
end
...you get the idea.
Only, instead of littering your model with presentation-only methods, create a decorator that acts as a presenter:
require 'delegate'
class UserPresenter < SimpleDelegator
def welcome_message
"hello #{name}"
end
end
And now current_user
looks like so:
# application_controller
def current_user
if signed_in?
UserPresenter.new(User.find(session[:user_id]))
else
Guest.new
end
end
Decorate the user model and add the welcome_message to it directly. Yes, this may involve some kind of conditional statement at some point.
http://robots.thoughtbot.com/post/14825364877/evaluating-alternative-decorator-implementations-in
I think you should watch the railscasts episode on Presenters for the answer.
Logic in the view is hard to maintain, we should put the business logic in the model and all view logic in helpers.
If you want your code to be in Object Oriented fashion, make use of Decorators (object oriented way of helpers)
Best Example : https://github.com/jcasimir/draper
Put the code defining current_user.welcome_message
in _app/helpers/application_helper.rb_, it will then be accessible by any view rendered with the application layout.
Another option is to define a custom helper module, one which is not necessarily associated with a given view or controller (See the video I linked below), and include
it in the modules of the view/controllers you wish to have that functionality in.
This is not something that is black and white. But, from what you have described it sounds like this is code that is obtrusive to stick in your application_controller.rb and it is not code with functionality which justifies it's own controller, the most effective and efficient option may be to create a custom helper module and include it in the helpers you wish to have that functionality. That said, this is ultimately a judgement call which the designer of the application (i.e. you) needs to decide upon.
Here is a good article outlining helper modules from May, 2011
Here is is a RailsCast outlining custom helper modules (i.e. custom as in modules not necessarily associated with a given controller or view). Short, sweet, and to the point.
You can define helper method for that stuff. I don't think it is a good Idea to make a welcome sentences in a model, but in the controller too. But you should try to make you views clean from code, and if you can use helpers for that then you should to.
A good practice would be to have real View
instances. Rails parody of MVP (there is difference, look it up) unfortunately seems to pretend that views are templates. That is wrong.
Views are supposed to contain the presentation logic in MVC and MVC-inspired patterns. They are also supposed to manipulate multiple templates and make decision on which templates to employ to represent the state and information from the model layer (yes, model is a layer not an ORM instance).
So, to answer the question: presentation logic has no place in controllers.