I’ll preface this by saying I (still) consider myself a relative
newcomer to Rails, and its likely that I’m missing something obvious in
ActiveRecords, but this has been bugging me…
I’ve written a function that I use all the time, but I get a nagging
feeling that this functionality MUST already exist, and even if it
doesn’t, there’s a better way than I’m doing it. Anyway, thus:
ActiveRecord.create_or_update_if_needed(attrs_to_match,
attrs_to_update)
which lets me do things like:
Address.create_or_update_if_needed({:street=>“1600 Pennsylvania
Avenue, NW”, :state=>“DC”}, {:zip=>“20500”})
If no records match on attrs_to_match then create one with those
attributes AND with attrs_to_update. If a record does exist but doesn’t
match the given attrs_to_update, then update only those attributes.
Otherwise, leave the record alone - no change needed.
If there’s an obvious way to do this, you can stop reading here and
simply give me the recipe!!
Still reading? Really? Okay, here’s how I’ve defined it:
class ActiveRecord::Base
def self.create_or_update_if_needed(attrs_to_match, attrs_to_update)
record = find(:first, :conditions=>attrs_to_match)
if (record.nil?)
record = create(attrs_to_match.merge(attrs_to_update))
elsif (!record.attributes_match?(attrs_to_update))
record.update_attributes(attrs_to_update)
end
record
end
def attributes_match?(attrs)
# use new() to convert attribute keys and values
mangled_attrs = self.class.new(attrs).attributes
mangled_attrs.all? {|k,v| v.nil? || self.attributes[k] == v }
end
end
Note that the “obvious” definition of attributes_match? would NOT work:
def attributes_match?(attrs)
attrs.all? {|k,v| self.attributes[k] == v}
end
… because self.attributes use strings as hash keys, not symbols, and
attribute values may be subject to type conversions. So I create a new
record, using the given attrs, and let the ActiveRecord mechanism handle
the conversions.
But by the time I’ve gone to this level of hackery, it seems prudent to
harness the wisdom of the masses: is there a better way?
- ff