activerecord 4.0.13 → 4.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +745 -2700
  3. data/README.rdoc +2 -2
  4. data/examples/performance.rb +30 -18
  5. data/examples/simple.rb +4 -4
  6. data/lib/active_record.rb +2 -6
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +0 -4
  9. data/lib/active_record/associations.rb +87 -43
  10. data/lib/active_record/associations/alias_tracker.rb +1 -3
  11. data/lib/active_record/associations/association.rb +8 -16
  12. data/lib/active_record/associations/association_scope.rb +5 -16
  13. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  15. data/lib/active_record/associations/builder/association.rb +78 -54
  16. data/lib/active_record/associations/builder/belongs_to.rb +91 -58
  17. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
  19. data/lib/active_record/associations/builder/has_many.rb +2 -2
  20. data/lib/active_record/associations/builder/has_one.rb +5 -7
  21. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  22. data/lib/active_record/associations/collection_association.rb +68 -105
  23. data/lib/active_record/associations/collection_proxy.rb +12 -15
  24. data/lib/active_record/associations/has_many_association.rb +11 -9
  25. data/lib/active_record/associations/has_many_through_association.rb +16 -12
  26. data/lib/active_record/associations/has_one_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +204 -165
  28. data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
  29. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  31. data/lib/active_record/associations/join_helper.rb +2 -11
  32. data/lib/active_record/associations/preloader.rb +89 -34
  33. data/lib/active_record/associations/preloader/association.rb +43 -25
  34. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  38. data/lib/active_record/associations/singular_association.rb +6 -5
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +5 -2
  41. data/lib/active_record/attribute_methods.rb +45 -40
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +8 -22
  44. data/lib/active_record/attribute_methods/primary_key.rb +1 -7
  45. data/lib/active_record/attribute_methods/read.rb +55 -28
  46. data/lib/active_record/attribute_methods/serialization.rb +12 -33
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
  48. data/lib/active_record/attribute_methods/write.rb +37 -12
  49. data/lib/active_record/autosave_association.rb +207 -207
  50. data/lib/active_record/base.rb +5 -1
  51. data/lib/active_record/callbacks.rb +2 -2
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
  76. data/lib/active_record/connection_handling.rb +2 -2
  77. data/lib/active_record/core.rb +22 -43
  78. data/lib/active_record/counter_cache.rb +7 -7
  79. data/lib/active_record/enum.rb +100 -0
  80. data/lib/active_record/errors.rb +10 -5
  81. data/lib/active_record/fixture_set/file.rb +2 -1
  82. data/lib/active_record/fixtures.rb +171 -74
  83. data/lib/active_record/inheritance.rb +16 -22
  84. data/lib/active_record/integration.rb +52 -1
  85. data/lib/active_record/locking/optimistic.rb +7 -2
  86. data/lib/active_record/locking/pessimistic.rb +1 -1
  87. data/lib/active_record/log_subscriber.rb +5 -12
  88. data/lib/active_record/migration.rb +62 -46
  89. data/lib/active_record/migration/command_recorder.rb +7 -13
  90. data/lib/active_record/model_schema.rb +7 -14
  91. data/lib/active_record/nested_attributes.rb +10 -8
  92. data/lib/active_record/no_touching.rb +52 -0
  93. data/lib/active_record/null_relation.rb +3 -3
  94. data/lib/active_record/persistence.rb +16 -34
  95. data/lib/active_record/querying.rb +14 -12
  96. data/lib/active_record/railtie.rb +0 -50
  97. data/lib/active_record/railties/databases.rake +12 -15
  98. data/lib/active_record/readonly_attributes.rb +0 -6
  99. data/lib/active_record/reflection.rb +189 -75
  100. data/lib/active_record/relation.rb +69 -94
  101. data/lib/active_record/relation/batches.rb +57 -23
  102. data/lib/active_record/relation/calculations.rb +36 -43
  103. data/lib/active_record/relation/delegation.rb +54 -39
  104. data/lib/active_record/relation/finder_methods.rb +107 -62
  105. data/lib/active_record/relation/merger.rb +7 -20
  106. data/lib/active_record/relation/predicate_builder.rb +57 -38
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  109. data/lib/active_record/relation/query_methods.rb +110 -98
  110. data/lib/active_record/relation/spawn_methods.rb +1 -2
  111. data/lib/active_record/result.rb +45 -6
  112. data/lib/active_record/runtime_registry.rb +5 -0
  113. data/lib/active_record/sanitization.rb +6 -8
  114. data/lib/active_record/schema_dumper.rb +16 -5
  115. data/lib/active_record/schema_migration.rb +24 -25
  116. data/lib/active_record/scoping/default.rb +5 -18
  117. data/lib/active_record/scoping/named.rb +8 -29
  118. data/lib/active_record/store.rb +56 -28
  119. data/lib/active_record/tasks/database_tasks.rb +8 -4
  120. data/lib/active_record/timestamp.rb +4 -4
  121. data/lib/active_record/transactions.rb +8 -10
  122. data/lib/active_record/validations/presence.rb +1 -1
  123. data/lib/active_record/validations/uniqueness.rb +1 -6
  124. data/lib/active_record/version.rb +1 -1
  125. data/lib/rails/generators/active_record.rb +2 -8
  126. data/lib/rails/generators/active_record/migration.rb +18 -0
  127. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  128. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  129. metadata +32 -45
  130. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  131. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  132. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  133. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  134. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  135. data/lib/active_record/test_case.rb +0 -102
@@ -64,8 +64,7 @@ module ActiveRecord
64
64
  private
65
65
 
66
66
  def relation_with(values) # :nodoc:
67
- result = Relation.new(klass, table, values)
68
- result.default_scoped = default_scoped
67
+ result = Relation.create(klass, table, values)
69
68
  result.extend(*extending_values) if extending_values.any?
70
69
  result
71
70
  end
@@ -3,11 +3,36 @@ module ActiveRecord
3
3
  # This class encapsulates a Result returned from calling +exec_query+ on any
4
4
  # database connection adapter. For example:
5
5
  #
6
- # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
7
- # x # => #<ActiveRecord::Result:0xdeadbeef>
6
+ # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
7
+ # result # => #<ActiveRecord::Result:0xdeadbeef>
8
+ #
9
+ # # Get the column names of the result:
10
+ # result.columns
11
+ # # => ["id", "title", "body"]
12
+ #
13
+ # # Get the record values of the result:
14
+ # result.rows
15
+ # # => [[1, "title_1", "body_1"],
16
+ # [2, "title_2", "body_2"],
17
+ # ...
18
+ # ]
19
+ #
20
+ # # Get an array of hashes representing the result (column => value):
21
+ # result.to_hash
22
+ # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
23
+ # {"id" => 2, "title" => "title_2", "body" => "body_2"},
24
+ # ...
25
+ # ]
26
+ #
27
+ # # ActiveRecord::Result also includes Enumerable.
28
+ # result.each do |row|
29
+ # puts row['title'] + " " + row['body']
30
+ # end
8
31
  class Result
9
32
  include Enumerable
10
33
 
34
+ IDENTITY_TYPE = Class.new { def type_cast(v); v; end }.new # :nodoc:
35
+
11
36
  attr_reader :columns, :rows, :column_types
12
37
 
13
38
  def initialize(columns, rows, column_types = {})
@@ -17,8 +42,20 @@ module ActiveRecord
17
42
  @column_types = column_types
18
43
  end
19
44
 
45
+ def identity_type # :nodoc:
46
+ IDENTITY_TYPE
47
+ end
48
+
49
+ def column_type(name)
50
+ @column_types[name] || identity_type
51
+ end
52
+
20
53
  def each
21
- hash_rows.each { |row| yield row }
54
+ if block_given?
55
+ hash_rows.each { |row| yield row }
56
+ else
57
+ hash_rows.to_enum
58
+ end
22
59
  end
23
60
 
24
61
  def to_hash
@@ -46,12 +83,14 @@ module ActiveRecord
46
83
  end
47
84
 
48
85
  def initialize_copy(other)
49
- @columns = columns.dup
50
- @rows = rows.dup
51
- @hash_rows = nil
86
+ @columns = columns.dup
87
+ @rows = rows.dup
88
+ @column_types = column_types.dup
89
+ @hash_rows = nil
52
90
  end
53
91
 
54
92
  private
93
+
55
94
  def hash_rows
56
95
  @hash_rows ||=
57
96
  begin
@@ -13,5 +13,10 @@ module ActiveRecord
13
13
  extend ActiveSupport::PerThreadRegistry
14
14
 
15
15
  attr_accessor :connection_handler, :sql_runtime, :connection_id
16
+
17
+ [:connection_handler, :sql_runtime, :connection_id].each do |val|
18
+ class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
19
+ class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
20
+ end
16
21
  end
17
22
  end
@@ -3,8 +3,8 @@ module ActiveRecord
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- def quote_value(value, column = nil) #:nodoc:
7
- connection.quote(value,column)
6
+ def quote_value(value, column) #:nodoc:
7
+ connection.quote(value, column)
8
8
  end
9
9
 
10
10
  # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
@@ -86,6 +86,7 @@ module ActiveRecord
86
86
  # { address: Address.new("123 abc st.", "chicago") }
87
87
  # # => "address_street='123 abc st.' and address_city='chicago'"
88
88
  def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
89
+ attrs = PredicateBuilder.resolve_column_aliases self, attrs
89
90
  attrs = expand_hash_conditions_for_aggregates(attrs)
90
91
 
91
92
  table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
@@ -99,9 +100,8 @@ module ActiveRecord
99
100
  # { status: nil, group_id: 1 }
100
101
  # # => "status = NULL , group_id = 1"
101
102
  def sanitize_sql_hash_for_assignment(attrs, table)
102
- c = connection
103
103
  attrs.map do |attr, value|
104
- "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
104
+ "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}"
105
105
  end.join(', ')
106
106
  end
107
107
 
@@ -152,10 +152,8 @@ module ActiveRecord
152
152
  end
153
153
  end
154
154
 
155
- def quote_bound_value(value, c = connection, column = nil) #:nodoc:
156
- if column
157
- c.quote(value, column)
158
- elsif value.respond_to?(:map) && !value.acts_like?(:string)
155
+ def quote_bound_value(value, c = connection) #:nodoc:
156
+ if value.respond_to?(:map) && !value.acts_like?(:string)
159
157
  if value.respond_to?(:empty?) && value.empty?
160
158
  c.quote(nil)
161
159
  else
@@ -17,9 +17,19 @@ module ActiveRecord
17
17
  cattr_accessor :ignore_tables
18
18
  @@ignore_tables = []
19
19
 
20
- def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
21
- new(connection).dump(stream)
22
- stream
20
+ class << self
21
+ def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
22
+ new(connection, generate_options(config)).dump(stream)
23
+ stream
24
+ end
25
+
26
+ private
27
+ def generate_options(config)
28
+ {
29
+ table_name_prefix: config.table_name_prefix,
30
+ table_name_suffix: config.table_name_suffix
31
+ }
32
+ end
23
33
  end
24
34
 
25
35
  def dump(stream)
@@ -32,10 +42,11 @@ module ActiveRecord
32
42
 
33
43
  private
34
44
 
35
- def initialize(connection)
45
+ def initialize(connection, options = {})
36
46
  @connection = connection
37
47
  @types = @connection.native_database_types
38
48
  @version = Migrator::current_version rescue nil
49
+ @options = options
39
50
  end
40
51
 
41
52
  def header(stream)
@@ -202,7 +213,7 @@ HEADER
202
213
  end
203
214
 
204
215
  def remove_prefix_and_suffix(table)
205
- table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
216
+ table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
206
217
  end
207
218
  end
208
219
  end
@@ -4,41 +4,40 @@ require 'active_record/base'
4
4
 
5
5
  module ActiveRecord
6
6
  class SchemaMigration < ActiveRecord::Base
7
- def self.primary_key
8
- nil
9
- end
7
+ class << self
10
8
 
11
- def self.table_name
12
- "#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}"
13
- end
9
+ def table_name
10
+ "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
11
+ end
14
12
 
15
- def self.index_name
16
- "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
17
- end
13
+ def index_name
14
+ "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
15
+ end
18
16
 
19
- def self.create_table(limit=nil)
20
- unless connection.table_exists?(table_name)
21
- version_options = {null: false}
22
- version_options[:limit] = limit if limit
17
+ def table_exists?
18
+ connection.table_exists?(table_name)
19
+ end
23
20
 
24
- connection.create_table(table_name, id: false) do |t|
25
- t.column :version, :string, version_options
21
+ def create_table(limit=nil)
22
+ unless table_exists?
23
+ version_options = {null: false}
24
+ version_options[:limit] = limit if limit
25
+
26
+ connection.create_table(table_name, id: false) do |t|
27
+ t.column :version, :string, version_options
28
+ end
29
+ connection.add_index table_name, :version, unique: true, name: index_name
26
30
  end
27
- connection.add_index table_name, :version, unique: true, name: index_name
28
31
  end
29
- end
30
32
 
31
- def self.drop_table
32
- if connection.table_exists?(table_name)
33
- connection.remove_index table_name, name: index_name
34
- connection.drop_table(table_name)
33
+ def drop_table
34
+ if table_exists?
35
+ connection.remove_index table_name, name: index_name
36
+ connection.drop_table(table_name)
37
+ end
35
38
  end
36
39
  end
37
40
 
38
- def self.normalize_migration_number(number)
39
- "%.3d" % number.to_i
40
- end
41
-
42
41
  def version
43
42
  super.to_i
44
43
  end
@@ -8,14 +8,6 @@ module ActiveRecord
8
8
  class_attribute :default_scopes, instance_writer: false, instance_predicate: false
9
9
 
10
10
  self.default_scopes = []
11
-
12
- def self.default_scopes?
13
- ActiveSupport::Deprecation.warn(
14
- "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
15
- )
16
-
17
- !!self.default_scopes
18
- end
19
11
  end
20
12
 
21
13
  module ClassMethods
@@ -91,29 +83,24 @@ module ActiveRecord
91
83
  scope = Proc.new if block_given?
92
84
 
93
85
  if scope.is_a?(Relation) || !scope.respond_to?(:call)
94
- ActiveSupport::Deprecation.warn(
95
- "Calling #default_scope without a block is deprecated. For example instead " \
86
+ raise ArgumentError,
87
+ "Support for calling #default_scope without a block is removed. For example instead " \
96
88
  "of `default_scope where(color: 'red')`, please use " \
97
89
  "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
98
90
  "self.default_scope.)"
99
- )
100
91
  end
101
92
 
102
93
  self.default_scopes += [scope]
103
94
  end
104
95
 
105
- def build_default_scope(base_rel = relation) # :nodoc:
96
+ def build_default_scope # :nodoc:
106
97
  if !Base.is_a?(method(:default_scope).owner)
107
98
  # The user has defined their own default scope method, so call that
108
99
  evaluate_default_scope { default_scope }
109
100
  elsif default_scopes.any?
110
101
  evaluate_default_scope do
111
- default_scopes.inject(base_rel) do |default_scope, scope|
112
- if !scope.is_a?(Relation) && scope.respond_to?(:call)
113
- default_scope.merge(base_rel.scoping { scope.call })
114
- else
115
- default_scope.merge(scope)
116
- end
102
+ default_scopes.inject(relation) do |default_scope, scope|
103
+ default_scope.merge(unscoped { scope.call })
117
104
  end
118
105
  end
119
106
  end
@@ -25,22 +25,18 @@ module ActiveRecord
25
25
  if current_scope
26
26
  current_scope.clone
27
27
  else
28
- scope = relation
29
- scope.default_scoped = true
30
- scope
28
+ default_scoped
31
29
  end
32
30
  end
33
31
 
32
+ def default_scoped # :nodoc:
33
+ relation.merge(build_default_scope)
34
+ end
35
+
34
36
  # Collects attributes from scopes that should be applied when creating
35
37
  # an AR instance for the particular class this is called on.
36
38
  def scope_attributes # :nodoc:
37
- if current_scope
38
- current_scope.scope_for_create
39
- else
40
- scope = relation
41
- scope.default_scoped = true
42
- scope.scope_for_create
43
- end
39
+ all.scope_for_create
44
40
  end
45
41
 
46
42
  # Are there default attributes associated with this scope?
@@ -145,26 +141,9 @@ module ActiveRecord
145
141
  def scope(name, body, &block)
146
142
  extension = Module.new(&block) if block
147
143
 
148
- # Check body.is_a?(Relation) to prevent the relation actually being
149
- # loaded by respond_to?
150
- if body.is_a?(Relation) || !body.respond_to?(:call)
151
- ActiveSupport::Deprecation.warn(
152
- "Using #scope without passing a callable object is deprecated. For " \
153
- "example `scope :red, where(color: 'red')` should be changed to " \
154
- "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \
155
- "in the former usage and it makes the implementation more complicated " \
156
- "and buggy. (If you prefer, you can just define a class method named " \
157
- "`self.red`.)"
158
- )
159
- end
160
-
161
144
  singleton_class.send(:define_method, name) do |*args|
162
- if body.respond_to?(:call)
163
- scope = all.scoping { body.call(*args) }
164
- scope = scope.extending(extension) if extension
165
- else
166
- scope = body
167
- end
145
+ scope = all.scoping { body.call(*args) }
146
+ scope = scope.extending(extension) if extension
168
147
 
169
148
  scope || all
170
149
  end
@@ -15,6 +15,11 @@ module ActiveRecord
15
15
  # You can set custom coder to encode/decode your serialized attributes to/from different formats.
16
16
  # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
17
17
  #
18
+ # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
19
+ # the serialization provided by +store+. Simply use +store_accessor+ instead to generate
20
+ # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
21
+ # using a symbol.
22
+ #
18
23
  # Examples:
19
24
  #
20
25
  # class User < ActiveRecord::Base
@@ -61,9 +66,8 @@ module ActiveRecord
61
66
  extend ActiveSupport::Concern
62
67
 
63
68
  included do
64
- class << self
65
- attr_accessor :local_stored_attributes
66
- end
69
+ class_attribute :stored_attributes, instance_accessor: false
70
+ self.stored_attributes = {}
67
71
  end
68
72
 
69
73
  module ClassMethods
@@ -89,9 +93,9 @@ module ActiveRecord
89
93
 
90
94
  # assign new store attribute and create new hash to ensure that each class in the hierarchy
91
95
  # has its own hash of stored attributes.
92
- self.local_stored_attributes ||= {}
93
- self.local_stored_attributes[store_attribute] ||= []
94
- self.local_stored_attributes[store_attribute] |= keys
96
+ self.stored_attributes = {} if self.stored_attributes.blank?
97
+ self.stored_attributes[store_attribute] ||= []
98
+ self.stored_attributes[store_attribute] |= keys
95
99
  end
96
100
 
97
101
  def _store_accessors_module
@@ -101,38 +105,62 @@ module ActiveRecord
101
105
  mod
102
106
  end
103
107
  end
104
-
105
- def stored_attributes
106
- parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
107
- if self.local_stored_attributes
108
- parent.merge!(self.local_stored_attributes) { |k, a, b| a | b }
109
- end
110
- parent
111
- end
112
108
  end
113
109
 
114
110
  protected
115
111
  def read_store_attribute(store_attribute, key)
116
- attribute = initialize_store_attribute(store_attribute)
117
- attribute[key]
112
+ accessor = store_accessor_for(store_attribute)
113
+ accessor.read(self, store_attribute, key)
118
114
  end
119
115
 
120
116
  def write_store_attribute(store_attribute, key, value)
121
- attribute = initialize_store_attribute(store_attribute)
122
- if value != attribute[key]
123
- send :"#{store_attribute}_will_change!"
124
- attribute[key] = value
125
- end
117
+ accessor = store_accessor_for(store_attribute)
118
+ accessor.write(self, store_attribute, key, value)
126
119
  end
127
120
 
128
121
  private
129
- def initialize_store_attribute(store_attribute)
130
- attribute = send(store_attribute)
131
- unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
132
- attribute = IndifferentCoder.as_indifferent_hash(attribute)
133
- send :"#{store_attribute}=", attribute
122
+ def store_accessor_for(store_attribute)
123
+ @column_types[store_attribute.to_s].accessor
124
+ end
125
+
126
+ class HashAccessor
127
+ def self.read(object, attribute, key)
128
+ prepare(object, attribute)
129
+ object.public_send(attribute)[key]
130
+ end
131
+
132
+ def self.write(object, attribute, key, value)
133
+ prepare(object, attribute)
134
+ if value != read(object, attribute, key)
135
+ object.public_send :"#{attribute}_will_change!"
136
+ object.public_send(attribute)[key] = value
137
+ end
138
+ end
139
+
140
+ def self.prepare(object, attribute)
141
+ object.public_send :"#{attribute}=", {} unless object.send(attribute)
142
+ end
143
+ end
144
+
145
+ class StringKeyedHashAccessor < HashAccessor
146
+ def self.read(object, attribute, key)
147
+ super object, attribute, key.to_s
148
+ end
149
+
150
+ def self.write(object, attribute, key, value)
151
+ super object, attribute, key.to_s, value
152
+ end
153
+ end
154
+
155
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor
156
+ def self.prepare(object, store_attribute)
157
+ attribute = object.send(store_attribute)
158
+ unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
159
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
160
+ object.send :"#{store_attribute}=", attribute
161
+ end
162
+ attribute
134
163
  end
135
- attribute
136
164
  end
137
165
 
138
166
  class IndifferentCoder # :nodoc:
@@ -150,7 +178,7 @@ module ActiveRecord
150
178
  end
151
179
 
152
180
  def load(yaml)
153
- self.class.as_indifferent_hash @coder.load(yaml || '')
181
+ self.class.as_indifferent_hash(@coder.load(yaml))
154
182
  end
155
183
 
156
184
  def self.as_indifferent_hash(obj)