From 3c0bf0435cfbc7a349faec5e1b5631e506b5dc70 Mon Sep 17 00:00:00 2001 From: Andrey Voronkov Date: Wed, 9 May 2012 19:20:14 +0400 Subject: Custom coders support for ActiveRecord::Store. JSON, YAML, Marshal can be used out of the box. --- activerecord/CHANGELOG.md | 6 ++++++ activerecord/lib/active_record/store.rb | 15 +++++++++----- activerecord/test/cases/store_test.rb | 36 ++++++++++++++++++++++++++++++++- activerecord/test/models/admin/user.rb | 2 ++ activerecord/test/schema/schema.rb | 2 ++ 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 31886c8212..befc5c9999 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,10 @@ ## Rails 4.0.0 (unreleased) ## +* Added custom coders support for ActiveRecord::Store. Now you can set + your custom coder like this: + + store :settings, accessors: [ :color, :homepage ], coder: JSON + + *Andrey Voronkov* * `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 1c7b839e5e..a67d6a2288 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -10,15 +10,20 @@ module ActiveRecord # Make sure that you declare the database column used for the serialized store as a text, so there's # plenty of room. # + # You can set custom coder to encode/decode your serialized attributes to/from different formats. + # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. + # # Examples: # # class User < ActiveRecord::Base - # store :settings, accessors: [ :color, :homepage ] + # store :settings, accessors: [ :color, :homepage ], coder: JSON # 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 + # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor + # String keys should be used for direct access to virtual attributes because of most of the coders do not + # distinguish symbols and strings as keys. # # # Add additional accessors to an existing store through store_accessor # class SuperUser < User @@ -29,7 +34,7 @@ module ActiveRecord module ClassMethods def store(store_attribute, options = {}) - serialize store_attribute, Hash + serialize store_attribute, options.fetch(:coder, Hash) store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors end @@ -37,13 +42,13 @@ module ActiveRecord keys.flatten.each do |key| define_method("#{key}=") do |value| send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key] = value + send(store_attribute)[key.to_s] = value send("#{store_attribute}_will_change!") end define_method(key) do send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key] + send(store_attribute)[key.to_s] end end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 40520d6da2..e1d0f1f799 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -4,7 +4,7 @@ require 'models/admin/user' class StoreTest < ActiveRecord::TestCase setup do - @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true) + @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true) end test "reading store attributes through accessors" do @@ -40,4 +40,38 @@ class StoreTest < ActiveRecord::TestCase @john.remember_login = false assert_equal false, @john.remember_login end + + test "reading store attributes through accessors encoded with JSON" do + assert_equal 'tall', @john.height + assert_nil @john.weight + end + + test "writing store attributes through accessors encoded with JSON" do + @john.height = 'short' + @john.weight = 'heavy' + + assert_equal 'short', @john.height + assert_equal 'heavy', @john.weight + end + + test "accessing attributes not exposed by accessors encoded with JSON" do + @john.json_data['somestuff'] = 'somecoolstuff' + @john.save + + assert_equal 'somecoolstuff', @john.reload.json_data['somestuff'] + end + + test "updating the store will mark it as changed encoded with JSON" do + @john.height = 'short' + assert @john.json_data_changed? + end + + test "object initialization with not nullable column encoded with JSON" do + assert_equal true, @john.is_a_good_guy + end + + test "writing with not nullable column encoded with JSON" do + @john.is_a_good_guy = false + assert_equal false, @john.is_a_good_guy + end end diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index d0e628bd50..ad30039304 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -2,4 +2,6 @@ class Admin::User < ActiveRecord::Base belongs_to :account store :settings, :accessors => [ :color, :homepage ] store :preferences, :accessors => [ :remember_login ] + store :json_data, :accessors => [ :height, :weight ], :coder => JSON + store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index cef08cd99c..464fdba555 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -41,6 +41,8 @@ ActiveRecord::Schema.define do # MySQL does not allow default values for blobs. Fake it out with a # big varchar below. t.string :preferences, :null => false, :default => '', :limit => 1024 + t.text :json_data, :null => true + t.text :json_data_empty, :null => false, :default => "" t.references :account end -- cgit v1.2.3