Create, Save, Update and Destroy

This page describes the basic methods to use when creating, saving, updating and destroying resources with DataMapper. Some of DataMapper’s concepts might be confusing to users coming from ActiveRecord for example. For this reason, we start with a little background on the usage of bang vs. no-bang methods in DataMapper, followed by ways of manipulating the rules DataMapper abides when it comes to raising exceptions in case some persistence operation went wrong.

Bang(!) or no bang methods

This page is about creating, saving, updating and destroying resources with DataMapper. The main methods to achieve these tasks are #create, #save, #update and #destroy. All of these methods have bang method equivalents which operate in a slightly different manner. DataMapper follows the general ruby idiom when it comes to using bang or non-bang methods. A detailed explanation of this idiom can be found at

David A. Black’s weblog

When you call a non-bang method like #save, DataMapper will invoke all callbacks defined for resources of the model. This means that it will have to load all affected resources into memory in order to be able to execute the callbacks on each one of them. This can be considered the safe version, without the bang(!). While it sometimes may not be the best way to achieve a particular goal (bad performance), it’s as safe as it gets. In fact, if dm-validations are required and active, calling the non-bang version of any of these methods, will make sure that all validations are being run too.

Sometimes though, you either don’t need the extra safety you get from dm-validations, or you don’t want any callbacks to be invoked at all. In situations like this, you can use the bang(!) versions of the respective methods. You will probably find yourself using these unsafe methods when performing internal manipulation of resources as opposed to, say, persisting attribute values entered by users (in which case you’d most likely use the safe versions). If you call #save! instead of #save, no callbacks and no validations will be run. DataMapper just assumes that you know what you do. This can also have severe impact on the performance of some operations. If you’re calling #save!, #update! or #destroy! on a (large) DataMapper::Collection, this will result in much better performance than calling the safe non-bang counterparts. This is because DataMapper won’t load the collection into memory because it won’t execute any resource level callbacks or validations.

While the above examples mostly used #save and #save! to explain the different behavior, the same rules apply for #create!, #save!, #update! and #destroy!. The safe non-bang methods will always execute all callbacks and validations, and the unsafe bang(!) methods never will.

Raising an exception when save fails

By default, datamapper returns true or false for all operations manipulating the persisted state of a resource (#create, #save, #update and #destroy).

If you want it to raise exceptions instead, you can instruct datamapper to do so either globally, on a per-model, or on a per-instance basis.

1
2
3
DataMapper::Model.raise_on_save_failure = true  # globally across all models
Zoo.raise_on_save_failure = true                # per-model
zoo.raise_on_save_failure = true                # per-instance

If DataMapper is told to raise_on_save_failure it will raise the following when any save operation failed:

DataMapper::SaveFailureError: Zoo#save returned false, Zoo was not saved

You can then go ahead and rescue from this error.

The example Zoo

To illustrate the various methods used in manipulating records, we’ll create, save, update and destroy a record.

1
2
3
4
5
6
7
8
9
class Zoo
  include DataMapper::Resource

  property :id,          Serial
  property :name,        String
  property :description, Text
  property :inception,   DateTime
  property :open,        Boolean,  :default => false
end

Create

If you want to create a new resource with some given attributes and then save it all in one go, you can use the #create method.

1
zoo = Zoo.create(:name => 'The Glue Factory', :inception => Time.now)

If the creation was successful, #create will return the newly created DataMapper::Resource. If it failed, it will return a new resource that is initialized with the given attributes and possible default values declared for that resource, but that’s not yet saved. To find out wether the creation was successful or not, you can call #saved? on the returned resource. It will return true if the resource was successfully persisted, or false otherwise.

If you want to either find the first resource matching some given criteria or just create that resource if it can’t be found, you can use #first_or_create.

1
zoo = Zoo.first_or_create(:name => 'The Glue Factory')

This will first try to find a Zoo instance with the given name, and if it fails to do so, it will return a newly created Zoo with that name.

If the criteria you want to use to query for the resource differ from the attributes you need for creating a new resource, you can pass the attributes for creating a new resource as the second parameter to #first_or_create, also in the form of a #Hash.

1
zoo = Zoo.first_or_create({ :name => 'The Glue Factory' }, { :inception => Time.now })

This will search for a Zoo named ‘The Glue Factory’ and if it can’t find one, it will return a new Zoo instance with its name set to ‘The Glue Factory’ and the inception set to what has been Time.now at the time of execution. You can see that for creating a new resource, both hash arguments will be merged so you don’t need to specify the query criteria again in the second argument Hash that lists the attributes for creating a new resource. However, if you really need to create the new resource with different values from those used to query for it, the second Hash argument will overwrite the first one.

1
2
3
4
zoo = Zoo.first_or_create({ :name => 'The Glue Factory' }, {
  :name      => 'Brooklyn Zoo',
  :inception => Time.now
})

This will search for a Zoo named ‘The Glue Factory’ but if it fails to find one, it will return a Zoo instance with its name set to ‘Brooklyn Zoo’ and its inception set to the value of Time.now at execution time.

Save

We can also create a new instance of the model, update its properties and then save it to the data store. The call to #save will return true if saving succeeds, or false in case something went wrong.

1
2
3
zoo = Zoo.new
zoo.attributes = { :name => 'The Glue Factory', :inception => Time.now }
zoo.save

In this example we’ve updated the attributes using the #attributes= method, but there are multiple ways of setting the values of a model’s properties.

1
2
3
zoo = Zoo.new(:name => 'Awesome Town Zoo')                  # Pass in a hash to the new method
zoo.name = 'Dodgy Town Zoo'                                 # Set individual property
zoo.attributes = { :name => 'No Fun Zoo', :open => false }  # Set multiple properties at once

Just like #create has an accompanying #first_or_create method, #new has its #first_or_new counterpart as well. The only difference with #first_or_new is that it returns a new unsaved resource in case it couldn’t find one for the given query criteria. Apart from that, #first_or_new behaves just like #first_or_create and accepts the same parameters. For a detailed explanation of the arguments these two methods accept, have a look at the explanation of #first_or_create in the above section on Create.

It is important to note that #save will save the complete loaded object graph when called. This means that calling #save on a resource that has relationships of any kind (established via belongs_to or has) will also save those related resources, if they are loaded at the time #save is being called. Related resources are loaded if they’ve been accessed either for read or for write purposes, prior to #save being called.

NOTE the following behavior of #save when dm-validations are in effect!

The symptom that people are seeing is that their records fail to save (i.e. #save returns false) while calling #valid? returns true. This is caused when an object has a parent or child that fails validation and thus refuses to save, thereby also blocking the object which #save was called on from saving.

Update

You can also update a model’s properties and save it with one method call. #update will return true if the record saves and false if the save fails, exactly like the #save method.

1
zoo.update(:name => 'Funky Town Municipal Zoo')

One thing to note is that the #update method refuses to update a resource in case the resource itself is #dirty? at this time.

1
2
3
zoo.name = 'Brooklyn Zoo'
zoo.update(:name => 'Funky Town Municipal Zoo')
# => DataMapper::UpdateConflictError: Zoo#update cannot be called on a dirty resource

You can also use #update to do mass updates on a model. In the previous examples we’ve used DataMapper::Resource#update to update a single resource. We can also use DataMapper::Model#update which is available as a class method on our models. Calling it will update all instances of the model with the same values.

1
Zoo.update(:name => 'Funky Town Municipal Zoo')

This will set all Zoo instances’ name property to ‘Funky Town Municipal Zoo’. Internally it does the equivalent of:

1
Zoo.all.update(:name => 'Funky Town Municipal Zoo')

This shows that actually, #update is also available on any DataMapper::Collection and performs a mass update on that collection when being called. You typically retrieve a DataMapper::Collection from either a call to SomeModel.all or a call to a relationship accessor for any 1:n or m:n relationship.

Destroy

To destroy a record, you simply call its #destroy method. It will return true or false depending if the record is successfully deleted or not. Here is an example of finding an existing record then destroying it.

1
2
zoo = Zoo.get(5)
zoo.destroy  # => true

You can also use #destroy to do mass deletes on a model. In the previous examples we’ve used DataMapper::Resource#destroy to destroy a single resource. We can also use DataMapper::Model#destroy which is available as a class method on our models. Calling it will remove all instances of that model from the repository.

1
Zoo.destroy

This will delete all Zoo instances from the repository. Internally it does the equivalent of:

1
Zoo.all.destroy

This shows that actually, #destroy is also available on any DataMapper::Collection and performs a mass delete on that collection when being called. You typically retrieve a DataMapper::Collection from either a call to SomeModel.all or a call to a relationship accessor for any 1:n or m:n relationship.

Talking to your datastore directly

Sometimes you may find that you need to execute a non-query task directly against your database. For example, performing bulk inserts might be such a situation.

The following snippet shows how to insert multiple records with only one statement on MySQL. It may not work with other databases but it should give you an idea of how to execute non-query statements against your own database of choice.

1
2
3
4
5
adapter = DataMapper.repository(:default).adapter
# Insert multiple records with one statement (MySQL)
adapter.execute("INSERT INTO zoos (id, name) VALUES (1, 'Lion'), (2, 'Elephant')")
# The interpolated array condition syntax works as well:
adapter.execute('INSERT INTO zoos (id, name) VALUES (?, ?), (?, ?)', 1, 'Lion', 2, 'Elephant')