DataMapper update_or_create

October 13th, 2010

Source code: git repository at github

DataMapper has a defined method named first_or_create, very explicit name, that takes a condition and attributes as params. Its source code defined in dm-core-1.0.2/lib/dm-core/model.rb is:

def first_or_create(conditions = {}, attributes = {})
  first(conditions) || create(conditions.merge(attributes))
end

What is does is simply find the first resource by conditions, or create a new resource with the attributes if none found. This always returns an instance of the resource found or created.

It became handy to create an update_or_create method, so I created:
module DataMapper
  module Model
    def update_or_create(conditions = {}, attributes = {}, merger = true)
      (first(conditions) && first(conditions).update(attributes)) || create(merger ? (conditions.merge(attributes)) : attributes )
    end
  end # Module Model
end # Module DataMapper

There are differences to consider, like the merger param and the returned TrueClass bool upon update (and not an instance of the resource).

I found a merger param necessary because of the Serial type of properties. In the implementation of first_or_create method if we specify a Serial property as a search condition and the search has no success, then the conditions will be merged with the attributes and the Serial +1 will not be taken in account. Thus, in this implementation setting merger to false, search conditions will not be merged. Some examples:

Article.update_or_create({:id => 10}, {:name => "Spirou meet Franquin"})
If Article with ‘id’ 10 exists then it will receive a new name
=> true
If Article with ‘id’ 10 doesn’t exist, it will be created with {:id => 10, :name => “Spirou meet Franquin”}
=> #[Article @id=10 @name="Spirou meet Franquin" @date=#[Date: 4910965/2,0,2299161] @updated_on=#[Date: 4910965/2,0,2299161]]

Article.update_or_create({:id => 123}, {:name => "Fantasio meet Franquin}, false)
If Article with ‘id’ 123 doesn’t exist, it will be created with {:name => “Fantasio meet Franquin”} and a self sequential id:
=> #[Article @id=240 @name="Fantasio meet Franquin" @date=#[Date: 4910965/2,0,2299161] @updated_on=#[Date: 4910965/2,0,2299161]]

(blogged from Vim)

EDIT: new implementation to always get a DataMapper Object: