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 Susser wrote:> 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?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/node277.html#SECTION003216000000000000000). 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 -- Marcel Molina Jr. <marcel@vernix.org>
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 -- Posted via http://www.ruby-forum.com/.
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 -- Posted via http://www.ruby-forum.com/.
On Tue, Feb 21, 2006 at 12:14:05AM +0100, Joe wrote:> 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.The dynamic finder methods are not deprecated. marcel -- Marcel Molina Jr. <marcel@vernix.org>
On Tue, Feb 21, 2006 at 12:15:51AM +0100, Joshua Susser 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 -- Marcel Molina Jr. <marcel@vernix.org>