I've come to appreciate the "skinny controllers" philosophy in Rails that says that business logic should not be in controllers, but that they should basically only be responsible for calling a few model methods and then deciding what to render/redirect. Pushing business logic into the model (or elsewhere) keeps action methods clean (and avoids stubbing long chains of ActiveRecord methods in functional tests of controllers).
Most cases I've run across are like this: I have three models, Foo, Bar, and Baz. Each of them has a method or scope defined (call it filter) that narrows down the objects to what I'm looking for. A skinny action method might look like:
def index
#foos = Foo.filter
#bars = Bar.filter
#bazs = Baz.filter
end
However, I've run into a case where the view needs to display a more hierarchical data structure. For example, Foo has_many bars and Bar has_many bazs. In the view (a general "dashboard" page), I'm going to display something like this, where each foo, bar, and baz has been filtered down with some criteria (e.g. for each level I only want to show active ones):
Foo1 - Bar1 (Baz1, Baz2)
Bar2 (Baz3, Baz4)
-----------------------
Foo2 - Bar3 (Baz5, Baz6)
Bar4 (Baz7, Baz8)
To provide the view with the data it needs, my initial thought is to put something crazy like this in the controller:
def index
#data = Foo.filter.each_with_object({}) do |foo, hash|
hash[foo] = foo.bars.filter.each_with_object({}) do |bar, hash2|
hash2[bar] = bar.bazs.filter
end
end
end
I could push that down to the Foo model, but that's not much better. This doesn't seem like a complex data structure that merits factoring out into a separate non-ActiveRecord model or something like that, it's just fetching some foos and their bars and their bazs with a very simple filter applied at each step.
What is the best practice for passing hierarchical data like this from a controller to a view?
You could get #foos like this:
#foos = Foo.filter.includes(:bars, :bazs).merge(Bar.filter).merge(Baz.filter).references(:bars, :bazs)
Now your relation is filtered and eager loaded. The rest of what you want to do is a concern of how you want it presented in the view. Maybe you'd do something like this:
<% Foo.each do |foo| %>
<%= foo.name %>
<% foo.bars.each do |bar| %>
<%= bar.name %>
<% bar.bazs.each do |baz| %>
<%= baz.name %>
<% end %>
<% end %>
<% end %>
Any kind of hash-building in the controller is unnecessary. The level of abstraction you're working with in the view is reasonable.
One of the widely accepted best practices for this kind of thing if Extracting a Form object. Bryan Helmkamp from Code Climate has written a very good blog post about this:
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
See section three "Extract Form Objects".
The thing is, yes, you should definitely move business logic out of your controllers. But it does not belong in your data models (Activerecord models) either. You will want to use a combniation of number 2, "Extract Service Objects", and 3, "Extract Form Objects", in order to build a good structure for your app.
You can also watch a good video of Bryan explaining these concepts here: http://www.youtube.com/watch?v=5yX6ADjyqyE
For more on these topics, it is also highly recommended that you watch Confreaks conference videos: http://www.confreaks.com/
Related
Currently I'm performing some calculations in my views, which is a bad thing, of course:
<% categories.each do |c| %>
....
<%= c.transactions.sum("amount_cents") %>
....
<% end %>
I am researching ways that will help me to refactor the above issue.
One thing is to move the calculation to my controller
#category_sum = #transaction.sum("amount_cents")
Which is probably a better solution, but you know. Not perfect.
Since I have many users, I do not see how can I move the calculator logic into my Model. So I guess I might need to use a new Class, create a bunch of methods (sum, average, etc.) and use them in the views? Am I on the right track? Will be thankful for any advice on how to restructure my code and design and implement this Class.
One mean to isolate view logic is to use presenters.
A presenter allows you to do something like that :
<% categories.each do |c| %>
....
<% present c do |category| %>
<%= category.transaction_sum %>
<% end %>
....
<% end %>
You then have a presenter class in app/presenters/category_presenter.rb :
class CategoryPresenter < BasePresenter
presents :category
def transaction_sum
category.transactions.sum("amount_cents")
end
end
Of course, it is best used if you have many methods in that presenter (but once you begins to reduce view logic, it's quick to fill presenters).
The implementation used here rely on what is describe in this pro railscast. The basic idea is simply to have a #present helper that infers a class name based on object class, load and initialize the proper presenter class.
An other popular alternative is to use drapper, which use the concept of decorator, but a presenter is basically a decorator.
The main code smell you're seeing is called the law of Demeter (which, like many programming "laws", you should think of it more like a "guideline of Demeter").
What you could do is move the actual calculation step into a method on the category, e.g.
class Category < ActiveRecord::Base
def transaction_amount
transactions.sum("amount_cents")
end
end
<% categories.each do |c| %>
....
<%= c.transaction_amount %>
....
<% end %>
Technically speaking the calculation is still performed while rendering the view, but the logic for how that summed amount gets calculated is no longer inside the view itself. All the view now cares about is that it can send the message transaction_amount to the category objects. This also leaves room for you to add a cache for the sums, or for you to stop passing actual records, but instead pass static objects (that aren't ActiveRecord models) that come out of some piece of code that performs the sum in a more efficient manner.
I have a view that I call school.html.erb and in that view I have Ruby code that calculates the average rating for that school.
Like this:
<span class="label label-info">
<%= #school.reviews.average(:rating).round(1) unless #school.reviews.blank? %>
</span>
I like to move this somewhere else, should I put this in the model, a helper or in a controller. And if I do that how can I call it from the view.
I have the following models: User, Review and School.
Calculating the average belongs in the model:
class School < ActiveRecord::Base
...
def average_review_rating
return nil if reviews.blank?
reviews.average(:rating)
end
end
Rounding the average belongs in the view, because it is formatting. Put calculation in the model, and formatting in the view (or a helper).
<%= #school.average_review_rating.round(1) if #school.average_review_rating %>
This can be shorted considerably using the andand gem.
<%= #school.average_review_rating.andand.round(1) %>
You may wish to push the rounding into the helper, where it can be independently tested:
class SchoolHelper
def format_rating(n)
n.andand.round(1)
end
end
which is used like this:
<%= format_rating(#school.average_review_rating) %>
I think I makes a lot of sense to put it into a model as a method.
class School
def avg_rating
reviews.average(:rating).round(1) unless reviews.blank?
end
end
Why it makes sense? Well, school rating is something you'll likely have to access in plenty of other places: other views, other models, etc. By putting it into a method, you may it look like just a property of School. school.avg_rating is pretty much saying for itself: "School, what's your rating?"
Why view won't do? You may want to access it in other views. There are helpers for that, right? But why helpers won't do? You may also want to access it from other models. Helpers aren't meant to calculate data, they're meant to format it and do other similar stuff.
Put in in the model (School):
def reviews_average
reviews.average(:rating).round(1) unless reviews.blank?
end
View:
<%= #school.reviews_average %>
Reason: Do not put DB intensive procedures into the view because the rendering speed depends on it.
I would say it works best in the model, because it's working with the data. This kind of follows the "fat model, skinny controller" principle.
Another rational option would be a decorator or presenter, using a library like Draper.
I need to build a nested comments system in a Rails 3 application that allows for comments on many models (articles, posts, etc) and am debating rolling my own solution along the lines of this post. There are gems available like acts_as_commentable_with_threading with awesome_nested_set, but they feel bloated for my needs.
I need to be able to add comments to multiple models
I need to be able to add comments to comments, infinitely deep
I need to be able to efficiently retrieve all descendants for a post, article, etc
I need to be able to efficiently present the comments in their appropriate nesting
My question is, were I to roll my own solution what potential hiccups I could face. I want to avoid going down one path only to reach a dead end. My initial concerns relate to efficiently querying for children. Say, for instance, getting a list of an articles descendant comments (children and children of children).
Anyone have input on this? Thanks.
There are two kinds of nesting you can do: a tree and a nested set.
acts_as_tree stores only a parent_id and so it is really fast to write new entries, but you have to recursively walk the chain of id numbers to get a list of all the children. This is not a good choice when you need to do lots of reads.
awesome_nested_set records three bits of information: parent_id, lft and rgt. The left and right values are calculated so that they contain all the children ids for that entry. This is very fast for read operations but slower to write.
In your case I think awesome_nested_set is more appropriate. You might think it seems overkill, but nested sets get complicated in a hurry. You need to use the nested set pattern to efficiently query children.
You only need to use two methods to render the entire tree of comments: iterate over Comment.roots and for each comment, render comment.children.
class ModelController < ApplicationController
def show
#model = Model.find_by_id(params[:id])
#comments = #model.comments.roots
end
end
<ul id="comments">
<% #comments.each do |comment| %>
<%= render :partial => 'comment', :object => comment %>
<% end %>
</ul>
<!-- _comment partial -->
<li class="comment">
<!-- comment markup -->
<% if comment.children.present? %>
<ul>
<%= render :partial => 'comment', :collection => comment.children %>
</ul>
<% end %>
</li>
To save a nested comment, simply fill in the parent_id and awesome_nested_set will do the rest. I don't think rolling your own solution will be any more elegant than this.
Update: Looks like the awesome_nested_set hasn't been updated in some time. Check out ancestry instead. Does basically the same things.
A tree structure, yes, is a good idea - however it is the query execution itself that is of the utmost concern and i don't think most tree implementations as a gem take this into account and whilst nested set is alright - i reckon if something really bad happened, its overcomplicated in terms of write operations.
I'd check out Recursive CTEs - whilst not database agnostic, it gives you a nice data structure to work with without having to have extra attributes to track.
I used to do something like this by adding the following fields to the comments table:
attached_to_controller (string)
attached_to_id (int)
Then when showing I would make an AJAX call to the comments index and filter based on these two fields.
Of course, when creating comments you need to pass the appropriate values for these fields.
I've had a problem with the following issue in Rails and ASP.Net MVC. Often there are multiple widgets of functionality on a page, yet one controller action is supposed to render the page. Let me illustrate:
Let's say I have a normal e-commerce site, and the menu is made of categories, while the page is to display an group of products.
For the products, let's say I have an action on a controller that looks something like:
def product_list
#products = Products.find_by_category(:name => 'lawnmowers')
end
And I have a layout with something like
<div id="menu"><%= render :partial => 'menu' %></div>
<div id="content"><%= yield %></div>
The products have a view...
<%= render :partial => 'product', :collection => #products %>
(note I've ommited the product view as irrelevant)
And the menu has a partial...
<% Category.each {|c| %>
<%= render :partial => 'menu_node', :locals => { :category => c } %>
<% } %>
The line I have a problem with is the "Category.each.do" in the view. I'm fetching data in the view, as opposed to using variables that were set and bound in the controller. And it could easily be a more complex method call that produces the menu.
The solutions I've considered are:
-A view model base class that knows how to get various pieces of data. But you could end up with one of these for each conceptual "section" of the site.
-a local variable that populates at the top of each method (violates DRY)
-the same thing, but in a before_filter call
None of these seem very elegant to me. I can't help but look at this problem and think that a MVP presenter per view (not screen) is a more elegant solution.
ASP.Net MVC has render action (different from rails render :action), which does address this, but I'm not sure what I think of that solution.
Thoughts? Solution suggestions?
Added Note:
The answers provided so far are good suggestions. And they apply to the example I gave, where a menu is likely present in every layout, and is clearly secondary to the product data.
However, what if there is clearly no second class citizen? Portal type sites commonly have multiple unrelated widgets, in which each is important.
For example, What if this page was displaying weather trends, with widgets for temperature, humidity, and precipitation (and each is a different model and view type).
In rails we like to have a concept of thin-controllers, thick-models. So I think you're right to not want to have variables set in the controller.
Also, in order to enable a more-complex method later on, I recommend doing something like:
/app/controllers/application_controller.rb
before_filter :add_menu_nodes
def add_menu_nodes
#menu_nodes = Category.menu_nodes(current_user)
end
/app/views/layouts/application.html.erb
<%= render :partial=>:menu, :locals=>{:categories=>#menu_nodes} %>
/app/models/category.rb
def self.menu_nodes(current_user)
Category.all.order(:name)
end
That way in the future you could update Category.menu_nodes with a more complicated solution, based on the current user, if you need.
Forgive me if I butcher the Ruby (or misunderstand your question), but what's wrong with
class section_helper
def menu( section )
// ...
menuBuiltAbove
end
end
in the view
<%= section_helper.menu( 'section' ) %>
?
This question relates to cleaning up the view and giving the controller more of the work.
I have many cases in my project where I have nested variables being displayed in my view. For example:
# controller
#customers = Customer.find_all_by_active(true)
render :layout => 'forms'
# view
<% #customers.each do |c| %>
<%= c.name %>
<% #orders = c.orders %> # I often end up defining nested variables inside the view
<% #orders.each do |o| %>
...
<% end %>
<% end %>
I am fairly new to RoR but it seems that what I'm doing here is at odds with the 'intelligent controller, dumb view' mentality. Where I have many customers, each with many orders, how can I define these variables properly inside my controller and then access them inside the view?
If you could provide an example of how the controller would look and then how I would relate to that in the view it would be incredibly helpful. Thank you very much!
I don't think there is anything drastically wrong with what you're doing. Looping through the customers and outputting some of their attributes and for each customer, looping through their orders and outputting some attributes is very much a view-oriented operation.
In the MVC architecture, the controller has responsibility for interacting with the model, selecting the view and (certainly in the case of Rails) providing the view with the information it needs to render the model.
You might consider extracting the code into a view helper though, if you have that exact code repeated more than once. You could even genericize it, passing in the name of a model and association. I haven't tested it, but you should be able to do something like this:
def display_attributes(models, association, attribute, association_attribute)
content = ''
models.each do |m|
content << "<p>#{m.attribute}</p>"
associated_models = m.association
associated_models.each do |am|
content << "<p>#{am.association_attribute}</p>"
end
end
content
end
Then in the view, you could use the helper like this:
<%= display_attributes(#customers, orders, name, name) %>
Obviously you would change the HTML markup within the helper method to suit your requirements. Note that if you're not using Rails 3 then you'll want to escape the output of the attribute names in the helper method.
I don't think there's anything wrong with your code. I'd just suggest for you to use a :include in your find
#customers = Customer.find_all_by_active(true, :include => :orders)
to reduce the number of queries.
I see nothing wrong with the code as you showed.
You are mixed up about the "intelligent controller, dumb view" approach though, i tend to prefer the "skinny controller, fat model", so indeed the view should be dumb, but you put the intelligence inside your model, and your helpers (or use a presenter), but definitely not in the controller.