Skip to content
This repository
Browse code

Convert Hash to HashWithIndifferentAccess in ActiveRecord::Store.

In order to make migration from 3.x apps easier, we should try to
convert
Hash instances to HashWithIndifferentAccess, to allow accessing values
with both symbol and a string. This is follow up to changes in 3c0bf04.
  • Loading branch information...
commit 940c135175cba6e6611cb67a93b9370da4e81fd9 1 parent f491c6a
authored May 22, 2012
37  activerecord/lib/active_record/store.rb
... ...
@@ -1,3 +1,5 @@
  1
+require 'active_support/core_ext/hash/indifferent_access'
  2
+
1 3
 module ActiveRecord
2 4
   # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
3 5
   # It's like a simple key/value store backed into your record when you don't care about being able to
@@ -13,9 +15,6 @@ module ActiveRecord
13 15
   # You can set custom coder to encode/decode your serialized attributes to/from different formats.
14 16
   # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
15 17
   #
16  
-  # String keys should be used for direct access to virtual attributes because of most of the coders do not
17  
-  # distinguish symbols and strings as keys.
18  
-  #
19 18
   # Examples:
20 19
   #
21 20
   #   class User < ActiveRecord::Base
@@ -23,8 +22,12 @@ module ActiveRecord
23 22
   #   end
24 23
   #
25 24
   #   u = User.new(color: 'black', homepage: '37signals.com')
26  
-  #   u.color                           # Accessor stored attribute
27  
-  #   u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor
  25
+  #   u.color                          # Accessor stored attribute
  26
+  #   u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
  27
+  #
  28
+  #   # There is no difference between strings and symbols for accessing custom attributes
  29
+  #   u.settings[:country]  # => 'Denmark'
  30
+  #   u.settings['country'] # => 'Denmark'
28 31
   #
29 32
   #   # Add additional accessors to an existing store through store_accessor
30 33
   #   class SuperUser < User
@@ -35,24 +38,38 @@ module Store
35 38
 
36 39
     module ClassMethods
37 40
       def store(store_attribute, options = {})
38  
-        serialize store_attribute, options.fetch(:coder, Hash)
  41
+        serialize store_attribute, options.fetch(:coder, ActiveSupport::HashWithIndifferentAccess)
39 42
         store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
40 43
       end
41 44
 
42 45
       def store_accessor(store_attribute, *keys)
43 46
         keys.flatten.each do |key|
44 47
           define_method("#{key}=") do |value|
45  
-            send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
46  
-            send(store_attribute)[key.to_s] = value
  48
+            initialize_store_attribute(store_attribute)
  49
+            send(store_attribute)[key] = value
47 50
             send("#{store_attribute}_will_change!")
48 51
           end
49 52
 
50 53
           define_method(key) do
51  
-            send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
52  
-            send(store_attribute)[key.to_s]
  54
+            initialize_store_attribute(store_attribute)
  55
+            send(store_attribute)[key]
53 56
           end
54 57
         end
55 58
       end
56 59
     end
  60
+
  61
+    private
  62
+      def initialize_store_attribute(store_attribute)
  63
+        case attribute = send(store_attribute)
  64
+        when ActiveSupport::HashWithIndifferentAccess
  65
+          # Already initialized. Do nothing.
  66
+        when Hash
  67
+          # Initialized as a Hash. Convert to indifferent access.
  68
+          send :"#{store_attribute}=", attribute.with_indifferent_access
  69
+        else
  70
+          # Uninitialized. Set to an indifferent hash.
  71
+          send :"#{store_attribute}=", ActiveSupport::HashWithIndifferentAccess.new
  72
+        end
  73
+      end
57 74
   end
58 75
 end
34  activerecord/test/cases/store_test.rb
@@ -41,6 +41,40 @@ class StoreTest < ActiveRecord::TestCase
41 41
     assert_equal false, @john.remember_login
42 42
   end
43 43
 
  44
+  test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do
  45
+    @john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy')
  46
+    @john.height = 'low'
  47
+    assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
  48
+    assert_equal 'low', @john.json_data[:height]
  49
+    assert_equal 'low', @john.json_data['height']
  50
+    assert_equal 'heavy', @john.json_data[:weight]
  51
+    assert_equal 'heavy', @john.json_data['weight']
  52
+  end
  53
+
  54
+  test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do
  55
+    @john.json_data = { :height => 'tall', 'weight' => 'heavy' }
  56
+    assert_equal true, @john.json_data.instance_of?(Hash)
  57
+    assert_equal 'tall', @john.json_data[:height]
  58
+    assert_equal nil, @john.json_data['height']
  59
+    assert_equal nil, @john.json_data[:weight]
  60
+    assert_equal 'heavy', @john.json_data['weight']
  61
+    @john.height = 'low'
  62
+    assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
  63
+    assert_equal 'low', @john.json_data[:height]
  64
+    assert_equal 'low', @john.json_data['height']
  65
+    assert_equal 'heavy', @john.json_data[:weight]
  66
+    assert_equal 'heavy', @john.json_data['weight']
  67
+  end
  68
+
  69
+  test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do
  70
+    @john.json_data = "somedata"
  71
+    @john.height = 'low'
  72
+    assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
  73
+    assert_equal 'low', @john.json_data[:height]
  74
+    assert_equal 'low', @john.json_data['height']
  75
+    assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any?
  76
+  end
  77
+
44 78
   test "reading store attributes through accessors encoded with JSON" do
45 79
     assert_equal 'tall', @john.height
46 80
     assert_nil @john.weight

0 notes on commit 940c135

Please sign in to comment.
Something went wrong with that request. Please try again.