Living on the edge (of Rails) #5 - better eager loading and more

Another week of edge Rails changes, featured on the Rails Envy podcast. This weeks’ report covers changes from 21 Jan 2008 to 27 Jan 2008 (the day the Rails Envy podcast was recorded).

Eager loading :includes does pre-loading

The current gem Rails behavior when loading associations with something like

Author.find(:all, :include => [:posts, :comments])

is to make a big query with multiple joins that fetches all the associations in the same query (for you RDBMS geeks, the “cartesian product”).

Frederick Cheung (fcheung on the Rails Trac) has contributed an impressive optimization to this by pre-loading the associations rather than trying to eager loading with one big query. So a find like this:

Author.find(:all, :include => [:posts, :comments])

would first load all the authors, then all the posts, and then all the comments in 3 smaller, faster queries.

The main benefit of this (depending on your data): Running X simple queries (to fetch authors, then posts and comments) rather than one mega-query that joins all the associated tables is faster most of the time. The result set is often smaller as well.

More details can be found at ticket #9640 on the Rails Trac.

Related changeset: http://dev.rubyonrails.org/changeset/8672

composed_of aggregations can now be used in finder conditions

You can now use value objects that you’ve previously specified that your model is composed_of in the finder conditions hash. E.g. if you have a Customer that has a balance aggregation:

class Customer < ActiveRecord::Base
  composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
end

You can now pass a Money value object to Customer#find:

Customer.find(:all, :conditions => { :balance => Money.new(20, "USD") })

Convenient.

Related changeset: http://dev.rubyonrails.org/changeset/8671

New helper: label_tag

A label_tag helper has been missing for quite awhile since the label helper was added. No longer:

label_tag 'name'
# => <label for="name">Name</label>

label_tag 'name', 'Your name'
# => <label for="name">Your name</label>

Related changeset: http://dev.rubyonrails.org/changeset/8685

New ActiveSupport class: ActiveSupport::TimeWithZone

A new ActiveSupport::TimeWithZone class has been introduced to make timezone support in Rails easier. Ryan Daigle has a good writeup on this.

A word of warning though: this is still work in progress, as Geoff Buesing, the core team member responsible for these timezone changes, has more to add on how his plans for the timezone puzzle in the comments.

Related changeset: http://dev.rubyonrails.org/changeset/8696

map.root can be easily aliased

map.root now accept a single symbol as an argument to declare an alias. Just a little something to keep your routes a little more DRY, e.g.:

map.new_session :controller => 'sessions', :action => 'new'
map.root :new_session

Related changeset: http://dev.rubyonrails.org/changeset/8738

As usual, let me know of any inaccuracies or any suggestions you may have in the comments!

Trying code blocks in Ruby (aka Prototype’s Try.these)

Prototype’s Try.these is a really useful bit of code (though I doubt it sees much application outside of JavaScript libraries). I have this evil bit of code somewhere, for example:

var results = Try.these(
  function() { return response.responseText.evalJSON(true); },
  function() { return eval('(' + response.responseText + ')'); }
);

Evilness personified in its evaled glory, but that’s besides the point here.

Just for fun, I tried to do this in Ruby, just to see how easy it was, and came up with this try method:

module Kernel
  def try(*these)
    raise ArgumentError, 'try requires at least 2 arguments' if these.size <= 1
    fallback = these.pop unless these.last.respond_to?(:call)
    these.each { |candidate| begin return candidate.call rescue next end }
    fallback || raise(RuntimeError, 'None of the given procs succeeded')
  end
end

Which you can (ab)use like this:

try(
  Proc.new { open('http://finance.yahoo.com/foo/') },
  lambda { open('http://finance.google.com/bar/') },
  proc { open('http://finantiq.com/flomp/') },
  :fallback
)

Real world applications would be, like the example above, retrieving content from several unreliable websites to retrieve currency rates.

You can find the specs for Kernel#try here: http://pastie.org/144339.

So the question now is, is there a better way to implement this, or a more idiomatic Ruby way?

Songza - finally an easy way to listen to boy band music

Songza’s quite nice, especially after us non-US users lost access to Pandora. Now I can finally listen to some boy band music when I feel like it (because I don’t have any in my library).

Rails and penis enhancements

Funniest bug report I’ve seen on the Rails issue tracker: http://dev.rubyonrails.org/ticket/10919. Be sure to read the comments, and the resolution:

I appreciate that penis enhancements are the norm for most of the commenters here, but their use is definitely not widespread enough to justify fixing this.

Living on the edge (of Rails) #4 - faster routes, easier form partials

It’s time again for your weekly dose of what’s new in edge Rails. This weeks’ report covers changes from 14 Jan 2008 to 20 Jan 2008 (the day the Rails Envy podcast was recorded).

Route recognition is faster

Rails’ route recognition has been optimized and is significantly faster especially for applications using many resources (i.e. via map.resources in your config/routes.rb). Big thumbs up to Oleg Andreev (aka oleganza) who also wrote a detailed post about it. You can also find out more about how this works in the code comments.

Related changesets: http://dev.rubyonrails.org/changeset/8674 and http://dev.rubyonrails.org/changeset/8652.

render :partial and forms

Instead of doing:

<% form_for(:person) do |f| %>
  <%= render :partial => 'form', :locals => { :form => f } %>
<% end %>

you can now do:

<% form_for(:person) do |f| %>
  <%= render :partial => f %>
<% end %>

Convenient. This also works with your custom form builders:

<% form_for(:person, :builder => LabellingFormBuilder) do |f| %>
  <%= render :partial => f %>
<% end %>

The partial template rendered is then people/_labelling_form and the local variable in the partial is labelling_form.

Related changeset: http://dev.rubyonrails.org/changeset/8646.

Callbacks refactored into ActiveSupport::Callbacks

All that duplicated callback code in ActiveRecord, ActionController::Dispatcher, and in test case setup and teardown methods have been extracted into the ActiveSupport::Callbacks module.

Less duplication means more robust code so yay!

Related changeset: http://dev.rubyonrails.org/changeset/8664.

ActiveRecord test suite gets a makeover

The ActiveRecord test suite has become something of a maze jungle in recent times, with no clear hierarchy of how things are organize. It’s not that easy to find what you are looking for especially for newcomers trying to contribute to ActiveRecord. Thankfully, someone (John Barnette - the foxy fixtures guy) has stepped up and cleaned things up quite a bit. Fixtures, test cases, fixture models, etc. are now in their own directories and from looking at the directory hierarchy one should be able to know where to put what at a glance.

Related ticket: http://dev.rubyonrails.org/ticket/10742.

Bugfixes

* redirect_to nil no longer explodes (but raises a friendlier ‘cannot redirect to nil’ exception) (related changeset: http://dev.rubyonrails.org/changeset/8633).