activerecord 4.2.0 → 4.2.11

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -1
  3. data/lib/active_record.rb +3 -0
  4. data/lib/active_record/aggregations.rb +6 -3
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations.rb +5 -4
  7. data/lib/active_record/associations/association.rb +15 -3
  8. data/lib/active_record/associations/association_scope.rb +1 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +13 -5
  10. data/lib/active_record/associations/builder/association.rb +1 -1
  11. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -4
  13. data/lib/active_record/associations/collection_association.rb +35 -15
  14. data/lib/active_record/associations/collection_proxy.rb +15 -9
  15. data/lib/active_record/associations/foreign_association.rb +11 -0
  16. data/lib/active_record/associations/has_many_association.rb +30 -15
  17. data/lib/active_record/associations/has_many_through_association.rb +11 -2
  18. data/lib/active_record/associations/has_one_association.rb +1 -0
  19. data/lib/active_record/associations/join_dependency.rb +8 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +7 -1
  21. data/lib/active_record/associations/preloader.rb +4 -4
  22. data/lib/active_record/associations/preloader/association.rb +5 -1
  23. data/lib/active_record/associations/singular_association.rb +2 -8
  24. data/lib/active_record/associations/through_association.rb +11 -6
  25. data/lib/active_record/attribute.rb +15 -1
  26. data/lib/active_record/attribute_assignment.rb +2 -2
  27. data/lib/active_record/attribute_methods.rb +4 -8
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +5 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +14 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +5 -1
  31. data/lib/active_record/attribute_methods/write.rb +1 -1
  32. data/lib/active_record/attribute_set.rb +4 -0
  33. data/lib/active_record/attribute_set/builder.rb +32 -12
  34. data/lib/active_record/attributes.rb +8 -0
  35. data/lib/active_record/autosave_association.rb +24 -9
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +12 -6
  39. data/lib/active_record/connection_adapters/abstract/database_statements.rb +23 -3
  40. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -16
  44. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +87 -24
  45. data/lib/active_record/connection_adapters/abstract/transaction.rb +2 -6
  46. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -7
  47. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -10
  48. data/lib/active_record/connection_adapters/column.rb +2 -2
  49. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -21
  50. data/lib/active_record/connection_adapters/mysql_adapter.rb +10 -3
  51. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -1
  53. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -0
  54. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -0
  55. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  56. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -0
  58. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -3
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +21 -13
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -12
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -28
  63. data/lib/active_record/connection_handling.rb +1 -1
  64. data/lib/active_record/core.rb +28 -15
  65. data/lib/active_record/counter_cache.rb +1 -1
  66. data/lib/active_record/enum.rb +2 -3
  67. data/lib/active_record/errors.rb +6 -5
  68. data/lib/active_record/explain_subscriber.rb +1 -1
  69. data/lib/active_record/fixtures.rb +9 -7
  70. data/lib/active_record/gem_version.rb +1 -1
  71. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  72. data/lib/active_record/locking/optimistic.rb +16 -14
  73. data/lib/active_record/migration.rb +38 -10
  74. data/lib/active_record/model_schema.rb +4 -2
  75. data/lib/active_record/nested_attributes.rb +13 -3
  76. data/lib/active_record/no_touching.rb +1 -1
  77. data/lib/active_record/persistence.rb +7 -4
  78. data/lib/active_record/railtie.rb +5 -3
  79. data/lib/active_record/railties/databases.rake +17 -24
  80. data/lib/active_record/reflection.rb +40 -28
  81. data/lib/active_record/relation.rb +3 -2
  82. data/lib/active_record/relation/calculations.rb +10 -3
  83. data/lib/active_record/relation/delegation.rb +1 -1
  84. data/lib/active_record/relation/finder_methods.rb +4 -16
  85. data/lib/active_record/relation/merger.rb +24 -1
  86. data/lib/active_record/relation/predicate_builder.rb +32 -3
  87. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -2
  88. data/lib/active_record/relation/query_methods.rb +29 -27
  89. data/lib/active_record/relation/spawn_methods.rb +7 -3
  90. data/lib/active_record/schema_dumper.rb +1 -1
  91. data/lib/active_record/schema_migration.rb +1 -4
  92. data/lib/active_record/scoping/default.rb +1 -0
  93. data/lib/active_record/tasks/database_tasks.rb +5 -2
  94. data/lib/active_record/tasks/mysql_database_tasks.rb +30 -16
  95. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -8
  96. data/lib/active_record/transactions.rb +21 -11
  97. data/lib/active_record/type/boolean.rb +1 -0
  98. data/lib/active_record/type/date.rb +4 -0
  99. data/lib/active_record/type/date_time.rb +14 -3
  100. data/lib/active_record/type/decimal.rb +27 -3
  101. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  102. data/lib/active_record/type/integer.rb +9 -5
  103. data/lib/active_record/type/numeric.rb +1 -1
  104. data/lib/active_record/type/serialized.rb +7 -1
  105. data/lib/active_record/type/string.rb +4 -0
  106. data/lib/active_record/type/value.rb +9 -0
  107. data/lib/active_record/validations/uniqueness.rb +16 -6
  108. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -3
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -6
  110. metadata +9 -7
@@ -94,6 +94,11 @@ module ActiveRecord
94
94
 
95
95
  through_record = through_association.build(*options_for_through_record)
96
96
  through_record.send("#{source_reflection.name}=", record)
97
+
98
+ if options[:source_type]
99
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
100
+ end
101
+
97
102
  through_record
98
103
  end
99
104
  end
@@ -188,9 +193,9 @@ module ActiveRecord
188
193
 
189
194
  if through_reflection.collection? && update_through_counter?(method)
190
195
  update_counter(-count, through_reflection)
196
+ else
197
+ update_counter(-count)
191
198
  end
192
-
193
- update_counter(-count)
194
199
  end
195
200
 
196
201
  def through_records_for(record)
@@ -229,6 +234,10 @@ module ActiveRecord
229
234
  false
230
235
  end
231
236
 
237
+ def has_cached_counter?(reflection = reflection())
238
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
239
+ end
240
+
232
241
  def through_reflection_updates_counter_cache?
233
242
  counter_name = cached_counter_attribute_name
234
243
  inverse_updates_counter_named?(counter_name, through_reflection)
@@ -2,6 +2,7 @@ module ActiveRecord
2
2
  # = Active Record Belongs To Has One Association
3
3
  module Associations
4
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
5
6
 
6
7
  def handle_dependency
7
8
  case options[:dependent]
@@ -151,7 +151,8 @@ module ActiveRecord
151
151
 
152
152
  message_bus.instrument('instantiation.active_record', payload) do
153
153
  result_set.each { |row_hash|
154
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
154
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
155
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
155
156
  construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
156
157
  }
157
158
  end
@@ -233,6 +234,7 @@ module ActiveRecord
233
234
  end
234
235
 
235
236
  def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
237
+ return if ar_parent.nil?
236
238
  primary_id = ar_parent.id
237
239
 
238
240
  parent.children.each do |node|
@@ -249,7 +251,11 @@ module ActiveRecord
249
251
 
250
252
  key = aliases.column_alias(node, node.primary_key)
251
253
  id = row[key]
252
- next if id.nil?
254
+ if id.nil?
255
+ nil_association = ar_parent.association(node.reflection.name)
256
+ nil_association.loaded!
257
+ next
258
+ end
253
259
 
254
260
  model = seen[parent.base_klass][primary_id][node.base_klass][id]
255
261
 
@@ -52,7 +52,13 @@ module ActiveRecord
52
52
  end
53
53
  scope_chain_index += 1
54
54
 
55
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
55
+ klass_scope =
56
+ if klass.current_scope && klass.current_scope.values.blank?
57
+ klass.unscoped
58
+ else
59
+ klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))
60
+ end
61
+ scope_chain_items.concat [klass_scope].compact
56
62
 
57
63
  rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
58
64
  left.merge right
@@ -10,13 +10,13 @@ module ActiveRecord
10
10
  # end
11
11
  #
12
12
  # class Book < ActiveRecord::Base
13
- # # columns: title, sales
13
+ # # columns: title, sales, author_id
14
14
  # end
15
15
  #
16
16
  # When you load an author with all associated books Active Record will make
17
17
  # multiple queries like this:
18
18
  #
19
- # Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
19
+ # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
20
20
  #
21
21
  # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
22
22
  # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
@@ -160,7 +160,7 @@ module ActiveRecord
160
160
  h
161
161
  end
162
162
 
163
- class AlreadyLoaded
163
+ class AlreadyLoaded # :nodoc:
164
164
  attr_reader :owners, :reflection
165
165
 
166
166
  def initialize(klass, owners, reflection, preload_scope)
@@ -175,7 +175,7 @@ module ActiveRecord
175
175
  end
176
176
  end
177
177
 
178
- class NullPreloader
178
+ class NullPreloader # :nodoc:
179
179
  def self.new(klass, owners, reflection, preload_scope); self; end
180
180
  def self.run(preloader); end
181
181
  def self.preloaded_records; []; end
@@ -145,6 +145,10 @@ module ActiveRecord
145
145
  scope.joins! preload_values[:joins] || values[:joins]
146
146
  scope.order! preload_values[:order] || values[:order]
147
147
 
148
+ if preload_values[:reordering] || values[:reordering]
149
+ scope.reordering_value = true
150
+ end
151
+
148
152
  if preload_values[:readonly] || values[:readonly]
149
153
  scope.readonly!
150
154
  end
@@ -153,7 +157,7 @@ module ActiveRecord
153
157
  scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
154
158
  end
155
159
 
156
- scope.unscope_values = Array(values[:unscope])
160
+ scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
157
161
  klass.default_scoped.merge(scope)
158
162
  end
159
163
  end
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  class SingularAssociation < Association #:nodoc:
4
4
  # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
5
5
  def reader(force_reload = false)
6
- if force_reload
6
+ if force_reload && klass
7
7
  klass.uncached { reload }
8
8
  elsif !loaded? || stale_target?
9
9
  reload
@@ -39,13 +39,7 @@ module ActiveRecord
39
39
  end
40
40
 
41
41
  def get_records
42
- if reflection.scope_chain.any?(&:any?) ||
43
- scope.eager_loading? ||
44
- klass.current_scope ||
45
- klass.default_scopes.any?
46
-
47
- return scope.limit(1).to_a
48
- end
42
+ return scope.limit(1).to_a if skip_statement_cache?
49
43
 
50
44
  conn = klass.connection
51
45
  sc = reflection.association_scope_cache(conn, owner) do
@@ -15,12 +15,6 @@ module ActiveRecord
15
15
  scope = super
16
16
  reflection.chain.drop(1).each do |reflection|
17
17
  relation = reflection.klass.all
18
-
19
- reflection_scope = reflection.scope
20
- if reflection_scope && reflection_scope.arity.zero?
21
- relation.merge!(reflection_scope)
22
- end
23
-
24
18
  scope.merge!(
25
19
  relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
26
20
  )
@@ -91,6 +85,17 @@ module ActiveRecord
91
85
  raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
92
86
  end
93
87
  end
88
+
89
+ def build_record(attributes)
90
+ inverse = source_reflection.inverse_of
91
+ target = through_association.target
92
+
93
+ if inverse && target && !target.is_a?(Array)
94
+ attributes[inverse.foreign_key] = target.id
95
+ end
96
+
97
+ super(attributes)
98
+ end
94
99
  end
95
100
  end
96
101
  end
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  end
52
52
 
53
53
  def changed_in_place_from?(old_value)
54
- type.changed_in_place?(old_value, value)
54
+ has_been_read? && type.changed_in_place?(old_value, value)
55
55
  end
56
56
 
57
57
  def with_value_from_user(value)
@@ -74,6 +74,10 @@ module ActiveRecord
74
74
  true
75
75
  end
76
76
 
77
+ def came_from_user?
78
+ false
79
+ end
80
+
77
81
  def ==(other)
78
82
  self.class == other.class &&
79
83
  name == other.name &&
@@ -89,6 +93,12 @@ module ActiveRecord
89
93
  end
90
94
  end
91
95
 
96
+ private
97
+
98
+ def has_been_read?
99
+ defined?(@value)
100
+ end
101
+
92
102
  class FromDatabase < Attribute # :nodoc:
93
103
  def type_cast(value)
94
104
  type.type_cast_from_database(value)
@@ -99,6 +109,10 @@ module ActiveRecord
99
109
  def type_cast(value)
100
110
  type.type_cast_from_user(value)
101
111
  end
112
+
113
+ def came_from_user?
114
+ true
115
+ end
102
116
  end
103
117
 
104
118
  class WithCastValue < Attribute # :nodoc:
@@ -52,7 +52,7 @@ module ActiveRecord
52
52
 
53
53
  def _assign_attribute(k, v)
54
54
  public_send("#{k}=", v)
55
- rescue NoMethodError
55
+ rescue NoMethodError, NameError
56
56
  if respond_to?("#{k}=")
57
57
  raise
58
58
  else
@@ -69,7 +69,7 @@ module ActiveRecord
69
69
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
70
70
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
71
71
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
72
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
72
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
73
73
  # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
74
74
  def assign_multiparameter_attributes(pairs)
75
75
  execute_callstack_for_multiparameter_attributes(
@@ -287,9 +287,8 @@ module ActiveRecord
287
287
  # Returns an <tt>#inspect</tt>-like string for the value of the
288
288
  # attribute +attr_name+. String attributes are truncated up to 50
289
289
  # characters, Date and Time attributes are returned in the
290
- # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
291
- # Other attributes return the value of <tt>#inspect</tt> without
292
- # modification.
290
+ # <tt>:db</tt> format. Other attributes return the value of
291
+ # <tt>#inspect</tt> without modification.
293
292
  #
294
293
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
295
294
  #
@@ -300,7 +299,7 @@ module ActiveRecord
300
299
  # # => "\"2012-10-22 00:15:07\""
301
300
  #
302
301
  # person.attribute_for_inspect(:tag_ids)
303
- # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
302
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
304
303
  def attribute_for_inspect(attr_name)
305
304
  value = read_attribute(attr_name)
306
305
 
@@ -308,9 +307,6 @@ module ActiveRecord
308
307
  "#{value[0, 50]}...".inspect
309
308
  elsif value.is_a?(Date) || value.is_a?(Time)
310
309
  %("#{value.to_s(:db)}")
311
- elsif value.is_a?(Array) && value.size > 10
312
- inspected = value.first(10).inspect
313
- %(#{inspected[0...-1]}, ...])
314
310
  else
315
311
  value.inspect
316
312
  end
@@ -368,7 +364,7 @@ module ActiveRecord
368
364
  # person = Person.new
369
365
  # person[:age] = '22'
370
366
  # person[:age] # => 22
371
- # person[:age] # => Fixnum
367
+ # person[:age].class # => Integer
372
368
  def []=(attr_name, value)
373
369
  write_attribute(attr_name, value)
374
370
  end
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
 
29
29
  included do
30
30
  attribute_method_suffix "_before_type_cast"
31
+ attribute_method_suffix "_came_from_user?"
31
32
  end
32
33
 
33
34
  # Returns the value of the attribute identified by +attr_name+ before
@@ -66,6 +67,10 @@ module ActiveRecord
66
67
  def attribute_before_type_cast(attribute_name)
67
68
  read_attribute_before_type_cast(attribute_name)
68
69
  end
70
+
71
+ def attribute_came_from_user?(attribute_name)
72
+ @attributes[attribute_name].came_from_user?
73
+ end
69
74
  end
70
75
  end
71
76
  end
@@ -40,6 +40,7 @@ module ActiveRecord
40
40
 
41
41
  def initialize_dup(other) # :nodoc:
42
42
  super
43
+ @original_raw_attributes = nil
43
44
  calculate_changes_from_defaults
44
45
  end
45
46
 
@@ -76,6 +77,10 @@ module ActiveRecord
76
77
 
77
78
  private
78
79
 
80
+ def changes_include?(attr_name)
81
+ super || attribute_changed_in_place?(attr_name)
82
+ end
83
+
79
84
  def calculate_changes_from_defaults
80
85
  @changed_attributes = nil
81
86
  self.class.column_defaults.each do |attr, orig_value|
@@ -104,7 +109,8 @@ module ActiveRecord
104
109
  end
105
110
 
106
111
  def save_changed_attribute(attr, old_value)
107
- if attribute_changed?(attr)
112
+ clear_changed_attributes_cache
113
+ if attribute_changed_by_setter?(attr)
108
114
  clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
109
115
  else
110
116
  set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
@@ -130,7 +136,7 @@ module ActiveRecord
130
136
  # Serialized attributes should always be written in case they've been
131
137
  # changed in place.
132
138
  def keys_for_partial_write
133
- changed
139
+ changed & persistable_attribute_names
134
140
  end
135
141
 
136
142
  def _field_changed?(attr, old_value)
@@ -161,7 +167,7 @@ module ActiveRecord
161
167
  end
162
168
 
163
169
  def store_original_raw_attribute(attr_name)
164
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
170
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
165
171
  end
166
172
 
167
173
  def store_original_raw_attributes
@@ -174,7 +180,11 @@ module ActiveRecord
174
180
  @cached_changed_attributes = changed_attributes
175
181
  yield
176
182
  ensure
177
- remove_instance_variable(:@cached_changed_attributes)
183
+ clear_changed_attributes_cache
184
+ end
185
+
186
+ def clear_changed_attributes_cache
187
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
178
188
  end
179
189
  end
180
190
  end
@@ -12,7 +12,11 @@ module ActiveRecord
12
12
  if value.is_a?(Array)
13
13
  value.map { |v| type_cast_from_user(v) }
14
14
  elsif value.respond_to?(:in_time_zone)
15
- value.in_time_zone || super
15
+ begin
16
+ value.in_time_zone || super
17
+ rescue ArgumentError
18
+ nil
19
+ end
16
20
  end
17
21
  end
18
22
 
@@ -50,7 +50,7 @@ module ActiveRecord
50
50
  end
51
51
 
52
52
  # Updates the attribute identified by <tt>attr_name</tt> with the
53
- # specified +value+. Empty strings for fixnum and float columns are
53
+ # specified +value+. Empty strings for Integer and Float columns are
54
54
  # turned into +nil+.
55
55
  def write_attribute(attr_name, value)
56
56
  write_attribute_with_type_cast(attr_name, value, true)
@@ -64,6 +64,10 @@ module ActiveRecord
64
64
  end
65
65
  end
66
66
 
67
+ def ==(other)
68
+ attributes == other.attributes
69
+ end
70
+
67
71
  protected
68
72
 
69
73
  attr_reader :attributes
@@ -1,3 +1,5 @@
1
+ require 'active_record/attribute'
2
+
1
3
  module ActiveRecord
2
4
  class AttributeSet # :nodoc:
3
5
  class Builder # :nodoc:
@@ -20,7 +22,7 @@ module ActiveRecord
20
22
  end
21
23
 
22
24
  class LazyAttributeHash # :nodoc:
23
- delegate :select, :transform_values, to: :materialize
25
+ delegate :transform_values, to: :materialize
24
26
 
25
27
  def initialize(types, values, additional_types)
26
28
  @types = types
@@ -54,10 +56,39 @@ module ActiveRecord
54
56
  super
55
57
  end
56
58
 
59
+ def select
60
+ keys = types.keys | values.keys | delegate_hash.keys
61
+ keys.each_with_object({}) do |key, hash|
62
+ attribute = self[key]
63
+ if yield(key, attribute)
64
+ hash[key] = attribute
65
+ end
66
+ end
67
+ end
68
+
69
+ def ==(other)
70
+ if other.is_a?(LazyAttributeHash)
71
+ materialize == other.materialize
72
+ else
73
+ materialize == other
74
+ end
75
+ end
76
+
57
77
  protected
58
78
 
59
79
  attr_reader :types, :values, :additional_types, :delegate_hash
60
80
 
81
+ def materialize
82
+ unless @materialized
83
+ values.each_key { |key| self[key] }
84
+ types.each_key { |key| self[key] }
85
+ unless frozen?
86
+ @materialized = true
87
+ end
88
+ end
89
+ delegate_hash
90
+ end
91
+
61
92
  private
62
93
 
63
94
  def assign_default_value(name)
@@ -71,16 +102,5 @@ module ActiveRecord
71
102
  delegate_hash[name] = Attribute.uninitialized(name, type)
72
103
  end
73
104
  end
74
-
75
- def materialize
76
- unless @materialized
77
- values.each_key { |key| self[key] }
78
- types.each_key { |key| self[key] }
79
- unless frozen?
80
- @materialized = true
81
- end
82
- end
83
- delegate_hash
84
- end
85
105
  end
86
106
  end