4.7. Editing a Record

Adding data is quite nice, but often you want to edit a record. To show how that's done I use the album database from Section 4.6, “Searching and Finding with Queries”.

Simple Editing

Simple editing of a record takes place in the following steps:
  1. Finding the record and creating a corresponding instance
  2. Changing the attribute
  3. Saving the record via the method save
We are now searching for the album The Beatles and changing its name to A Test:
$ rails console
Loading development environment (Rails 3.2.9)
>> album = Album.where(:name => 'The Beatles').first
  Album Load (0.1ms)  SELECT "albums".* FROM "albums" WHERE "albums"."name" = 'The Beatles' LIMIT 1
=> #<Album id: 10, name: "The Beatles", release_year: 1968, created_at: "2012-11-14 13:26:01", updated_at: "2012-11-14 13:26:01">
>> album.name
=> "The Beatles"
>> album.name = 'A Test'
=> "A Test"
>> album.save
   (0.1ms)  begin transaction
   (0.4ms)  UPDATE "albums" SET "name" = 'A Test', "updated_at" = '2012-11-14 18:51:46.694219' WHERE "albums"."id" = 10
   (3.9ms)  commit transaction
=> true
>> Album.last
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" ORDER BY "albums"."id" DESC LIMIT 1
=> #<Album id: 13, name: "I Got a Woman", release_year: 1955, created_at: "2012-11-14 13:39:49", updated_at: "2012-11-14 13:39:49">
>> exit
$

changed?

If you are not sure if a record has been changed and not yet saved, you can check via the method changed?:
$ rails console
Loading development environment (Rails 3.2.9)
>> album = Album.last
  Album Load (0.2ms)  SELECT "albums".* FROM "albums" ORDER BY "albums"."id" DESC LIMIT 1
=> #<Album id: 13, name: "I Got a Woman", release_year: 1955, created_at: "2012-11-14 13:39:49", updated_at: "2012-11-14 13:39:49">
>> album.changed?
=> false
>> album.name = 'The Beatles'
=> "The Beatles"
>> album.changed?
=> true
>> album.save
   (0.1ms)  begin transaction
   (0.5ms)  UPDATE "albums" SET "name" = 'The Beatles', "updated_at" = '2012-11-14 18:55:35.410521' WHERE "albums"."id" = 13
   (0.7ms)  commit transaction
=> true
>> album.changed?
=> false
>> exit
$

update_attributes

With the method update_attributes you can change several attributes of an object in one go and then immediately save them automatically.
Let's use this method within the example used in the section called “Simple Editing”:
$ rails console
Loading development environment (Rails 3.2.9)
>> album = Album.last
  Album Load (0.1ms)  SELECT "albums".* FROM "albums" ORDER BY "albums"."id" DESC LIMIT 1
=> #<Album id: 13, name: "The Beatles", release_year: 1955, created_at: "2012-11-14 13:39:49", updated_at: "2012-11-14 18:55:35">
>> album.changed?
=> false
>> album.update_attributes(:name => 'Another Test')
   (0.1ms)  begin transaction
   (0.4ms)  UPDATE "albums" SET "name" = 'Another Test', "updated_at" = '2012-11-14 18:56:52.466887' WHERE "albums"."id" = 13
   (1.7ms)  commit transaction
=> true
>> album.changed?
=> false
>> Album.last
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" ORDER BY "albums"."id" DESC LIMIT 1
=> #<Album id: 13, name: "Another Test", release_year: 1955, created_at: "2012-11-14 13:39:49", updated_at: "2012-11-14 18:56:52">
>> 
This kind of update can also be directly combined with a where method and then becomes almost atomic:
>> Album.where(:name => 'Another Test').first.update_attributes(:name => 'The Beatles')
  Album Load (0.1ms)  SELECT "albums".* FROM "albums" WHERE "albums"."name" = 'Another Test' LIMIT 1
   (0.1ms)  begin transaction
   (0.3ms)  UPDATE "albums" SET "name" = 'The Beatles', "updated_at" = '2012-11-14 18:57:59.415265' WHERE "albums"."id" = 13
   (0.7ms)  commit transaction
=> true
>> Album.last
  Album Load (0.3ms)  SELECT "albums".* FROM "albums" ORDER BY "albums"."id" DESC LIMIT 1
=> #<Album id: 13, name: "The Beatles", release_year: 1955, created_at: "2012-11-14 13:39:49", updated_at: "2012-11-14 18:57:59">
>> exit
$

Locking

By default, Rails uses optimistic locking of records. But you can turn this off.
As optimistic locking is ideal for most users and chances are you never thought about the problem before reading this paragraph, I will not dive into it in any more detail and simply point you towards the ri help which includes good examples. If you require pessimistic locking, you will find the required parameters there.
$ ri -T ActiveRecord::Locking::Optimistic
ActiveRecord::Locking::Optimistic

(from gem activerecord-3.2.9)
------------------------------------------------------------------------------
What is Optimistic Locking

Optimistic locking allows multiple users to access the same record for edits,
and assumes a minimum of conflicts with the data. It does this by checking
whether another process has made changes to a record since it was opened, an
ActiveRecord::StaleObjectError exception is thrown if that has occurred
and the update is ignored.

Check out ActiveRecord::Locking::Pessimistic for an alternative.

Usage

Active Records support optimistic locking if the field lock_version is
present. Each update to the record increments the lock_version column
and the locking facilities ensure that records instantiated twice will let the
last one saved raise a StaleObjectError if the first was also updated.
Example:

  p1 = Person.find(1)
  p2 = Person.find(1)

  p1.first_name = "Michael"
  p1.save

  p2.first_name = "should fail"
  p2.save # Raises a ActiveRecord::StaleObjectError

Optimistic locking will also check for stale data when objects are destroyed.
Example:

  p1 = Person.find(1)
  p2 = Person.find(1)

  p1.first_name = "Michael"
  p1.save

  p2.destroy # Raises a ActiveRecord::StaleObjectError

You're then responsible for dealing with the conflict by rescuing the
exception and either rolling back, merging, or otherwise apply the business
logic needed to resolve the conflict.

This locking mechanism will function inside a single Ruby process. To make it
work across all web requests, the recommended approach is to add
lock_version as a hidden field to your form.

You must ensure that your database schema defaults the lock_version
column to 0.

This behavior can be turned off by setting
ActiveRecord::Base.lock_optimistically = false. To override the name of
the lock_version column, invoke the set_locking_column method.
This method uses the same syntax as set_table_name
------------------------------------------------------------------------------
$