All, Is there a safe, one-line way of finding a record that: 1) Must be present 2) Must be unique (according to the conditions) Bootstrapping data (often stored as a constant) often spurs this need, e.g. class Status < ActiveRecord::Base APPROVED = Status.find(:first, :conditions => ["name = ''approved''"]) ... end class AccountType < ActiveRecord::Base AccountType.find(:first, :conditions => ["type = ''commercial''"]) end or us_state = UsState.find(:first, :conditions => ["abbrev = ?", abbreviation]) I don''t want the burden of error-checking the results, e.g. ''x.nil?'' or ''x.size == 1'', every time I issue a query of this type. I haven''t seen any good solution so far, and I''m considering overriding ''find'' to take an '':exactly_one'' option in addition to :first and :all, raising RecordNotFoundException or TooManyRecordsException. Before I go down that road I wanted to ask the mailing list if I''m missing something. Thanks! Brian Hartin -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Brian Hartin wrote:> All, > > Is there a safe, one-line way of finding a record that: > > 1) Must be present > 2) Must be unique (according to the conditions) > > Bootstrapping data (often stored as a constant) often spurs this need, > e.g. > > class Status < ActiveRecord::Base > APPROVED = Status.find(:first, :conditions => ["name = ''approved''"]) > ... > end > > class AccountType < ActiveRecord::Base > AccountType.find(:first, :conditions => ["type = ''commercial''"]) > end > > or > > us_state = UsState.find(:first, :conditions => ["abbrev = ?", > abbreviation]) > > > I don''t want the burden of error-checking the results, e.g. ''x.nil?'' or > ''x.size == 1'', every time I issue a query of this type. > > I haven''t seen any good solution so far, and I''m considering overriding > ''find'' to take an '':exactly_one'' option in addition to :first and :all, > raising RecordNotFoundException or TooManyRecordsException. Before I go > down that road I wanted to ask the mailing list if I''m missing > something. > > Thanks! > > Brian HartinThe access you are asking for would be given by class ActiveRecord::Base def self.find_exactly_one(conditions) return nil unless self.count(:conditions => conditions) == 1 self.find(:first, :conditions => conditions) end end (or something close to this) I don''t know if you want to bother into one sql-statement. Can you not put a uniqueness constraint on the column? Stephan -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Stephan Wehner wrote:> Brian Hartin wrote: >> All, >> >> Is there a safe, one-line way of finding a record that: >> >> 1) Must be present >> 2) Must be unique (according to the conditions) >> >> Bootstrapping data (often stored as a constant) often spurs this need, >> e.g. >> >> class Status < ActiveRecord::Base >> APPROVED = Status.find(:first, :conditions => ["name = ''approved''"]) >> ... >> end >> >> class AccountType < ActiveRecord::Base >> AccountType.find(:first, :conditions => ["type = ''commercial''"]) >> end >> >> or >> >> us_state = UsState.find(:first, :conditions => ["abbrev = ?", >> abbreviation]) >> >> >> I don''t want the burden of error-checking the results, e.g. ''x.nil?'' or >> ''x.size == 1'', every time I issue a query of this type. >> >> I haven''t seen any good solution so far, and I''m considering overriding >> ''find'' to take an '':exactly_one'' option in addition to :first and :all, >> raising RecordNotFoundException or TooManyRecordsException. Before I go >> down that road I wanted to ask the mailing list if I''m missing >> something. >> >> Thanks! >> >> Brian Hartin > > The access you are asking for would be given by > > class ActiveRecord::Base > def self.find_exactly_one(conditions) > return nil unless self.count(:conditions => conditions) == 1 > self.find(:first, :conditions => conditions) > end > end > > (or something close to this) > > I don''t know if you want to bother into one sql-statement. > > Can you not put a uniqueness constraint on the column? > > StephanThere is a uniqueness constraint, but that doesn''t cover the case in which the record doesn''t exist. The behavior I''m shooting for is: 1) One and only one query is issued (your example issues two) 2) The conditions will usually reference a column(s) with a unique index, but not necessarily (some dev shops don''t use them - sigh) 3) It must raise a NoRecordFound if no record is found 4) It must raise a TooManyRecordsFound if more than one record is found 5) It must not interfere with other, expected find behavior So far, I''ve got: def self.find_exactly_one(conditions) results = self.find(:all, :conditions => conditions) raise RecordNotFound if results.size == 0 raise TooManyRecordsFound, "#{results.size} found." if results.size > 1 results[0] # Found just one end But this is only because I''m not yet ready to tackle replacing ''find'' itself. Ideally, I could do: find(:exactly_one, :conditions => ...) Thanks for the response! Brian -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Patching it into find should be easy. Something like below (which is aircoded and probably not correct) should get you started: class ActionView::Base # this method will become "find" def find_with_exactly_one(*args) options = args.extract_options! case args.first when :exactly_one then find_exactly_one(options) else find_without_exactly_one(args, options) end end # this makes "find_with_exactly_one" into "find" # and turn the original "find" method into "find_without_exactly_one" alias_method_chain :find, :exactly_one def self.find_exactly_one(conditions) results = self.find(:all, :conditions => conditions) raise RecordNotFound if results.size == 0 raise TooManyRecordsFound, "#{results.size} found." if results.size>1 results[0] # Found just one end end -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
>> Brian Hartin wrote: >>> ...but that doesn''t cover the case in > which the record doesn''t exist. The behavior I''m shooting for is: > > 1) One and only one query is issued (your example issues two) > 2) The conditions will usually reference a column(s) with a unique > index, but not necessarily (some dev shops don''t use them - sigh) > 3) It must raise a NoRecordFound if no record is found > 4) It must raise a TooManyRecordsFound if more than one record is found > 5) It must not interfere with other, expected find behavior >How something like this one: class ActiveRecord::Base def self.find_exactly_one(conditions) results = self.find(:all, :condition => conditions) raise ''None found'' if results.blank? # might also be results==[] raise ''Too many'' if results.length > 1 results.first end end Stephan -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I think that typically you use alias_method_chain when you want to inject something into the call chain (ie., before/after processing, adding callbacks) rather than simply extending the method. If you only need to extend it, you''re probably better adding an extension in lib (often lib/core_ext when extending a core object) and then including the functionality where you need it. The find_exactly_on code is good (should probably be protected), though you could simply: def self.find options = args.extract_options! return find_exactly_one if args.first==:exactly_one # add your new method when the conditions are right super # give way to default if not end On Apr 18, 8:04 pm, Nathan Esquenazi <rails-mailing-l...@andreas- s.net> wrote:> Patching it into find should be easy. Something like below (which is > aircoded and probably not correct) should get you started: > > class ActionView::Base > > # this method will become "find" > def find_with_exactly_one(*args) > options = args.extract_options! > case args.first > when :exactly_one then find_exactly_one(options) > else find_without_exactly_one(args, options) > end > end > > # this makes "find_with_exactly_one" into "find" > # and turn the original "find" method into "find_without_exactly_one" > alias_method_chain :find, :exactly_one > > def self.find_exactly_one(conditions) > results = self.find(:all, :conditions => conditions) > raise RecordNotFound if results.size == 0 > raise TooManyRecordsFound, "#{results.size} found." if results.size > > 1 > results[0] # Found just one > end > end > > -- > Posted viahttp://www.ruby-forum.com/.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
AndyV wrote:> I think that typically you use alias_method_chain when you want to > inject something into the call chain (ie., before/after processing, > adding callbacks) rather than simply extending the method. If you > only need to extend it, you''re probably better adding an extension in > lib (often lib/core_ext when extending a core object) and then > including the functionality where you need it. > > The find_exactly_on code is good (should probably be protected), > though you could simply: > > def self.find > options = args.extract_options! > return find_exactly_one if args.first==:exactly_one # add your new > method when the conditions are right > super # give way to default if not > end > > On Apr 18, 8:04 pm, Nathan Esquenazi <rails-mailing-l...@andreas-That implies that we either do this in a particular model or use a common base class for all our models, no? For a monkeypatch, I''d have to use alias_method_chain, right? I prefer the common base-class approach. I''ll give that a shot. Thanks! Brian -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Brian Hartin wrote:> AndyV wrote: >> I think that typically you use alias_method_chain when you want to >> inject something into the call chain (ie., before/after processing, >> adding callbacks) rather than simply extending the method. If you >> only need to extend it, you''re probably better adding an extension in >> lib (often lib/core_ext when extending a core object) and then >> including the functionality where you need it. >> >> The find_exactly_on code is good (should probably be protected), >> though you could simply: >> >> def self.find >> options = args.extract_options! >> return find_exactly_one if args.first==:exactly_one # add your new >> method when the conditions are right >> super # give way to default if not >> end >> >> On Apr 18, 8:04 pm, Nathan Esquenazi <rails-mailing-l...@andreas- > > That implies that we either do this in a particular model or use a > common base class for all our models, no? For a monkeypatch, I''d have > to use alias_method_chain, right? > > I prefer the common base-class approach. I''ll give that a shot. > > Thanks! > > BrianI''ve done this as a monkeypatch for now. Here''s what I have: # Add common behaviors to models module ActiveRecord class TooManyRecordsFound < ActiveRecordError end class Base class << self # Adds support for the new :exactly_one option def find_with_exactly_one(*args) options = args.last.is_a?(Hash) ? args.last : {} if args.first == :exactly_one find_exactly_one(options) else find_without_exactly_one(*args) end end alias_method_chain :find, :exactly_one protected # Raises either TooManyRecordsFound or # RecordNotFound if we don''t find exactly # one record. def find_exactly_one(options) results = find(:all, options) raise RecordNotFound if results.size == 0 raise TooManyRecordsFound, "#{results.size} found." if results.size > 1 results[0] # Found just one end end end end Thanks for all your responses, Brian Hartin -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I am not sure but I actually think Andy was right. I am not sure alias_method_chain is the best method to use here BUT it works so I am happy you at least got that worked up. Although I think Andy might have been pointing you to a better solution (i.e included module extension) -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---