# module ActiveRecord::Locking::Optimistic

## 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 Record supports optimistic locking if the `lock_version` field 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 an 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 an 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.

This behavior can be turned off by setting `ActiveRecord::Base.lock_optimistically = false`. To override the name of the `lock_version` column, set the `locking_column` class attribute:

    class Person < ActiveRecord::Base
      self.locking_column = :lock_person
    end

### Private Instance Methods

_create_record(attribute_names = self.attribute_names, *) click to toggle source
Calls superclass method

    # File lib/active_record/locking/optimistic.rb, line 70
    def _create_record(attribute_names = self.attribute_names, *)
      if locking_enabled?
        # We always want to persist the locking version, even if we don't detect
        # a change from the default, since the database might have no default
        attribute_names |= [self.class.locking_column]
      end
      super
    end

_update_record(attribute_names = self.attribute_names) click to toggle source
Calls superclass method

    # File lib/active_record/locking/optimistic.rb, line 79
    def _update_record(attribute_names = self.attribute_names)
      return super unless locking_enabled?
      return 0 if attribute_names.empty?

      begin
        lock_col = self.class.locking_column

        previous_lock_value = read_attribute_before_type_cast(lock_col)

        increment_lock

        attribute_names.push(lock_col)

        relation = self.class.unscoped

        affected_rows = relation.where(
          self.class.primary_key => id,
          lock_col => previous_lock_value
        ).update_all(
          attributes_for_update(attribute_names).map do |name|
            [name, _read_attribute(name)]
          end.to_h
        )

        unless affected_rows == 1
          raise ActiveRecord::StaleObjectError.new(self, "update")
        end

        affected_rows

      # If something went wrong, revert the locking_column value.
      rescue Exception
        send(lock_col + "=", previous_lock_value.to_i)
        raise
      end
    end

destroy_row() click to toggle source
Calls superclass method

    # File lib/active_record/locking/optimistic.rb, line 116
    def destroy_row
      affected_rows = super

      if locking_enabled? && affected_rows != 1
        raise ActiveRecord::StaleObjectError.new(self, "destroy")
      end

      affected_rows
    end

increment_lock() click to toggle source

    # File lib/active_record/locking/optimistic.rb, line 64
    def increment_lock
      lock_col = self.class.locking_column
      previous_lock_value = send(lock_col).to_i
      send(lock_col + "=", previous_lock_value + 1)
    end

relation_for_destroy() click to toggle source
Calls superclass method

    # File lib/active_record/locking/optimistic.rb, line 126
    def relation_for_destroy
      relation = super

      if locking_enabled?
        locking_column = self.class.locking_column
        relation = relation.where(locking_column => _read_attribute(locking_column))
      end

      relation
    end