diff options
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-x | activerecord/lib/active_record.rb | 6 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/locking.rb | 57 |
3 files changed, 63 insertions, 2 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index ecf8499e16..963a14dd3e 100755 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -39,11 +39,13 @@ require 'active_record/reflection' require 'active_record/timestamp' require 'active_record/acts/list' require 'active_record/acts/tree' +require 'active_record/locking' ActiveRecord::Base.class_eval do include ActiveRecord::Validations - include ActiveRecord::Timestamp include ActiveRecord::Callbacks + include ActiveRecord::Locking + include ActiveRecord::Timestamp include ActiveRecord::Associations include ActiveRecord::Aggregations include ActiveRecord::Transactions @@ -55,4 +57,4 @@ end require 'active_record/connection_adapters/mysql_adapter' require 'active_record/connection_adapters/postgresql_adapter' require 'active_record/connection_adapters/sqlite_adapter' -require 'active_record/connection_adapters/sqlserver_adapter'
\ No newline at end of file +require 'active_record/connection_adapters/sqlserver_adapter' diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e4283ac157..849fb1578b 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -26,6 +26,8 @@ module ActiveRecord #:nodoc: end class PreparedStatementInvalid < ActiveRecordError #:nodoc: end + class StaleObjectError < ActiveRecordError #:nodoc: + end # Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change diff --git a/activerecord/lib/active_record/locking.rb b/activerecord/lib/active_record/locking.rb new file mode 100644 index 0000000000..e08e2fcec8 --- /dev/null +++ b/activerecord/lib/active_record/locking.rb @@ -0,0 +1,57 @@ +module ActiveRecord + module Locking + # Active Records support optimistic locking if the field <tt>lock_version</tt> 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 + # + # 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. + # + # You must ensure that your database schema defaults the lock_version column to 0. + # + # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>. + def self.append_features(base) + super + base.class_eval do + alias_method :update_without_lock, :update + alias_method :update, :update_with_lock + end + end + + def update_with_lock + if locking_enabled? + previous_value = self.lock_version + self.lock_version = previous_value + 1 + + affected_rows = connection.update( + "UPDATE #{self.class.table_name} "+ + "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " + + "WHERE #{self.class.primary_key} = #{quote(id)} AND lock_version = #{quote(previous_value)}", + "#{self.class.name} Update with optimistic locking" + ) + + raise(ActiveRecord::StaleObjectError, "Attempted to update a stale object") unless affected_rows == 1 + else + update_without_lock + end + end + end + + class Base + @@lock_optimistically = true + cattr_accessor :lock_optimistically + + def locking_enabled? + lock_optimistically && respond_to?(:lock_version) + end + end +end
\ No newline at end of file |