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 User
s 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'})
</small>
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
... < 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.