Forum: Ruby on Rails find(:all) vs find_all

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Joshua S. (Guest)
on 2006-02-21 00:02
(Received via mailing list)
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
Marcel Molina Jr. (Guest)
on 2006-02-21 00:14
(Received via mailing list)
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"
(http://en.wikipedia.org/wiki/Multiple_dispatch), which is built into,
for
example, the Common Lisp Object System
(http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node...).

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"
(http://martinfowler.com/bliki/HumaneInterface.html) and a "Minimal
Interface" (http://martinfowler.com/bliki/MinimalInterface.html).

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
Joe (Guest)
on 2006-02-21 01:14
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
Joshua S. (Guest)
on 2006-02-21 01:15
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"
> (http://martinfowler.com/bliki/HumaneInterface.html) and a "Minimal
> Interface" (http://martinfowler.com/bliki/MinimalInterface.html).

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
Marcel Molina Jr. (Guest)
on 2006-02-21 01:19
(Received via mailing list)
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
Marcel Molina Jr. (Guest)
on 2006-02-21 01:28
(Received via mailing list)
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
This topic is locked and can not be replied to.