Today we will talk about code organization in Ruby on Rails projects. As everybody knows, Ruby on Rails is a conventional framework, which means you should follow framework architects’ decisions (put your controllers inside app/controllers
, move all your logic into models, etc.) But there are many open questions around those conventions. In this write-up I will try to summarize my personal experience and show how I usually solve these problems.
Here is the list of questions we will talk about:
-
You have some logic in your view, which uses your models extensively. There are no places in other views with such logic. The classic recommendation is to move this code into a model, but after a short time your models become bloated with stupid one-off helper methods. The solution: pattern Presenter.
-
Your constructor contains a lot of code to retrieve some values for your views from the database or another storage. You have a lot of
fragment_exist?
calls to ensure no of your data is loaded when corresponding fragment is already in cache. It’s really hard to test a particular action because of it’s size. The solution: pattern Presenter. -
You have a partial, used everywhere on the site. It accepts a lot of parameters to configure how rendered code should look like. The header of this partial, which initializes default values of parameters, becomes larger and larger. The solution: cells plugin.
Please note: sample application is available on GitHub.
Presenter Pattern
Okay, you have an idea when to use this patterns. Let’s look at the example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class HomeController < ApplicationController def show unless fragment_exist?('home/top_videos') @top_videos = Video.top.all(:limit => 10) end unless fragment_exist?('home/categories') @categories = Category.all(:order => 'name DESC') end unless fragment_exist?('home/featured_videos') @featured_videos = Video.featured.all(:limit => 5) end unless fragment_exist?('home/latest_videos') @latest_videos = Video.latest.all(:limit => 5) end end end |
And the view:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <h1>Home page</h1> <div id="top_videos"> <h2>Top videos</h2> <% cache('home/top_videos') do %> <%= render 'videos', :videos => @top_videos, :hide_description => true %> <% end %> </div> <div class="tabs"> <ul id="taxonomy"> <li><a href="#" id="categories" class="current">Categories</a></li> </ul> <div class="categories_panel"> <h2>Categories</h2> <% cache('home/categories') do %> <%= render 'categories' %> <% end %> </div> </div> <div class="box"> <div id="latest"> <h2>Latest videos</h2> <% cache('home/latest_videos') do %> <%= render 'videos', :videos => @latest_videos, :hide_thumbnail => true %> <% end %> </div> <div id="featured"> <h2>Featured videos</h2> <% cache('home/featured_videos') do %> <%= render 'videos', :videos => @featured_videos, :hide_thumbnail => true %> <% end %> </div> </div> |
Note: this code is available in the first commit of my presenter example project.
Scary code, isn’t it? So let’s refactor it using Presenter pattern. I prefer to put presenters into a separate folder app/presenters
, so first we should add it to Rails load path. Add this line to your config/environment.rb
:
1 2 3 | config.load_paths += %W( #{Rails.root}/app/presenters ) |
Now we are ready to write our presenter (app/presenters/home_presenters/show_presenter.rb
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module HomePresenters class ShowPresenter def top_videos @top_videos ||= Video.top.all(:limit => 10) end def categories @categories ||= Category.all(:order => 'name DESC') end def featured_videos @featured_videos ||= Video.featured.all(:limit => 5) end def latest_videos @latest_videos ||= Video.latest.all(:limit => 5) end end end |
Sometimes presenters depend on parameters, so feel free to add an initialize
method. It could accept particular params or whole params collection:
1 2 3 | def initialize(video_id) @video_id = video_id end |
Now let’s refactor our controller:
1 2 3 4 5 | class HomeController < ApplicationController def show @presenter = HomePresenters::ShowPresenter.new end end |
Whoa, that’s nice! View now is little different:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <h1>Home page</h1> <div id="top_videos"> <h2>Top videos</h2> <% cache('home/top_videos') do %> <%= render 'videos', :videos => @presenter.top_videos, :hide_description => true %> <% end %> </div> <div class="tabs"> <ul id="taxonomy"> <li><a href="#" id="categories" class="current">Categories</a></li> </ul> <div class="categories_panel"> <h2>Categories</h2> <% cache('home/categories') do %> <%= render 'categories' %> <% end %> </div> </div> <div class="box"> <div id="latest"> <h2>Latest videos</h2> <% cache('home/latest_videos') do %> <%= render 'videos', :videos => @presenter.latest_videos, :hide_thumbnail => true %> <% end %> </div> <div id="featured"> <h2>Featured videos</h2> <% cache('home/featured_videos') do %> <%= render 'videos', :videos => @presenter.featured_videos, :hide_thumbnail => true %> <% end %> </div> </div> |
Presenters testing is much easier than testing of bloated controllers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | describe HomePresenters::ShowPresenter do before :each do @presenter = HomePresenters::ShowPresenter.new end it 'should respond to :top_videos' do expect { @presenter.top_videos }.to_not raise_error end it 'should respond to :categories' do expect { @presenter.categories }.to_not raise_error end it 'should respond to :featured_videos' do expect { @presenter.featured_videos }.to_not raise_error end it 'should respond to :latest_videos' do expect { @presenter.latest_videos }.to_not raise_error end end |
Please note: this code is available in the second commit of my presenter example project.
Please note: you should not do any manipulations on models in presenters. They only decorate models with helper methods to be used inside controllers or views, nothing else. There are several articles describing a Conductor pattern as a presenter, do not repeat their mistakes. See the first link in the list below to get an idea about the differences.
Related links:
- Presenters & Conductors on Rails
- Presenters in Ruby on Rails Applications
- Rails Model View Controller + Presenter?
- RailsConf Europe 07: Presenter Links
Cells Plugin
Okay, now we have a clean controller. But what about views? Let’s take a look at the videos
partial:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <% hide_thumbnail = hide_thumbnail === true; hide_description = hide_description === true; css_class ||= 'videos' style ||= :div case style.to_sym when :section parent_tag = 'section' child_tag = 'div' when :list parent_tag = 'ul' child_tag = 'li' else parent_tag = 'div' child_tag = 'div' end %> <% content_tag parent_tag, :class => css_class do %> <% videos.each do |video| %> <% content_tag child_tag do %> <h3><%= h video.title %></h3> <%= image_tag(video.thumbnail_url, :class => 'thumb') unless hide_thumbnail %> <%= '<p>%s</p>' % h(video.description) unless hide_description %> <% end %> <% end %> <% end %> |
So, what the heck? Is this a view or a controller? Remember old PHP days, with all this spaghetti code? That is it. It’s hard to test, it looks scary, it bad. So here cells plugin comes to the stage.
First, we need to install the plugin:
1 | script/plugin install git://github.com/apotonick/cells.git |
Now let’s generate a cell:
1 | script/generate cell Video videos |
And write some code (app/cells/video.rb
):
1 2 3 4 5 6 7 8 9 10 11 12 13 | class VideoCell < Cell::Base def videos @videos = @opts[:videos] @hide_thumbnail = @opts[:hide_thumbnail] === true; @hide_description = @opts[:hide_description] === true; @css_class = @opts[:css_class] || 'videos' view = (@opts[:style] || :div).to_sym view = :div unless [:section, :list].include?(view) render :view => "videos_#{view}" end end |
app/cells/video/videos_section.html.erb
:
1 2 3 4 5 6 7 | <section class="<%= @css_class %>"> <% @videos.each do |video| %> <div> <%= render :partial => 'video', :locals => { :video => video } %> </div> <% end %> </section> |
app/cells/video/videos_list.html.erb
:
1 2 3 4 5 6 7 | <ul class="<%= @css_class %>"> <% @videos.each do |video| %> <li> <%= render :partial => 'video', :locals => { :video => video } %> </li> <% end %> </ul> |
app/cells/video/videos_div.html.erb
:
1 2 3 4 5 6 7 | <div class="<%= @css_class %>"> <% @videos.each do |video| %> <div> <%= render :partial => 'video', :locals => { :video => video } %> </div> <% end %> </div> |
app/cells/video/_video.html.erb
:
1 2 3 | <h3><%= h video.title %></h3> <%= image_tag(video.thumbnail_url, :class => 'thumb') unless @hide_thumbnail %> <%= '<p>%s</p>' % h(video.description) unless @hide_description %> |
And the view:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <h1>Home page</h1> <div id="top_videos"> <h2>Top videos</h2> <% cache('home/top_videos') do %> <%= render_cell :video, :videos, :videos => @presenter.top_videos, :hide_description => true %> <% end %> </div> <div class="tabs"> <ul id="taxonomy"> <li><a href="#" id="categories" class="current">Categories</a></li> </ul> <div class="categories_panel"> <h2>Categories</h2> <% cache('home/categories') do %> <%= render 'categories' %> <% end %> </div> </div> <div class="box"> <div id="latest"> <h2>Latest videos</h2> <% cache('home/latest_videos') do %> <%= render_cell :video, :videos, :videos => @presenter.latest_videos, :hide_thumbnail => true %> <% end %> </div> <div id="featured"> <h2>Featured videos</h2> <% cache('home/featured_videos') do %> <%= render_cell :video, :videos, :videos => @presenter.featured_videos, :hide_thumbnail => true %> <% end %> </div> </div> |
Wow! That’s pretty easy to read and modify. All the logic is in the code now, all the views are easy to read, and moreover: it’s more than easy to test now! I have a little plugin called rspec-cells, and I have committed a patch yesterday to get it working with the latest RSpec. Here is how you spec could look like:
1 2 3 4 5 6 7 8 9 | describe VideoCell do context '.videos' do it 'should initialize :videos variable' do videos = mock('Videos') render_cell :videos, :videos => videos assigns[:videos].should be(videos) end end end |
So it looks almost like a classic Ruby on Rails controller spec. I hope to review the code in nearest feature and will send a pull request to the cells plugin author. Of course, if you
found a bug, feel free to contact me.
Please note: this code is available in the third commit of my presenter example project.
Related links:
That’s all I wanted to show you today. I think a Presenter Example project will be updated periodically, so follow it on GitHub, follow me in Twitter or on GitHub to get instant updates. Also take a look at the rspec-cells plugin, maybe you will have some time to make it better.
Now that’s a useful article, thanks. I think i should have used cells in my current project from the start =(
I was wondering why this plugin is not used everywhere. I can’t image how people can live without it!
Great post. I really like the way you organize everything. I have done something similar to a cell (creating a tableless model that a view partial uses) before I realized there is a convention out there already. I am definitely going to try and work this in to my next project.
Cells is “Components done right” :)
Nice, clear and extremelly usefull, rails community needs more of these. thanks
Wow, I wish I discovered this ages ago.
Have a look at Florian Hankes presenter plugin:
A Representer Pattern for Rails
We use it on restorm.com.
As for the problem of tracking and matching cache keys in controllers and views we’re using lazy loading of data used in cached fragments with what we call “cache proxy” which is just an implementation of the blank slate pattern.
Unless I’ve missed something, your HomePresenters::ShowPresenter implementation currently displayed in the post still queries the database with each request. You’ve lost the cache fragment checking and, while those queries will only run once per request (by storing the result to an instance variable) will still run once for *each* request – rather than once per empty cache. Otherwise, nice job. :)
2Nathaniel: You’ve missed something :-) These methods are called from inside
cache
blocks, so they will not be executed when fragment has been already cached before.Thank you for your comment!
Ahh .. you moved the actual query to occur from within the View rather than the Controller. I got it. :-D
The controller simply defines the @presenter, its methods – and thus queries – are only exercised within the view’s cache block. Apparently it’s too early in the morning for me to be evaluating someone’s code. ;)
Nice job.
Yep. Actually I’ve changed a view architecture from push (when you are loading your data in controller and “pushing” it to the view) to pull (when controller performs updates only, but views are “pulling” data by themselves). Here is an article about pull vs. push MVC architecture.
Cells also works great with Apotomo to provide stateful, ajaxy widgets:
http://rubyconf2008.confreaks.com/components-are-not-a-dirty-word.html
I agree with FX: this is definitely components done right.
It totally makes sense to *not* have to check for fragments in your controller, and makes it easier if you need to change fragment names. And the cell plugin makes everything fit together even better. Thanks for that!
What for the case equality operator is used here:
?
Also let’s just stop using old verbose render parameters and just
In cells you should use old good
render :partial
. In other places I userender 'partial'
. Be careful.Doh :) sorry then,
Btw. is there anything that prevents adding such a semantics?
From a quick look at
Cell::View#render
it seems that it could be updated to match the Rails version, but can’t be sure about that.You’re welcome :-) Think, author will accept your pull request quickly.
It seems to me that “cells” offer no compelling advantage over simply using presenters with Rails partials.
one thing I especially love in ruby, you don’t have to think about “patterns” at all. You just write ruby code and use one of them almost intuitivelly, without even remembering it got a big chapter in some Big Java/C++ Book and a Captial Letter Starting Name.
Representers, Proxies, Factories, whatever, sometimes what you’ll get is just another level of abstraction, not really useful at all on a second thought. Please, don’t bring that hell to ruby.
Back to subject: Long time ago we had components, but then world was filled with rumours about them being “slow”, and suddenly they fell out of usage and rails core. I yet to see them reintroduced back, in core, lightweight and isolated, but meanwhile both representers and cells functionality could be easily achieved using helpers alone, and I do it all the time.
differences are:
1) you have the same namespace for @vars (but locals are just as good) and helper names. It’s a bad thing because of possible nameclashes, but it’s a good thing for ctags and grep users.
<%= video_link @video, :style => :brief %>
is DRY enough2) you’ll get
ActionView::TemplateError
with 500 status instead of 404ActiveRecord::NotFound
if you trigger some faultyModel.find
in helper and not in controller, but it’s not a big deal and it could be fixed by several plugins.3) there’s no default place for such helpers, so what i have to do is just to group “presenter helpers” and “cell and decoration helpers” in different helper modules or in different sections of same “resource specific” helper module.
[…] Simplifying your Ruby on Rails code: Presenter pattern, cells plugin | Dmytro Shteflyuk’s Hom… […]
@alexey: one big advantage that Cells (and other ‘heavier’ approaches) have over helpers/partials is inheritance.
The example in this article may not be sufficiently complex to justify for inheritance between Cells, but believe me that this can tremendously simplify an app (compared with complex shared view partials).
Thanks for writing this up.
This technique is really invaluable as your app grows and you end up with small encapsulated bits of logic that are too big/messy for partials/helpers but are also kind of inappropriate for your models and controllers.
I’m really hoping something like this, or a conversion of Merb Parts, finds its way into Rails 3.
Nice writeup, I like the simplicity of this particular Presenter.
How would you go about adding its specs to autospec? It doesn’t seem to include them automatically.
Replying to myself, here is how to add the presenter specs to autospec. Put the code below in a .autotest file in your project root.
2
3
4
5
at.add_mapping(%r%^app/presenters/(.*)\.rb$%) { |_, m|
["spec/presenters/#{m[1]}_spec.rb"]
}
end
We could actually get rid of
HomePresenters::ShowPresenter
and use model methods (and scopes) directly. I don’t think Presenter role is to fetch data anyway. Actually Cells do all the work we need here. And if you use decorators (Draper), everything works just fine.