My five favorite “hidden” features in Rails 3.2
Rails 3.2 is out with great features on spotlight: faster development reloading, faster router and explain queries. However, every Rails release ships with minor features that do not get that much attention but still would be a great fit to your application. This blog post is about my five favorites “hidden” features of Rails 3.2.
1) Smarter content_tag_for
This feature written by Prem Sichanugrist provides a very simple but welcome clean up to your views. Both content_tag_for
and div_for
now accepts an array of records and automatically loop over each record. Therefore, instead of writing this:
@posts.each do |post| content_tag_for(:li, post) do ... end end
You can simply write:
content_tag_for(:li, @posts) do |post| ... end
2) Smarter migration generators
It is funny how some parts of Rails as old as the migration generators continue receiving improvements day after day. Rails 3.1 already added a feature that automatically generate indexes for associations, by simply invoking:
rails g scaffold Comment post:references title:string body:text
With the above, Rails will detect that post is a reference and it will automatically 1) add a post_id
integer column, 2) add an association to your model and 3) add an index to that column.
Right after 3.1 came out, I have pushed another small feature to the migration generator that simply makes the type attribute default to string. Therefore, you no longer need to write:
rails g scaffold Person name:string email:string
You could simply write:
rails g scaffold Person name email
Oddly enough, the idea for this feature came when I was preparing a presentation and the scaffold command could not fit in a slide (the so-called Presentation Driven Development). Anyhow, this small addition would not be enough to make to the best five “hidden” features of Rails 3.2. That’s when Dmitrii Samoilov comes in.
Dmitrii sent a pull request that allows you to specify which columns should have an (unique) index. So one could write:
rails g scaffold Person name:index email:uniq
And the generator will automatically generate an index for name and an unique index for e-mail. There are other features there as well, so don’t forget to checkout the CHANGELOG.
3) Flexible exception handling
When Rails 3.0 came out, one of the features that people suddenly missed was the ability to better handle exceptions. The issue was: since Rails 3 became a lot more Rack “fluent”, we had to move some features to the middleware stack and this forced us to move the whole exceptions handling as well. Rails 3.2 attempts to bring some customization back to the game by allowing you to set your own exceptions rack application that is invoked when a failure happens. For instance, you could set the exceptions application to your own router in yourconfig/application.rb
:
config.exceptions_app = self.routes
Now, every time there is an exception, your router is going to be invoked. Therefore, to render custom 404 pages, you could simply add to your router:
match "/404", :to => "errors#not_found"
And implement the logic in the controller as you wish! However, there are a few things to keep in mind if you go down this road:
- You need to use
match
in your routes and notget/post/put/delete
because such exceptions can happen in any HTTP request; - You won’t be able to see your custom exceptions in development unless you set
config.consider_all_requests_local
to false in yourconfig/environments/development.rb
. The reason is, if the request is considered local, Rails will always favor to show the debug exceptions page; - You can always access the original exception in the controller at
env["action_dispatch.exception"]
; - It is not possible to set cookies, the session nor the flash after an exception happens. They all were already serialized back to the client;
- Finally, the default exceptions application used by Rails that simply renders a page in
public/STATUS.html
is available here: action_dispatch/middleware/public_exceptions.rb
Remember that whatever you do in the errors controller, it should not be anything “fancy”. Keep it simple because something already went wrong with your application!
4) Custom partial paths
In order to render a partial for a given model, Rails 3.0 retrieved the partial name by calling:model.class.model_name.partial_path
. Grant Hutchins & Peter Jaros noticed that this was not very flexible because the class was responsible to define the partial path and therefore they decided to move this responsibility to the instance. In order to better understand how you can use this feature, let’s consider the following practical example.
Imagine your application have an activity feed and each activity in the feed has a certain type. Usually, each type is rendered differently. For example, if you consider a to-do-list application, activities could be both “marking a list as favorite” or “marking a task as done”. Usually, applications solve this by looping for each item and rendering its respective partial, something like this:
@activities.each do |activity| render :partial => "activities/#{activity.kind}", :locals => { :activity => activity } end
Now, you can solve this problem by defining to_partial_path
in the model (the methodto_partial_path
is part of the ActiveModel API and can be implemented in any object. The example above implements it in the model for convenience, but it could be a presenter, another ORM, etc):
class Activity < ActiveRecord::Base def to_partial_path() "activities/#{kind}" end end
And then invoking:
render :partial => @activities, :as => :activity
This will now work on Rails 3.2 because even though all activities are of the same class, each instance is actually responsible for telling Rails which partial should be rendered.
The difference here is not only in brevity, but also in performance. Although the first snippet works fine, it is slow. In the scenario where only one kind of activity happened, the first snippet will go through the render stack 30 times and lookup the same template in your filesystem 30 times. If you read Crafting Rails Applications you know that this lookup is cached, but even though it would certainly be faster if we didn’t have to do this 30 times, but once.
That’s where render :collection
or render :partial
with an array comes in. In such cases Rails will retrieve all templates up front skipping duplicates, and this new feature allows us to take advantage of it even if the partial lookup is dynamic. So, in the scenario where all the activities are of the same kind, the template lookup will happen just once and no longer 30 times. In other words, best case scenario becomes O(1)
, worst case scenario is still O(n)
.
5) Filtered chain logging is back
Another very small change that will make development more pleasant is that Rails will now log “Filter chain halted as CALLBACK_NAME rendered or redirected” every time a before/around/after filter in your controller halts the request. This was the case in Rails 2.3 but somehow got lost when Rails 3 came out. It is one of those small things you don’t know how much you missed until you see it again!
And what is your favorite Rails 3.2 “hidden” feature? Don’t forget to take a good look at the CHANGELOGs and check out many others improvements!
Tags: crafting rails applications, exception handling, rails 3.2