Skip to content

Instantly share code, notes, and snippets.

@duncanbeevers
Created February 4, 2012 18:38
Show Gist options
  • Save duncanbeevers/1739387 to your computer and use it in GitHub Desktop.
Save duncanbeevers/1739387 to your computer and use it in GitHub Desktop.
Update Or Create
# UpdateOrCreate makes it simple to to perform updating or creating models
# without a lot of boilerplate.
#
# It can be used in several forms. In its simplest form, it can be used with a
# single OrderedHash.
#
# User.update_or_create(username: 'Admin', admin: true)
#
# The first key/value pair from the provided hash is used to look up any existing record.
# In this case, it will look for a user with the username 'Admin'
# If one is found, it is updated with the entire set of provided attributes.
# If none is found, a new one is created with the entire set of provided attributes.
#
# If you need more granular control of what record is looked up, or if you have imperfect
# control over the order of the provided Hash, you can provide the find_by options
# explicitly and then provide the remaining options used to update or create the instance.
#
# # User 1 is going to be the Admin
# User.update_or_create({ id: 1 }, { username: 'Admin', admin: true })
#
# Finally, if you need to provide more sophisticated handling of the instance and attributes
# used to update it you can provide a block that will be evaluated in the context of the found
# instance if one is found. The block will be passed the attributes you want to update the
# instance with. The block can then modify and return the hash of attributes you want to use to
# update the model. This is useful if you want to create models with a certain attribute but
# want to avoid re-setting in specific circumstances.
#
# File.open('spec/support/data/default_avatar.png') do |f|
# User.update_or_create(username: 'Admin', admin: true, avatar: f) do |attrs|
# if avatar.try(:url)
# attrs.except(:avatar)
# else
# attrs
# end
# end
# end
module UpdateOrCreate
def update_or_create find_by, updated_attributes = nil, &block
update_or_create_with_methods find_by, updated_attributes, block, :update_attributes, :create
end
def update_or_create! find_by, updated_attributes = nil, &block
update_or_create_with_methods find_by, updated_attributes, block, :update_attributes!, :create!
end
private
def update_or_create_with_methods find_by, updated_attributes, block, update_method, create_method
# If only a single hash of attributes was provided, extract find_by from it.
unless updated_attributes
# Promote find_by to updated_attributes
# Create find_by from first entry of updated_attributes
updated_attributes = find_by
find_by = Hash[*updated_attributes.first]
end
instance = where(find_by).first
if instance
if block
updated_attributes = block.bind(instance).call updated_attributes.dup
end
instance.send update_method, updated_attributes
else
instance = send create_method, updated_attributes.except(:id)
end
instance
end
end
ActiveRecord::Relation.send :include, UpdateOrCreate
ActiveRecord::Base.send :extend, UpdateOrCreate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment