Find(:all) vs find_all

I started with Ruby on Rails in the 0.13.x period, so I’m sure I
missed out on a lot of history. There’s probably some good
explanation for something I’ve been wondering about, but I haven’t
seen it written down anywhere. Maybe someone can clue me in.

I’ve always felt that one of the uglier APIs was the
ActiveRecord::find() method. I call that sort of API a kitchen sink
method because it does so many different things. It really should be
multiple methods. And in fact, it used to be!

Look in ActiveRecord’s deprecated_finders.rb file. There are three
methods in there - find_all, find_first, and find_on_conditions.
While I can see an argument for dropping find_on_conditions, the
other two seem quite natural, and far preferable to find(:all) and
find(:first). Seems like it would simplify things, including the
documentation for under what circumstances find() returns a single
object vs a list, when it raises an exception, etc.

Can anyone shed some light on this design decision? Any chance of
getting those deprecated APIs supported again?

thanks,
–josh

On Mon, Feb 20, 2006 at 01:59:40PM -0800, Joshua S. wrote:

documentation for under what circumstances find() returns a single
object vs a list, when it raises an exception, etc.

Can anyone shed some light on this design decision? Any chance of
getting those deprecated APIs supported again?

One of the lessons of API design that has emerged from the development
of
Rails is that when you need to do something in several slightly
different
ways, write a single method and parameterize it with an options hash
rather
than write several similar methods.

So rather than the positional parameters that you had to pass to
find_first,
find_all, we now have the single find with the symbol and option hash
that
you talk about above. Similarly, render_file, render_template,
render_inline,
render_partial, render_collection_of_partials, (etc…), turn into a
single
render method, which dispatches to the desired functionality depending
on the
options hash passed to it. This approach is, essentially, “multiple
dispatch”
(Multiple dispatch - Wikipedia), which is built into,
for
example, the Common Lisp Object System
(28.1.6. Generic Functions and Methods).

What’s good about this API?

  • Fewer methods to remember

    Which also means, fewer method signatures to remember

  • It “scales”

    You can add options thereby increasing the method’s utility without
    changing the method signature, preserving backwards compatability

  • Encourages elegant usage

    When you have only one method as your point of entry, you also only
    have
    one method signature. You can build a hash dynamically and pass it
    along to
    your method. You don’t have to contruct the name of and dynamically
    call
    the desired method while conditionally determining the appropriate
    positional parameters to pass in. Sometimes the latter case is easy
    enough,
    but when it isn’t, it gets really messy, really fast.

  • It’s friendly

    With the implementation details increasingly hidden inside the
    framework, it is
    the framework’s responsibility, rather than your’s, to figure out
    how to
    make a single method do the job of, in the case of render, 10
    methods.

These multiple dispatch style methods provide a lot of functionality
with
their option hash, yet the single point of entry with just the single
method,
stricking a balance between a “Humane Interface”
(HumaneInterface) and a “Minimal
Interface” (MinimalInterface).

That is of course not the whole picture. But it’s the start of an
answer.

The old methods have indeed been officially deprecated and have been so
for
probably about over a year now. They could disappear any day now. I
wouldn’t
imagine them becoming undeprecated.

You indicated that the find_all and find_first were more natural to you
and
you said it would simplify things to have them split up. Is the relative
“naturalness” your primary beef with the single find method? It is my
experience that from both the framework implementation side and the
client
API side, the single find method is both more natural and simpler.

marcel

Are the dynamic find_by_condition methods deprecated (or going to be)? I
kinda like them - less typing and less tedious:

Item.find_all_by_member_id_and_category_id(1, 2)

vs.

Item.find(:all, :conditions=>[‘member_id=? and category_id=?’, 1, 2])

But for either I’d probably just put behind a
Item.member_category(member_id, category_id) method anyway.

Joe

On Tue, Feb 21, 2006 at 12:14:05AM +0100, Joe wrote:

Item.member_category(member_id, category_id) method anyway.
The dynamic finder methods are not deprecated.

marcel

Hi Marcel,

Thanks for the thoughtful response.

Marcel Molina Jr. wrote:

One of the lessons of API design that has emerged from the development
of Rails is that when you need to do something in several slightly
different ways, write a single method and parameterize it with an options hash
rather than write several similar methods…

This approach is, essentially, “multiple dispatch”…

These multiple dispatch style methods provide a lot of functionality
with their option hash, yet the single point of entry with just the single
method, stricking a balance between a “Humane Interface”
(HumaneInterface) and a “Minimal
Interface” (MinimalInterface).

Keyword params and even multimethods are fine. Yes, I’d rather use a
hash to simulate keyword params than use positional params. But that’s
different from multiple displatch. A multimethod dispatches to different
method implementations depending on (usually) the class of multiple
arguments instead of just the receiver, but otherwise leaves the method
signature alone.

What’s going on in find(id) vs find(:all) vs find(:first) is quite
different from multiple dispatch. It actually changes the signature of
the method to take a different set of parameters and return a different
return type. I’d be quite happy with a find_all() method that used a
param hash instead of positional params. But find(:all) and find(:first)
do different things - one returns a single object and raises and
exception if it can’t find one, the other returns a list of objects or
an empty list if none are found.

Following that style of API design can get very messy. What should be
distinct APIs get lumped together into something approching a device
driver call with a selector code and a param block. The logical extreme
of that is to lump all methods together into one uber-method that does
everything, and that’s obviously absurd. A high-level language like Ruby
is much more expressive than that, and we should use that expressiveness
to make code easier to read and write.

You indicated that the find_all and find_first were more natural to you
and you said it would simplify things to have them split up. Is the relative
“naturalness” your primary beef with the single find method? It is my
experience that from both the framework implementation side and the
client API side, the single find method is both more natural and simpler.

As I said, using an options hash to simulate keyword params is much
nicer than positional params, so that part is fine by me. What I don’t
like is the part where the signature and behavior of the API change so
significantly. Perhaps we could resurrect find_first and find_all with
the modern options hash. Or is that too much to hope for?

–josh

On Tue, Feb 21, 2006 at 12:15:51AM +0100, Joshua S. wrote:

What’s going on in find(id) vs find(:all) vs find(:first) is quite
different from multiple dispatch. It actually changes the signature of
the method to take a different set of parameters and return a different
return type. I’d be quite happy with a find_all() method that used a
param hash instead of positional params. But find(:all) and find(:first)
do different things - one returns a single object and raises and
exception if it can’t find one, the other returns a list of objects or
an empty list if none are found.

This is not a response to your comments as a whole but if you don’t like
that
find with an id raises ActiveRecord::NoRecordFound, you could use the
dynamic
find_by_id method which wraps find(:first) and therefore returns nil
rather
than raise an exception if it fails to find a record.

marcel