I\'m looking for a clean way to create a record with a set of attributes if the record does not exist and - if the record do exist - to update its attributes. I love the syn
I liked fl00r's answer. But why do we have to save the object every time? We can check if its already there in records or else save it
def self.find_or_create_by_id(id, &block)
obj = self.find_by_id(id)
unless obj
obj = self.create(id: id)
end
obj
end
Try this:
c = Category.find_or_initialize_by_id(category.id)
c.name = category.name
c.save!
This way you only save the instance once, instead of twice, if you called find_or_create_by_id
(assuming it's a new record).
I did this yesterday, wishing there was a way to do it in a one-liner.
Ended up going with (using your code):
c = Category.find_or_initialize_by_id(category.id)
c.name = category.name
c.save
Perhaps there is a nicer way, but this is what I used.
[Edit: use initialize instead of create to avoid hitting the DB twice)
I have coded this finders that can be used for different scenarios.
The most important thing is, that it removes the parameter :id
on create and update.
Creating a model with :id
can cause problems with MySql
or PostgreSQL
because Rails uses the auto sequence number of the database. If you create new model instances with an :id
you can get a UniqueViolation: ERROR: duplicate key value violates unique constraint
.
# config/initializers/model_finders.rb
class ActiveRecord::Base
def self.find_by_or_create(attributes, &block)
self.find_by(attributes) || self.create(attributes.except(:id), &block)
end
def self.find_by_or_create!(attributes, &block)
self.find_by(attributes) || self.create!(attributes.except(:id), &block)
end
def self.find_or_create_update_by(attributes, &block)
self.find_by(attributes).try(:update, attributes.except(:id), &block) || self.create(attributes.except(:id), &block)
end
def self.find_or_create_update_by!(attributes, &block)
self.find_by(attributes).try(:update!, attributes.except(:id), &block) || self.create!(attributes.except(:id), &block)
end
def self.find_by_or_initialize(attributes, &block)
self.find_by(attributes) || new(attributes.except(:id), &block)
end
end
I have been using this patten for seeds:
Category.find_or_initialize_by(id: category.id).update |c|
c.name = category.name
end
It works the same as Dale Wijnand's and Teoulas answers (only saves the instance once) but uses a block like in your question.
You can write your own method:
class ActiveRecord::Base
def self.find_by_id_or_create(id, &block)
obj = self.find_by_id( id ) || self.new
yield obj
obj.save
end
end
usage
Category.find_by_id_or_create(10) do |c|
c.name = "My new name"
end
Of course, in this way you should extend method missing
method and implement this method in the same way as others find_by_something
methods. But for being short this will be enough.