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 your config/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:

  1. You need to use match in your routes and not get/post/put/delete because such exceptions can happen in any HTTP request;
  2. You won’t be able to see your custom exceptions in development unless you set config.consider_all_requests_local to false in your config/environments/development.rb. The reason is, if the request is considered local, Rails will always favor to show the debug exceptions page;
  3. You can always access the original exception in the controller at env["action_dispatch.exception"];
  4. 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;
  5. 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 method to_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 

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!

14 responses to “My five favorite “hidden” features in Rails 3.2”

  1. Nicholas Phillips says:

    I was pleasantly surprised by the modularized association methods thanks to Josh Susser: http://blog.hasmanythrough.com/2012/1/20/modularized-association-methods-in-rails-3-2

  2. Ryan Bates says:

    Thanks for this José, great writeup. The ActiveRecord::Base#pluck method is one of my favorite additions: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck

  3. Andres Palacio says:

    I love the addition of ~/.railsrc file with default params for new apps

  4. Guest says:

    Thanks for the post (and for all your contributions BTW).

    It’s not really “hidden” but I really like ActiveRecord::Base.store http://api.rubyonrails.org/classes/ActiveRecord/Store.html. How does it differs from serialize? When do we have to choose one over another?

  5. View path in the model == :'(

  6. Paco Guzman says:

    I think don’t exist much difference between serialize and store https://github.com/rails/rails/blob/master/activerecord/lib/active_record/store.rb#L32 ::store only define some methods to access easly to the fields that compound the serialized objects. If you define the accessors as symbols ensure the serialized object can be accessed with symbols, if not define the accessors as strings 😉

  7. donaldball says:

    Doesn’t (4) fly in the face of the separation of concerns?

  8. josevalim says:

    It is all about duck typing. For brevity, I used an ActiveRecord object as an example, but all you need is an object that implements the Active Model API, so you don’t need to put that necessarily in your models.

  9. Can you explain a bit. I hope this is useful .

  10. Thanks for the great writeup. I elaborate a bit on item #4 in my blog post http://blog.obiefernandez.com/content/2012/01/rendering-collections-of-heterogeneous-objects-in-rails-32.html

  11. Ken Collins says:

    Does a model defining #cache_key which can be leveraged by view code  also break concerns? I do not think so.

  12. donaldball says:

    That’s something else altogether. #cache_key is a bona fide general purpose model method; it’s the model identifying itself a short name that’s consistent in certain conditions.

    #to_partial_path, by contrast, is strongly tied to the ActionView layer. Were it an ActiveRecord method, I’d be bothered, but Jose rightly points out that it’s ActiveModel, so could and should be implemented on presenter objects instead of AR objects.

  13. Chris Hunter says:

    Although given that the view path was derived from the mode’s class name, you could argue that it already was. I’ve had need of this feature for rendering diverse collections of objects, so I’m glad to see it implemented.

  14. Chris Bloom says:

    I wanted to say thank you, because you made me realize that I’ve been incorrectly expecting rails to magically add indexes for me. I had no reason to expect this – I know the framework well enough. But it suddenly occurred to me as I was reading tip #2 that I had an app that had been in production for months, one that I was in the process of trying to work out the apparent bottlenecks in throughput, and which had no indexes on any foreign keys. I nearly kicked myself. Hopefully these generator enhancements will save me from a good self-kicking in the future.