I find that I''m frequently writing code that could be described in english as "If there is an incumbent ActiveRecord that meets some specific criteria, then update one or more of its fields. Otherwise create a new record with the same criteria and new field values." ActiveRecord''s dynamic finder methods (find_or_create_by_xxx) are not usually expressive enough to do this. But I haven''t been able to create a general method that smells right. It''s been bugging me, so I turn to the mavens of style in this forum for suggestions. As an example, I just wrote this monstrosity: def set_xattribute(name, value) symbol_name = self.class.intern_symbol_name(name) incumbent = SymbolValue. where(:symbol_values => {:symbol_name_id => symbol_name.id}). where(:symbol_values => {:owner_id => self.id}). where(:symbol_values => {:xclass_id => self.class.xclass_id}).first if (incumbent) incumbent.update_attribute(:value, value) else SymbolValue.create(:symbol_name_id => symbol_name.id, :owner_id => self.id, :xclass_id => self.class.xclass_id, :value => value) end end Note the huge amount of repetition between the "finder" (incumbent ...) and the "creator" (SymbolValue.create(...)). I can think of a few approaches to DRYing this code (e.g. scopes), but what do the mavens of style in this forum suggest? - ff -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
I find that I''m frequently writing code that could be described in English as "If there is an incumbent ActiveRecord that meets some specific criteria, then update one or more of its fields. Otherwise create a new record with the same criteria and new field values." ActiveRecord''s dynamic finder methods (find_or_create_by_xxx) are not usually expressive enough to do this. But I haven''t been able to create a general method that smells right. It''s been bugging me, so I turn to the mavens of style in this forum for suggestions. As an example, I just wrote this monstrosity: def set_xattribute(name, v1, v2) symbol_name = self.class.intern_symbol_name(name) incumbent = SymbolValue. where(:symbol_values => {:symbol_name_id => symbol_name.id}). where(:symbol_values => {:owner_id => self.id}). where(:symbol_values => {:xclass_id => self.class.xclass_id}).first if (incumbent) incumbent.update_attributes(:v1 => v1, :v2 => v2) else SymbolValue.create(:symbol_name_id => symbol_name.id, :owner_id => self.id, :xclass_id => self.class.xclass_id, :v1 => v1, :v2 => v2) end end Note the code fragments repeated among the "finder" (incumbent = ...), the "updater" (update_attributes(...), and the "creator" (SymbolValue.create(...)). There may be a clever way to use scopes for this, but what do the mavens of style in this forum suggest? - ff -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Marnen Laibow-Koser
2010-Dec-07 19:40 UTC
Re: style question: "update or create" active record
Fearless Fool wrote in post #966959:> I find that I''m frequently writing code that could be described in > English as "If there is an incumbent ActiveRecord that meets some > specific criteria, then update one or more of its fields. Otherwise > create a new record with the same criteria and new field values." > > ActiveRecord''s dynamic finder methods (find_or_create_by_xxx) are not > usually expressive enough to do this.find_or_create_by_* will do exactly this. You can specify additional fields for the "create" part of the action. Please see the docs. If that won''t do the trick, then please explain further. Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Philip Hallstrom
2010-Dec-07 19:42 UTC
Re: style question: "update or create" active record
On Dec 7, 2010, at 11:34 AM, Fearless Fool wrote:> I find that I''m frequently writing code that could be described in > English as "If there is an incumbent ActiveRecord that meets some > specific criteria, then update one or more of its fields. Otherwise > create a new record with the same criteria and new field values." > > ActiveRecord''s dynamic finder methods (find_or_create_by_xxx) are not > usually expressive enough to do this. But I haven''t been able to create > a general method that smells right. It''s been bugging me, so I turn to > the mavens of style in this forum for suggestions. > > As an example, I just wrote this monstrosity: > > def set_xattribute(name, v1, v2) > symbol_name = self.class.intern_symbol_name(name) > incumbent = SymbolValue. > where(:symbol_values => {:symbol_name_id => symbol_name.id}). > where(:symbol_values => {:owner_id => self.id}). > where(:symbol_values => {:xclass_id => self.class.xclass_id}).first > if (incumbent) > incumbent.update_attributes(:v1 => v1, :v2 => v2) > else > SymbolValue.create(:symbol_name_id => symbol_name.id, > :owner_id => self.id, > :xclass_id => self.class.xclass_id, > :v1 => v1, > :v2 => v2) > end > end > > Note the code fragments repeated among the "finder" (incumbent = ...), > the "updater" (update_attributes(...), and the "creator" > (SymbolValue.create(...)). There may be a clever way to use scopes for > this, but what do the mavens of style in this forum suggest?You might play around with find_or_instantiator_by_attributes http://apidock.com/rails/ActiveRecord/FinderMethods/find_or_instantiator_by_attributes -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
BTW, the best I''ve come with so far is: file: ar_extensions.rb ================class ActiveRecord::Base def self.create_or_update(attrs_to_match, attrs_to_update = {}) if (incumbent = self.first(:conditions => attrs_to_match)) incumbent.update_attributes(attrs_to_update) incumbent else self.create!(attrs_to_match.merge(attrs_to_update)) end end end ================... but maybe there is something more appropriate in the new query interface or AREL models? -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Marnen Laibow-Koser wrote in post #966963:> find_or_create_by_* will do exactly this. You can specify additional > fields for the "create" part of the action. Please see the docs. > > If that won''t do the trick, then please explain further.Hi Marnen: Despite multiple re-reads of the documentation, I haven''t figured out how to get it to update specific fields. Maybe I''m just being dense. Consider this AR class (slightly different than the example I gave, but you''ll get the idea):>> SymbolValue.first=> #<SymbolValue id: 1, symbol_name_id: 2, owner_id: 1, owner_class_name_id: 1, value: "soso"> Let''s say I want to discriminate on :symbol_name_id, :owner_id and :owner_class_name_id to select the record, and I want to update the :value field. Putting aside any complaints about the length of the dynamic method name, here are two attempts that do NOT update :value:>>SymbolValue.find_or_create_by_symbol_name_id_and_owner_id_and_owner_class_name_id(2, 1, 1, :value => "new_soso") => #<SymbolValue id: 1, symbol_name_id: 2, owner_id: 1, owner_class_name_id: 1, value: "soso">>>SymbolValue.find_or_create_by_symbol_name_id_and_owner_id_and_owner_class_name_id(2, 1, 1) {|s| s.value = "new_soso"} => #<SymbolValue id: 1, symbol_name_id: 2, owner_id: 1, owner_class_name_id: 1, value: "soso"> As far as I can tell, the dynamic finder methods only set :value if a new record is created, not if an existing record is found. While that may be useful, it''s not what I was asking for in the OP. What am I missing? -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Marnen Laibow-Koser
2010-Dec-07 20:42 UTC
Re: style question: "update or create" active record
Fearless Fool wrote in post #966995:> Marnen Laibow-Koser wrote in post #966963: >> find_or_create_by_* will do exactly this. You can specify additional >> fields for the "create" part of the action. Please see the docs. >> >> If that won''t do the trick, then please explain further. > > Hi Marnen: > > Despite multiple re-reads of the documentation, I haven''t figured out > how to get it to update specific fields. Maybe I''m just being dense. > Consider this AR class (slightly different than the example I gave, but > you''ll get the idea): > >>> SymbolValue.first > => #<SymbolValue id: 1, symbol_name_id: 2, owner_id: 1, > owner_class_name_id: 1, value: "soso"> > > Let''s say I want to discriminate on :symbol_name_id, :owner_id and > :owner_class_name_id to select the record, and I want to update the > :value field. Putting aside any complaints about the length of the > dynamic method name, here are two attempts that do NOT update :value: > >>> >SymbolValue.find_or_create_by_symbol_name_id_and_owner_id_and_owner_class_name_id(2,> 1, 1, :value => "new_soso") > => #<SymbolValue id: 1, symbol_name_id: 2, owner_id: 1, > owner_class_name_id: 1, value: "soso"> > >>> >SymbolValue.find_or_create_by_symbol_name_id_and_owner_id_and_owner_class_name_id(2,> 1, 1) {|s| s.value = "new_soso"} > => #<SymbolValue id: 1, symbol_name_id: 2, owner_id: 1, > owner_class_name_id: 1, value: "soso"> > > As far as I can tell, the dynamic finder methods only set :value if a > new record is created, not if an existing record is found. While that > may be useful, it''s not what I was asking for in the OP.Oh, I see now. I misunderstood your original requirement. Yeah, find_or_create_by_* won''t do that. I think your create_or_update approach is reasonable. Just don''t call the file ar_extensions; there''s already a plugin by that name.> > What am I missing?Nothing, apparently. :) Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Well, I couldn''t rest until I figured out an implementation of create_or_update (see version 1 above) using the oh-so-nifty ActiveRecord::Relation framework. Here it is: class ActiveRecord::Base def self.create_or_update(relation, attrs_to_update) if (incumbent = relation.first).nil? relation.create!(attrs_to_update) else incumbent.update_attributes(attrs_to_update) incumbent end end end What''s nice about this is that the relation can be a more expressive selector than a simple hash -- 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-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.