Learning something new every day

After staring at a rake task for twenty minutes and manually testing some cases in a console, I've got some interesting larnin's about ActiveRecord::Base.find and what it does with an options hash.

Background

There are typically two ways to provide conditions for a database search via ActiveRecord. The first is find, which looks like this most of the time: User.find(:all, :conditions => {:first_name => 'Bob', :last_name => 'Smith'}, :limit => 5). find takes two arguments: {:all, :first, :last}, and an options hash (more here).

Or taking advantage of existing relationships, which basically ORM-magic away a one-to-many foreign-key relationship between tables, e.g. some_user.files would return an array of some_user's File objects. So on and so forth.

And a third way, which I like and think cleans things up really nicely but is apparently not relevant to this bit of learning, is using named scopes (more references) - essentially, a way to wrap up any part of that options hash into what looks like a method on the model itself. An example involving a female named scope on our beloved User model:

class User < ActiveRecord::Base
  named_scope :female, :conditions => {:gender => 'female'}
end

This allows you to not only get all female Users by calling User.female, but also allows combining of named scopes and the find method to minimize repetition and narrow the scope of a query. For example: User.female.find(:all, :conditions => {:first_name => 'Christine'})

But wait!

In a script I was working on recently, I needed to loop through an array, and look something up at each step:

args = { :select => 'some_attr_id', :limit => 5 }
user_array.each do |user|
  if condition
    files = user.files.published.find(:all, args)
  else
    files = user.files.unpublished.find(:all, args)
  end
  ... &lt; do stuff with the files >
end

Interestingly enough, as args passed through the loop each time, it picked up the parameters added by the user-files relationship! Meaning, the first time through the loop with User 1, the args hash looked like this:

{
  :include=>nil, :readonly=>nil,
  :conditions=>"`files`.user_id = 1",  # ... what?
  :joins=>nil, :select=>"some_attr_id", :group=>nil,
  :offset=>nil, :limit=>5, :having=>nil, :order=>"files.created_at DESC"
}

... which makes all sorts of sense, considering a query like user.files.published would just be a search across some files table with user_id and a published conditions, but how irritating that the args hash was 1) mutable at all, and 2) modified by being passed into the ActiveRecord query!

Let me know what you think on Twitter.