# AR create\_or\_update\_if\_needed rant -- must be a better way!

**URL:** <https://www.ruby-forum.com/t/ar-create-or-update-if-needed-rant-must-be-a-better-way/187423>\
**Category:** Rails\
**Created:** [April 22, 2010, 10:25pm UTC](https://www.ruby-forum.com/t/ar-create-or-update-if-needed-rant-must-be-a-better-way/187423 "2010-04-22T22:25:04Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![fearless\_fool](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/5e9695/32.png) [@fearless\_fool](https://www.ruby-forum.com/u/fearless_fool)\
**Post date:** [April 22, 2010, 10:25pm UTC](https://www.ruby-forum.com/t/ar-create-or-update-if-needed-rant-must-be-a-better-way/187423/1 "2010-04-22T22:25:04Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![fearless\_fool](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/5e9695/32.png) [@fearless\_fool](https://www.ruby-forum.com/u/fearless_fool)\
**Post date:** [April 23, 2010, 2:34pm UTC](https://www.ruby-forum.com/t/ar-create-or-update-if-needed-rant-must-be-a-better-way/187423/2 "2010-04-23T14:34:26Z")

</div>

Fearless F. wrote:

> 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!! 🙂

Well, update\_all with the :conditions option will get you most of the  
way there. And what do you want to have happen if more than one record  
matches the conditions?

> # 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?)

Lose the parentheses – if isn’t a function.

> ```
> 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.

Then try HashWithIndifferentAccess or stringify\_keys. You’re  
overcomplicating this.

> 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?

Yes! Work _with_ the framework instead of around it, as I suggested  
above.

> - ff

## Best,

Marnen Laibow-Koser  
[http://www.marnen.org](http://www.marnen.org)  
removed\_email\_address@domain.invalid
