aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md29
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb16
-rw-r--r--activerecord/lib/active_record/core.rb45
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb22
-rw-r--r--activerecord/lib/active_record/railtie.rb3
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb10
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb8
-rw-r--r--activerecord/test/cases/base_test.rb31
-rw-r--r--activerecord/test/cases/locking_test.rb20
10 files changed, 147 insertions, 40 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index f7de341fbe..a458f9e6e4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -72,6 +72,33 @@
## Rails 3.2.0 (unreleased) ##
+* Added a `with_lock` method to ActiveRecord objects, which starts
+ a transaction, locks the object (pessimistically) and yields to the block.
+ The method takes one (optional) parameter and passes it to `lock!`.
+
+ Before:
+
+ class Order < ActiveRecord::Base
+ def cancel!
+ transaction do
+ lock!
+ # ... cancelling logic
+ end
+ end
+ end
+
+ After:
+
+ class Order < ActiveRecord::Base
+ def cancel!
+ with_lock do
+ # ... cancelling logic
+ end
+ end
+ end
+
+ *Olek Janiszewski*
+
* 'on' and 'ON' boolean columns values are type casted to true
*Santiago Pastorino*
@@ -82,7 +109,7 @@
Example:
rake db:migrate SCOPE=blog
- *Piotr Sarnacki*
+ *Piotr Sarnacki*
* Migrations copied from engines are now scoped with engine's name,
for example 01_create_posts.blog.rb. *Piotr Sarnacki*
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index f8815fefc9..a1e34a3aa1 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/enumerable'
require 'active_support/deprecation'
-require 'thread'
module ActiveRecord
# = Active Record Attribute Methods
@@ -38,8 +37,6 @@ module ActiveRecord
def define_attribute_methods
# Use a mutex; we don't want two thread simaltaneously trying to define
# attribute methods.
- @attribute_methods_mutex ||= Mutex.new
-
@attribute_methods_mutex.synchronize do
return if attribute_methods_generated?
superclass.define_attribute_methods unless self == base_class
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e6ddf8bf8f..dbfb375ba8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -171,15 +171,15 @@ module ActiveRecord
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ return default unless default
+
case default
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- when NilClass
- nil
# Numeric types
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
$1
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 22574c4ce7..adba3f710c 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'thread'
module ActiveRecord
module Core
@@ -44,10 +45,10 @@ module ActiveRecord
##
# :singleton-method:
- # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
- # dates and times from the database. This is set to :local by default.
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
config_attribute :default_timezone, :global => true
- self.default_timezone = :local
+ self.default_timezone = :utc
##
# :singleton-method:
@@ -80,6 +81,8 @@ module ActiveRecord
end
def initialize_generated_modules
+ @attribute_methods_mutex = Mutex.new
+
# force attribute methods to be higher in inheritance hierarchy than other generated methods
generated_attribute_methods
generated_feature_methods
@@ -152,16 +155,8 @@ module ActiveRecord
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
@attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
- @association_cache = {}
- @aggregation_cache = {}
- @attributes_cache = {}
- @new_record = true
- @readonly = false
- @destroyed = false
- @marked_for_destruction = false
- @previously_changed = {}
- @changed_attributes = {}
- @relation = nil
+
+ init_internals
ensure_proper_type
@@ -185,13 +180,11 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
@attributes = self.class.initialize_attributes(coder['attributes'])
- @relation = nil
- @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
- @association_cache = {}
- @aggregation_cache = {}
- @readonly = @destroyed = @marked_for_destruction = false
+ init_internals
+
@new_record = false
+
run_callbacks :find
run_callbacks :initialize
@@ -219,7 +212,8 @@ module ActiveRecord
@aggregation_cache = {}
@association_cache = {}
- @attributes_cache = {}
+ @attributes_cache = {}
+
@new_record = true
ensure_proper_type
@@ -330,5 +324,18 @@ module ActiveRecord
def to_ary # :nodoc:
nil
end
+
+ def init_internals
+ @relation = nil
+ @aggregation_cache = {}
+ @association_cache = {}
+ @attributes_cache = {}
+ @previously_changed = {}
+ @changed_attributes = {}
+ @readonly = false
+ @destroyed = false
+ @marked_for_destruction = false
+ @new_record = true
+ end
end
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 66994e4797..58af92f0b1 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -38,6 +38,18 @@ module ActiveRecord
# account2.save!
# end
#
+ # You can start a transaction and acquire the lock in one go by calling
+ # <tt>with_lock</tt> with a block. The block is called from within
+ # a transaction, the object is already locked. Example:
+ #
+ # account = Account.first
+ # account.with_lock do
+ # # This block is called within a transaction,
+ # # account is already locked.
+ # account.balance -= 100
+ # account.save!
+ # end
+ #
# Database-specific information on row locking:
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
@@ -50,6 +62,16 @@ module ActiveRecord
reload(:lock => lock) if persisted?
self
end
+
+ # Wraps the passed block in a transaction, locking the object
+ # before yielding. You pass can the SQL locking clause
+ # as argument (see <tt>lock!</tt>).
+ def with_lock(lock = true)
+ transaction do
+ lock!(lock)
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index b1c8ae5b77..058dd58efb 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -38,7 +38,8 @@ module ActiveRecord
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDERR)
+ console = ActiveSupport::Logger.new(STDERR)
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
end
initializer "active_record.initialize_timezone" do
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index b5f202ef6a..a8ae7208fc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -75,16 +75,16 @@ module ActiveRecord
# array, it actually returns a relation object and can have other query
# methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
#
- # This method will also take multiple parameters:
+ # The argument to the method can also be an array of fields.
#
- # >> Model.select(:field, :other_field, :and_one_more)
+ # >> Model.select([:field, :other_field, :and_one_more])
# => [#<Model field: "value", other_field: "value", and_one_more: "value">]
#
- # Any attributes that do not have fields retrieved by a select
- # will return `nil` when the getter method for that attribute is used:
+ # Accessing attributes of an object that do not have fields retrieved by a select
+ # will throw <tt>ActiveModel::MissingAttributeError</tt>:
#
# >> Model.select(:field).first.other_field
- # => nil
+ # => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(value = Proc.new)
if block_given?
to_a.select {|*block_args| value.call(*block_args) }
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 0df9ffc0c5..764305459d 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'active_support/core_ext/object/inclusion'
+require 'thread'
module ActiveRecord
module AttributeMethods
@@ -20,6 +21,13 @@ module ActiveRecord
include ActiveRecord::AttributeMethods
+ def self.define_attribute_methods
+ # Created in the inherited/included hook for "proper" ARs
+ @attribute_methods_mutex ||= Mutex.new
+
+ super
+ end
+
def self.column_names
%w{ one two three }
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index f5c139e85f..87482e4181 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -187,6 +187,31 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_previously_changed
+ topic = Topic.find :first
+ topic.title = '<3<3<3'
+ assert_equal({}, topic.previous_changes)
+
+ topic.save!
+ expected = ["The First Topic", "<3<3<3"]
+ assert_equal(expected, topic.previous_changes['title'])
+ end
+
+ def test_previously_changed_dup
+ topic = Topic.find :first
+ topic.title = '<3<3<3'
+ topic.save!
+
+ t2 = topic.dup
+
+ assert_equal(topic.previous_changes, t2.previous_changes)
+
+ topic.title = "lolwut"
+ topic.save!
+
+ assert_not_equal(topic.previous_changes, t2.previous_changes)
+ end
+
def test_preserving_time_objects
assert_kind_of(
Time, Topic.find(1).bonus_time,
@@ -664,7 +689,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
end
def test_multiparameter_attributes_on_time_with_no_date
@@ -913,7 +938,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
end
def test_boolean
@@ -1868,7 +1893,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [], NonExistentTable.attribute_names
end
- def test_attribtue_names_on_abstract_class
+ def test_attribute_names_on_abstract_class
assert_equal [], AbstractCompany.attribute_names
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 0c458d5318..807274ca67 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -388,6 +388,26 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
end
end
+ def test_with_lock_commits_transaction
+ person = Person.find 1
+ person.with_lock do
+ person.first_name = 'fooman'
+ person.save!
+ end
+ assert_equal 'fooman', person.reload.first_name
+ end
+
+ def test_with_lock_rolls_back_transaction
+ person = Person.find 1
+ old = person.first_name
+ person.with_lock do
+ person.first_name = 'fooman'
+ person.save!
+ raise 'oops'
+ end rescue nil
+ assert_equal old, person.reload.first_name
+ end
+
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_no_locks_no_wait
first, second = duel { Person.find 1 }