diff options
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG | 11 | ||||
-rw-r--r-- | activerecord/lib/active_record.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/errors.rb | 10 | ||||
-rw-r--r-- | activerecord/lib/active_record/locking/optimistic.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/store.rb | 49 | ||||
-rw-r--r-- | activerecord/test/cases/locking_test.rb | 19 | ||||
-rw-r--r-- | activerecord/test/cases/store_test.rb | 29 | ||||
-rw-r--r-- | activerecord/test/models/admin/user.rb | 3 | ||||
-rw-r--r-- | activerecord/test/schema/schema.rb | 1 |
10 files changed, 124 insertions, 5 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 50203608c2..0f6a31d679 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,16 @@ *Rails 3.2.0 (unreleased)* +* Added ActiveRecord::Base.store for declaring simple single-column key/value stores [DHH] + + class User < ActiveRecord::Base + store :settings, accessors: [ :color, :homepage ] + end + + u = User.new(color: 'black', homepage: '37signals.com') + u.color # Accessor stored attribute + u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + + * MySQL: case-insensitive uniqueness validation avoids calling LOWER when the column already uses a case-insensitive collation. Fixes #561. diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 132dc12680..3572c640eb 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -69,6 +69,7 @@ module ActiveRecord autoload :Schema autoload :SchemaDumper autoload :Serialization + autoload :Store autoload :SessionStore autoload :Timestamp autoload :Transactions diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7a9654ffd8..360e494af1 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2145,7 +2145,7 @@ MSG # AutosaveAssociation needs to be included before Transactions, because we want # #save_with_autosave_associations to be wrapped inside a transaction. include AutosaveAssociation, NestedAttributes - include Aggregations, Transactions, Reflection, Serialization + include Aggregations, Transactions, Reflection, Serialization, Store NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner) diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 96870cb338..fc80f3081e 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -99,6 +99,16 @@ module ActiveRecord # # Read more about optimistic locking in ActiveRecord::Locking module RDoc. class StaleObjectError < ActiveRecordError + attr_reader :record, :attempted_action + + def initialize(record, attempted_action) + @record = record + @attempted_action = attempted_action + end + + def message + "Attempted to #{attempted_action} a stale object: #{record.class.name}" + end end # Raised when association is being configured improperly or diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index d9ad7e4132..2df3309648 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -103,7 +103,7 @@ module ActiveRecord affected_rows = connection.update stmt unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError.new(self, "update") end affected_rows @@ -127,7 +127,7 @@ module ActiveRecord affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError.new(self, "destroy") end end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb new file mode 100644 index 0000000000..d5910df891 --- /dev/null +++ b/activerecord/lib/active_record/store.rb @@ -0,0 +1,49 @@ +module ActiveRecord + # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. + # It's like a simple key/value store backed into your record when you don't care about being able to + # query that store outside the context of a single record. + # + # You can then declare accessors to this store that are then accessible just like any other attribute + # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's + # already built around just accessing attributes on the model. + # + # Make sure that you declare the database column used for the serialized store as a text, so there's + # plenty of room. + # + # Examples: + # + # class User < ActiveRecord::Base + # store :settings, accessors: [ :color, :homepage ] + # end + # + # u = User.new(color: 'black', homepage: '37signals.com') + # u.color # Accessor stored attribute + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # Add additional accessors to an existing store through store_accessor + # class SuperUser < User + # store_accessor :settings, :privileges, :servants + # end + module Store + extend ActiveSupport::Concern + + module ClassMethods + def store(store_attribute, options = {}) + serialize store_attribute, Hash + store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors + end + + def store_accessor(store_attribute, *keys) + Array(keys).flatten.each do |key| + define_method("#{key}=") do |value| + send(store_attribute)[key] = value + end + + define_method(key) do + send(store_attribute)[key] + end + end + end + end + end +end
\ No newline at end of file diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 61baa55027..e9bd7f07b6 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -125,6 +125,24 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end + def test_lock_exception_record + p1 = Person.new(:first_name => 'mira') + assert_equal 0, p1.lock_version + + p1.first_name = 'mira2' + p1.save! + p2 = Person.find(p1.id) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.first_name = 'mira3' + p1.save! + + p2.first_name = 'sue' + error = assert_raise(ActiveRecord::StaleObjectError) { p2.save! } + assert_equal(error.record.object_id, p2.object_id) + end + def test_lock_new_with_nil p1 = Person.new(:first_name => 'anika') p1.save! @@ -141,7 +159,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 1, p1.lock_version end - def test_lock_column_name_existing t1 = LegacyThing.find(1) t2 = LegacyThing.find(1) diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb new file mode 100644 index 0000000000..fb77220875 --- /dev/null +++ b/activerecord/test/cases/store_test.rb @@ -0,0 +1,29 @@ +require 'cases/helper' +require 'models/admin' +require 'models/admin/user' + +class StoreTest < ActiveRecord::TestCase + setup do + @john = Admin::User.create(:name => 'John Doe', :color => 'black') + end + + test "reading store attributes through accessors" do + assert_equal 'black', @john.color + assert_nil @john.homepage + end + + test "writing store attributes through accessors" do + @john.color = 'red' + @john.homepage = '37signals.com' + + assert_equal 'red', @john.color + assert_equal '37signals.com', @john.homepage + end + + test "accessing attributes not exposed by accessors" do + @john.settings[:icecream] = 'graeters' + @john.save + + assert 'graeters', @john.reload.settings[:icecream] + end +end diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 74bb21551e..c12c88e195 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -1,3 +1,4 @@ class Admin::User < ActiveRecord::Base belongs_to :account -end
\ No newline at end of file + store :settings, :accessors => [ :color, :homepage ] +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 9d5ad16a3c..bb08f5c181 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -37,6 +37,7 @@ ActiveRecord::Schema.define do create_table :admin_users, :force => true do |t| t.string :name + t.text :settings t.references :account end |