:include, , , , , " />

When to :select and :include your rubies and rails

April 6, 2009
Every blog post could use a finger monkey

Every blog post could use a finger monkey

The vanilla rails ActiveRecord finders do not allow us to specify the :select clause when associations are eager loaded with the :include option. There has been ticket after ticket on the rails site the proposedĀ  patches were rejected on the grounds that an alternative, such as ActiveRecordContext (a fantasic plugin by the way) should be used instead.
From the database perspective, selecting fewer columns can giveĀ  huge performance boosts in some situations especially when the selected fields are indexed. However, often when joined tables are sparse (many base table records are pointing to the same joined table records), you might be better off running two queries: one on the base table, followed by a second on the join table with the collected foreign keys. To illustrate this, an employee has a fairly unique address while she shares her position in the company with several others.

Employee.find :all, :include => :position

would probably be less efficient than

employees = Employee.find :all

positions = Positions.find :all, :conditions => ['id in (?)', employees.collect(&:id)]

employees.each{|employee| employee.position = positions.select {|p| p.to_param == employee.posistion_id}

This is exactly what active record context does but without the messy details. In addition, the records are cached so any subsequent references to the associations hit the cache instead of rerunning a query.

employees = Employee.find :all
Positions.preload(employees)

On the other hand, provided that a reasonable number of records are queried, including the address would probably be more efficient.

Employee.find :all, :include => :address, :select => 'addresses.city, employees.*'

In this case wouldn’t it be great to use a :select clause if for instance you were only interested in for instance the city. To achieve this, I updated the eload select plugin. It does use some tricky parsing but does have the advantage of accepting aliases and allowing you to select from the base table (some alternates include all base columns).

One disadvantage of using :select is a lot of overhead is spent in rails on aliasing columns. Its really nasty in there and probably the reason that :select is not supported with :include out of the box.

If you really are having performance issues, its probably best to abandon the :include altogether and rewrite it as a :join.

employees = Employee.find :all, :select => 'addresses.city as address_city, employees.*',

:joins => 'left outer join addresses on addresses.id = employees.address_id'

employees.first['address_city']

piggyStefan Kaes has written an excellent plugin called piggy back that does this for you with belongs_to and has_one relationships. For common delegated fields, this plugin is a winner.

Employees.find :all, :piggy => 'address_city'

employees.first.address_city

So in conclusion, install eload-select plugin and you get :select and :include playing happily together. However, sometimes its not the best option out there. There is a huge open source toolbox. Use them wisely.

Leave a Reply