The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

ActiveRecord::Base#find shortcut

4 April 2007 — 1-minute read

I use ActiveRecord::Base#find a lot in the Rails console. A lot. As a result, I’ve started doing the following:

1
2
3
class <<ActiveRecord::Base
  alias_method :[], :find
end

That little snippet saves me up to five entire keystrokes, every time I need to do a find!

1
2
3
prs = Person[5]
prs = Person[:first]
prs = Person[:all, :conditions => { :name => "Jamis" }]

And, thanks to the existing hash and array semantics, it loses none of find’s readability for most cases. Good stuff!

Reader Comments

Wow, this is like a bucket of cold water. Except.. good cold water. I’m sure a lot of people are thinking “why didn’t I think of this?” right about now.

Are there any downsides to this? I guess I wonder why it isn’t just in core.

I’ve been doing it for ages and I think it should be in the core.

Elegant and intuitive, I like it.

That’s really cool! Thanks for the tip.

One thing to be aware of, this doesn’t work when doing finds through associations:

@category.products[:all, ...] # won’t work

However, calling “find” on the association will still work.

I added a similar method directly to my User class so I could quickly look up users by id, email address or subdomain. Never thought to add it to AR directly.

Just last night, I was doing some work in the console, and I thought to myself, “I’m tired of typing User.find(..., I bet there is a way to make a shortcut, I’ll look into that later.”

Funny how you read my mind! Or maybe I read yours? Hmm…

How about:

Person5 Person.first Person.all(:name => ‘Jamis’)

Tasty! :)

Can this go in .irbrc?

John, sure, just make sure there’s a guard around it so it only applies when running from script/console.

I put mine in config/environment.rb, generally. That way I can use it in my code, as well as in the console.

That’s one of those Post-It note ideas. Ten seconds after the first time you see it, you’re wondering how you ever managed to survive without it.

Sorry for two comments in a row, I’ve just been thinking about this. I think this absolutely belongs in Rails core. Here’s why.

A lot of what Rails does turns SQL into memory management. Just like you don’t need to keep track of pointers and address space any more with garbage-collected languages, you don’t need to know SQL to write trivial Rails apps. Obviously writing a complex Rails app very often can require SQL, but SQL to some extent disappears when using Rails.

find is a method name which refers to the implementation of the method. #[] is the default collection access method name, and it’s actually much more appropriate, because it masks the implementation and refers only to the purpose of the method. #[] tells you what the method does; #find tells you how the method does it. I think #[] should be the encouraged idiom, and #find relegated to history.

Thanks, Giles. Here’s one counter argument for why it shouldn’t be in core.

It can’t be employed without surprises. Consider: Person[5] works great, but account.people[5] doesn’t (it just returns the 5th person in the list, rather than the person with id 5). Whereas Person.find(5) and account.people.find(5) both work as expected. That disconnect cannot be resolved without breaking the array semantics of plural associations.

That’s one argument, anyway. I’m actually kind of sitting the fence on this one. It’s certainly useful; perhaps someone could throw together a (trivial) plugin for it?

This isn’t a very strong argument, but there is the fact that Person5 is a class method and account.people5 is an instance method. So it shouldn’t be expected to do exactly the same thing anyway.

Sorry about the bold text, btw. I was trying to do the hash-mark thing for instance methods.

Just last night I was yearning for this!

Sorry for the lapse to newbiedom, but where would you put this shortcut? In its own file? Included in every model?

Luke, config/environment.rb is a good place for snippets like this.

I think Og uses this as its default find method and I thought it was nice… But I had no idea you could do what you’ve done with [:first, :conditions => ...]... That is unreal!

Wicked cool Jamis. I’ve been annoyed by all the typing in script/console as well, but never stopped to think there could be a better way. I actually vote for pluginizing it and keeping it out of core. I think it will just complicate future feature changes, where handling this needs to always be a consideration. Nice work as always.

Hey Jamis. Why did you use “alias_method” instead of “alias”?

Same as Roman, I always wonder exactly what is the different between alias_method and alias.

As far as I understand ‘alias’ is not a method… But apart from that I don’t know why one would use one instead of the other.

Mostly, I prefer alias_method because I like its syntax better than that of alias. Otherwise, alias and alias_method function identically; if you look at the implementation of alias_method, you’ll see it calls the same function internally as alias does. Because alias is an actual keyword and not a method call, it is probably slight faster, if that matters to you. Beyond that, I think it’s largely a matter of taste.

Oh, and note that alias_method returns the module/class that it was called for, while alias returns nil, so you could potentially chain alias_method with other method calls.

It is funny that you bring this up, Jamis. The other day as I was typing find I thought to myself, “wait, can I just use brackets here?”

The conclusion I came to was that there would be inconsistent semantics and also less uniformity Enumerable.find … which I believe is the part of Ruby that ActiveRecord::Base.find was modeled after.

In some ways it seems that AR find is moving more in the direction of taking a block as its argument… does anyone know what is coming down the pipe in the next version of Rails?

would using [] make AR slower ?

i.e. it’s an extra lookup ?

weepy, it won’t make any significant difference. The speed of the find itself is the bottleneck, not the extra method lookup.

If you are doing tons and tons of finds in a tight loop, you could try benchmarking it to see what the difference is, but I’ll be surprised if it makes much of a blip at all.

Jamis: WRT Giles’ suggestion: There’s no need to break the semantics of #[]. If we’re duck-typing anyways. There’s no reason that a has_many needs to return an Array. It’s almost against the Ruby way. :-)

That’s what I do in the DataMapper anyways. Return a HasManyAssociation instance that’s enumerable. So account.people5 isn’t a true Array indexer, but aside from the performance hit of iterating through the loader to find the existing object with that key, there’s no database call or anything, so it’s still comparatively inexpensive since it would only be used at a high level (the application level).

weepy: Not at the application level. The most expensive loops are going to be in the object instantiation mechanisms. The methods relating to query-generation make up a small fraction of the cost in comparison. So I’d avoid it in framework internals, but at the level most people will be working at the expense is not worth thinking twice about.