Putting presentation logic in controller is a good practice in Ruby? - ruby-on-rails

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?
http://robots.thoughtbot.com/post/27572137956/tell-dont-ask

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

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.

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.

Related

Recommended way to use Rails view helpers in a presentation class

I've been researching the 'recommended' way to use Rails view helpers (e.g. link_to, content_tag) in a plain ruby class, such as a presenter. It seems there's very little information on this front and I wanted to get an idea of what the Stack community thought.
So, the options we have are.. (note I'm using Rails 4, and am less concerned about older versions)
Include the required modules manually
This is probably the cleanest way, since only the helpers needed are included. However I have found this method to not work in some cases, as the usual view context provided in plain Rails helpers is configured for the current request. url_for wouldn't know about the current request for example, so the host might not match.
class MyPresenter
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::CaptureHelper
def wrapped_link
content_tag :div, link_to('My link', root_url)
end
end
Use ActionController::Base.helpers
Since Rails 3, ActionController::Base has included a helpers method to access the current view context. I believe the view context provided by this method is configured as it would be in a rails helper, but I might be wrong. There's not really any documentation about this which seems worrying, but it does work quite well in practice.
class MyPresenter
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
protected
def h
ActionController::Base.helpers
end
end
I believe this view context can also be mixed in with include, but the rails view helpers have hundreds of methods and it feels dirty to include them all indiscriminately.
Inject the view context when calling the presenter
Finally, we could just pass the view context to the class when it's initialized (or alternatively in a render method)
class MyPresenter
attr_accessor :context
alias_method :h, :context
def initialize(context)
#context = context
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
end
class MyController < ApplicationController
def show
# WARNING - `view_context` in a controller creates an object
#presenter = MyPresenter.new(view_context)
end
end
Personally I tend to lean towards the latter two options, but with no definitive answer from the Rails team (that I've been able to find) I felt a bit unsure. Who better to ask than Stack!
I would go with the mix of the second and third option, something like:
class MyPresenter
def initialize(helpers)
#h = helpers
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
private
attr_reader :h
end
Your second option require all your unit tests to be stubbed as ActionController::Base.helpers which maybe isn't a good option and your third option you're using a huge context to access just some methods.
I would really make that dependent on what kind of methods you use. If it's just the basics like content_tag etc. I would go for the ActionController::Base.helpers way. It is also possible to call some helpers directly, e.g. for paths inside models I almost always use something along the lines of Rails.application.routes.url_helpers.comment_path.
For controller-specific stuff the third option might be useful, but personally the "pure" way seems nicer. Draper has an interesting approach too: They save the view_context for the current request and then delegate the calls to h-helpers to it: https://github.com/drapergem/draper/blob/master/lib/draper/view_context.rb
It really is just a matter of preference. I would never include all helpers at once, as you already said. But the second option is quite nice if you want to build the presentation layer yourself without using a gem like Draper or Cells.

what is the best way to set author field on STI model

I have a basic model called Page and have many STI models based on Page such as
Drawing
Article
Story
etc...
I have separate controller and view for each of these STI models because I needed to customized the view layer based on the model type and have different logic for controller and hence separate controllers. However, I need the author field of all models to be set to current user. How do I do this in a single place?
For example, if I use before_action on Page controller and set the author, it affects the #page instance variable whereas my DrawingsController is using #drawing so it wont save the author of my #drawing unless I repeat the same code in DrawingsController.
Edit:
My controller hierarchy is
DrawingsController < PagesController
PagesController < ApplicationController
Both PagesController and DrawingsController have all the 7 restful actions. However, the actions on PagesController doesn't serve any purpose as I dont want any of my users to create Pages. I only want them to create the inherited STI classes like Drawings.
you could do this using some convention and meta programming in your controller hierarchy:
def add_author
model = instance_variable_get(:"##{controller_name.singularize}")
model.author = current_user
end
I'll be honest, I make no guarantees about the "best-practice"-ness of this answer, but I'll propose it anyways in case it helps to some degree. Also note that after rethinking the problem, I realized my first suggested solution in the comments was wrong, and the second is also not quite right either. So I'm posting a modified version of the second suggestion only:
Short answer: Let the PagesController handle most of the work, and delegate only to the subcontroller for model-specific things if needed. As phoet said, you can use a bit of meta programming (in a different way) to accomplish this.
class PagesController < ApplicationController
# pages controller stuff here
def create
#page = controller_name.classify.constantize.new(params[:page_params]) # I love Rails, don't you?
#page.author = current_user
handle_additional_create_actions
# For completeness of this example...
if #page.save
# render / redirect on success
else
# render errors
end
end
protected
# This method should be overwritten by sub controllers if needed
# Also, name this whatever you like, this is merely a verbose example for illustration purposes
def handle_additional_create_actions
# in the pages controller, this method does nothing
end
end
And, if there are additional things that need to be done by the model-specific controller:
class DrawingsController < PagesController
# drawing controller stuff here
protected
def handle_additional_create_actions
#page.some_other_field = some_other_data
end
end
A quick note: Note that in my suggestion, you're eliminating the model-specific variable names, meaning we don't have an #drawing and an #article, etc, anymore. Your models are all, essentially, types of Page objects, and so we're going to call it by its general name as a convention. That way, when you ask the DrawingsController to do something specific for the Drawing class, it knows that instance can be accessed via our generically named #page object.
So ultimately, the PagesController does the heavy lifting, regardless of which concrete model type you're dealing with. That way, only general page stuff is found in the pages controller, and drawing, article or story-specific stuff is found in their respective concrete controllers.

Extending model to include "helper" methods

I have a kind of social network website.
I have a logic to create the path for the user, and select an avatar for each user described in UsersHelper methods user_path(user) and user_avatar(user).
Instead I want to have methods like user.path and user.avatar, but I don't want to have that code inside the model file.
I tried extending the User class inside the helper:
module UsersHelper
class User
def avatar
...
end
end
end
That doesn't work - the method I added aren't recognized (I'm guessing Rails dynamically generates the ActiveRecord class on demand so my methods don't apply?)
I'd really appreciate ideas
First, there's a reason helpers are separated from models. According to the MVC pattern your model shouldn't know anything about your helpers, and not vice versa either (the latter is usually ignored in Rails, though).
But, if you want to do this, you need to do class ::User in model UsersHelper, or the following:
module UsersHelper
end
class User
end
The reason for this is that Ruby namespaces classes. So you defined UsersHelper::User, while your model is called User. Calling the class ::User or defining it outside the module forces it into the root namespace.
However, as I said, this is not the "correct" way to do it. A better way would be how you're already doing it, or maybe using a decorator pattern.
Draper is an awesome little gem that does an excellent job of achieving the functionality you're looking for (adding view / presentation specific code while still making it "feel" like the model you're working with). We've removed almost all of our model-specific helpers after starting to use draper.
Basically, it works by defining decorators, which work like a wrapper around your model. A decorator looks and feels like the model it's decorating, but can have additional functionality defined on top of it. In addition, you can restrict access to certain fields and lock stuff down if you like.
For your example, adding a decorator would be as simple as:
(in app/decorators/user_decorator.rb)
class UserDecorator < ApplicationDecorator
decorates :user
def avatar
# your implementation
end
(in your controller)
def index
respond_with UserDecorator.decorate(User.all)
end
(in your views)
<div class='avatar'><%= user.avatar %></div>
<div class='name'><%= user.name %></div>
Helpers are intended to use with the views, not with the models.
If you wish to have something like user.avatar, you have to add it to your model.
If you want to stick it in the helpers, then in the UsersHelper add a user_avatar method.

Where to put user access controls? The controller or the model?

I understand that we're supposed to avoid putting logic in the controller. So what is the proper way to implement things like user access controls. Let's suppose I have User, where each instance has a flag method admin? that determines whether the user can access information from other users.
Option 1: Put access controls in custom model
Model:
class User < ActiveRecord::Base
def self.get_list(accessor)
return [] unless accessor.admin?
self.all
end
end
Controller:
class UsersController < ApplicationController
def index
#users = User.get_list(current_user)
end
end
current_user would probably be defined somewhere in the application controller.
Option 2: Put access controls in the controller
Model:
class User < ActiveRecord::Base
end
Controller:
class UsersController < ApplicationController
def index
#users = current_user.admin? User.all : []
end
end
There are also some peripheral consequences such as where tests go and how they're implemented.
My instinct is that the first of the two options is preferable, but I've only ever used option 2 in the past. Also, it seems like the generally accepted (as far as I can tell) practice of putting on action-wide access filters is done at the controller level as in:
class UsersController < ApplicationController
before_filter :verify_logged_in
end
Any logic controlling routing of your application belongs in your controller. As for your example, the first is preferable but really there's not much in it.
It's easy for opinionated frameworks make us almost obsessive about doing things in a perceived correct way. In your example such a tiny amount of logic would, in my opinion, be perfectly fine to leave in your controller. If you were to abstract it, give it a descriptive name that described better what the method is doing otherwise you're simply making your code needlessly difficult to read.
As a rails rookie, the second option to me seems far easier to read and comphrehend. I prefer how I can read the single line in the controller and see exactly what you are doing.
In the first example, this logic is hidden someplace else (obviously the model, but I'm a rookie remember!) and seems slightly verbose (for this particular example).
I don't mean to suggest keeping it easier for new kids is A Good Thing, just pointing out a preference.

Tracking model changes in Rails, automatically

In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master

Resources