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.