activerecord 4.1.15 → 4.2.0.beta1

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2176
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/association_scope.rb +53 -21
  7. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  10. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  11. data/lib/active_record/associations/builder/has_one.rb +2 -2
  12. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  13. data/lib/active_record/associations/collection_association.rb +32 -44
  14. data/lib/active_record/associations/collection_proxy.rb +1 -10
  15. data/lib/active_record/associations/has_many_association.rb +60 -14
  16. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  17. data/lib/active_record/associations/has_one_association.rb +0 -1
  18. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  19. data/lib/active_record/associations/join_dependency.rb +7 -9
  20. data/lib/active_record/associations/preloader/association.rb +9 -5
  21. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/singular_association.rb +16 -1
  24. data/lib/active_record/associations/through_association.rb +6 -22
  25. data/lib/active_record/associations.rb +58 -33
  26. data/lib/active_record/attribute.rb +131 -0
  27. data/lib/active_record/attribute_assignment.rb +19 -11
  28. data/lib/active_record/attribute_decorators.rb +66 -0
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  30. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  31. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  32. data/lib/active_record/attribute_methods/read.rb +14 -57
  33. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  35. data/lib/active_record/attribute_methods/write.rb +8 -23
  36. data/lib/active_record/attribute_methods.rb +53 -90
  37. data/lib/active_record/attribute_set/builder.rb +32 -0
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attributes.rb +122 -0
  40. data/lib/active_record/autosave_association.rb +11 -21
  41. data/lib/active_record/base.rb +9 -19
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  52. data/lib/active_record/connection_adapters/column.rb +13 -244
  53. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  55. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  56. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  59. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  60. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  85. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  86. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  87. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  88. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  89. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  90. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  91. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  93. data/lib/active_record/connection_handling.rb +1 -1
  94. data/lib/active_record/core.rb +119 -22
  95. data/lib/active_record/counter_cache.rb +60 -6
  96. data/lib/active_record/enum.rb +9 -10
  97. data/lib/active_record/errors.rb +27 -26
  98. data/lib/active_record/explain.rb +1 -1
  99. data/lib/active_record/fixtures.rb +52 -45
  100. data/lib/active_record/gem_version.rb +3 -3
  101. data/lib/active_record/inheritance.rb +33 -8
  102. data/lib/active_record/integration.rb +4 -4
  103. data/lib/active_record/locking/optimistic.rb +34 -16
  104. data/lib/active_record/migration/command_recorder.rb +19 -2
  105. data/lib/active_record/migration/join_table.rb +1 -1
  106. data/lib/active_record/migration.rb +22 -32
  107. data/lib/active_record/model_schema.rb +39 -48
  108. data/lib/active_record/nested_attributes.rb +8 -18
  109. data/lib/active_record/persistence.rb +39 -22
  110. data/lib/active_record/query_cache.rb +3 -3
  111. data/lib/active_record/querying.rb +1 -8
  112. data/lib/active_record/railtie.rb +17 -10
  113. data/lib/active_record/railties/databases.rake +47 -42
  114. data/lib/active_record/readonly_attributes.rb +0 -1
  115. data/lib/active_record/reflection.rb +225 -92
  116. data/lib/active_record/relation/batches.rb +0 -2
  117. data/lib/active_record/relation/calculations.rb +28 -32
  118. data/lib/active_record/relation/delegation.rb +1 -1
  119. data/lib/active_record/relation/finder_methods.rb +42 -20
  120. data/lib/active_record/relation/merger.rb +0 -1
  121. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  122. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/query_methods.rb +98 -62
  125. data/lib/active_record/relation/spawn_methods.rb +6 -7
  126. data/lib/active_record/relation.rb +35 -11
  127. data/lib/active_record/result.rb +16 -9
  128. data/lib/active_record/sanitization.rb +8 -1
  129. data/lib/active_record/schema.rb +0 -1
  130. data/lib/active_record/schema_dumper.rb +51 -9
  131. data/lib/active_record/schema_migration.rb +4 -0
  132. data/lib/active_record/scoping/default.rb +5 -4
  133. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  134. data/lib/active_record/statement_cache.rb +79 -5
  135. data/lib/active_record/store.rb +5 -5
  136. data/lib/active_record/tasks/database_tasks.rb +37 -5
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  138. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  139. data/lib/active_record/timestamp.rb +9 -7
  140. data/lib/active_record/transactions.rb +35 -21
  141. data/lib/active_record/type/binary.rb +40 -0
  142. data/lib/active_record/type/boolean.rb +19 -0
  143. data/lib/active_record/type/date.rb +46 -0
  144. data/lib/active_record/type/date_time.rb +43 -0
  145. data/lib/active_record/type/decimal.rb +40 -0
  146. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  147. data/lib/active_record/type/float.rb +19 -0
  148. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  149. data/lib/active_record/type/integer.rb +23 -0
  150. data/lib/active_record/type/mutable.rb +16 -0
  151. data/lib/active_record/type/numeric.rb +36 -0
  152. data/lib/active_record/type/serialized.rb +51 -0
  153. data/lib/active_record/type/string.rb +36 -0
  154. data/lib/active_record/type/text.rb +11 -0
  155. data/lib/active_record/type/time.rb +26 -0
  156. data/lib/active_record/type/time_value.rb +38 -0
  157. data/lib/active_record/type/type_map.rb +48 -0
  158. data/lib/active_record/type/value.rb +101 -0
  159. data/lib/active_record/type.rb +20 -0
  160. data/lib/active_record/validations/uniqueness.rb +9 -23
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record.rb +2 -1
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -18,6 +18,8 @@ module ActiveRecord
18
18
  include TimeZoneConversion
19
19
  include Dirty
20
20
  include Serialization
21
+
22
+ delegate :column_for_attribute, to: :class
21
23
  end
22
24
 
23
25
  AttrNames = Module.new {
@@ -29,7 +31,7 @@ module ActiveRecord
29
31
  end
30
32
  }
31
33
 
32
- BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
34
+ BLACKLISTED_CLASS_METHODS = %w(private public protected)
33
35
 
34
36
  class AttributeMethodCache
35
37
  def initialize
@@ -48,7 +50,11 @@ module ActiveRecord
48
50
  end
49
51
 
50
52
  private
51
- def method_body; raise NotImplementedError; end
53
+
54
+ # Override this method in the subclasses for method body.
55
+ def method_body(method_name, const_name)
56
+ raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
57
+ end
52
58
  end
53
59
 
54
60
  class GeneratedAttributeMethods < Module; end # :nodoc:
@@ -63,15 +69,13 @@ module ActiveRecord
63
69
  @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
64
70
  @attribute_methods_generated = false
65
71
  include @generated_attribute_methods
66
-
67
- super
68
72
  end
69
73
 
70
74
  # Generates all the attribute related methods for columns in the database
71
75
  # accessors, mutators and query methods.
72
76
  def define_attribute_methods # :nodoc:
73
77
  return false if @attribute_methods_generated
74
- # Use a mutex; we don't want two thread simultaneously trying to define
78
+ # Use a mutex; we don't want two threads simultaneously trying to define
75
79
  # attribute methods.
76
80
  generated_attribute_methods.synchronize do
77
81
  return false if @attribute_methods_generated
@@ -84,7 +88,7 @@ module ActiveRecord
84
88
 
85
89
  def undefine_attribute_methods # :nodoc:
86
90
  generated_attribute_methods.synchronize do
87
- super if defined?(@attribute_methods_generated) && @attribute_methods_generated
91
+ super if @attribute_methods_generated
88
92
  @attribute_methods_generated = false
89
93
  end
90
94
  end
@@ -105,7 +109,7 @@ module ActiveRecord
105
109
  # # => false
106
110
  def instance_method_already_implemented?(method_name)
107
111
  if dangerous_attribute_method?(method_name)
108
- raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
112
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record"
109
113
  end
110
114
 
111
115
  if superclass == Base
@@ -155,16 +159,6 @@ module ActiveRecord
155
159
  end
156
160
  end
157
161
 
158
- def find_generated_attribute_method(method_name) # :nodoc:
159
- klass = self
160
- until klass == Base
161
- gen_methods = klass.generated_attribute_methods
162
- return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
163
- klass = klass.superclass
164
- end
165
- nil
166
- end
167
-
168
162
  # Returns +true+ if +attribute+ is an attribute method and table exists,
169
163
  # +false+ otherwise.
170
164
  #
@@ -193,24 +187,29 @@ module ActiveRecord
193
187
  []
194
188
  end
195
189
  end
196
- end
197
190
 
198
- # If we haven't generated any methods yet, generate them, then
199
- # see if we've created the method we're looking for.
200
- def method_missing(method, *args, &block) # :nodoc:
201
- self.class.define_attribute_methods
202
- if respond_to_without_attributes?(method)
203
- # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
204
- # call in a overwritten attribute method
205
- if attribute_method = self.class.find_generated_attribute_method(method)
206
- # this is probably horribly slow, but should only happen at most once for a given AR class
207
- attribute_method.bind(self).call(*args, &block)
208
- else
209
- return super unless respond_to_missing?(method, true)
210
- send(method, *args, &block)
191
+ # Returns the column object for the named attribute.
192
+ # Returns nil if the named attribute does not exist.
193
+ #
194
+ # class Person < ActiveRecord::Base
195
+ # end
196
+ #
197
+ # person = Person.new
198
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
199
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
200
+ #
201
+ # person.column_for_attribute(:nothing)
202
+ # # => nil
203
+ def column_for_attribute(name)
204
+ column = columns_hash[name.to_s]
205
+ if column.nil?
206
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
207
+ `column_for_attribute` will return a null object for non-existent columns
208
+ in Rails 5.0. Use `has_attribute?` if you need to check for an
209
+ attribute's existence.
210
+ MESSAGE
211
211
  end
212
- else
213
- super
212
+ column
214
213
  end
215
214
  end
216
215
 
@@ -231,18 +230,14 @@ module ActiveRecord
231
230
  # person.respond_to('age?') # => true
232
231
  # person.respond_to(:nothing) # => false
233
232
  def respond_to?(name, include_private = false)
233
+ return false unless super
234
234
  name = name.to_s
235
- self.class.define_attribute_methods
236
- result = super
237
-
238
- # If the result is false the answer is false.
239
- return false unless result
240
235
 
241
236
  # If the result is true then check for the select case.
242
237
  # For queries selecting a subset of columns, return false for unselected columns.
243
238
  # We check defined?(@attributes) not to issue warnings if called on objects that
244
239
  # have been allocated but not yet initialized.
245
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
240
+ if defined?(@attributes) && self.class.column_names.include?(name)
246
241
  return has_attribute?(name)
247
242
  end
248
243
 
@@ -259,7 +254,7 @@ module ActiveRecord
259
254
  # person.has_attribute?('age') # => true
260
255
  # person.has_attribute?(:nothing) # => false
261
256
  def has_attribute?(attr_name)
262
- @attributes.has_key?(attr_name.to_s)
257
+ @attributes.key?(attr_name.to_s)
263
258
  end
264
259
 
265
260
  # Returns an array of names for the attributes available on this object.
@@ -283,20 +278,13 @@ module ActiveRecord
283
278
  # person.attributes
284
279
  # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
285
280
  def attributes
286
- attribute_names.each_with_object({}) { |name, attrs|
287
- attrs[name] = read_attribute(name)
288
- }
289
- end
290
-
291
- # Placeholder so it can be overriden when needed by serialization
292
- def attributes_for_coder # :nodoc:
293
- attributes
281
+ @attributes.to_hash
294
282
  end
295
283
 
296
284
  # Returns an <tt>#inspect</tt>-like string for the value of the
297
- # attribute +attr_name+. String attributes are truncated upto 50
285
+ # attribute +attr_name+. String attributes are truncated up to 50
298
286
  # characters, Date and Time attributes are returned in the
299
- # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
287
+ # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
300
288
  # Other attributes return the value of <tt>#inspect</tt> without
301
289
  # modification.
302
290
  #
@@ -333,39 +321,24 @@ module ActiveRecord
333
321
  # class Task < ActiveRecord::Base
334
322
  # end
335
323
  #
336
- # person = Task.new(title: '', is_done: false)
337
- # person.attribute_present?(:title) # => false
338
- # person.attribute_present?(:is_done) # => true
339
- # person.name = 'Francesco'
340
- # person.is_done = true
341
- # person.attribute_present?(:title) # => true
342
- # person.attribute_present?(:is_done) # => true
324
+ # task = Task.new(title: '', is_done: false)
325
+ # task.attribute_present?(:title) # => false
326
+ # task.attribute_present?(:is_done) # => true
327
+ # task.title = 'Buy milk'
328
+ # task.is_done = true
329
+ # task.attribute_present?(:title) # => true
330
+ # task.attribute_present?(:is_done) # => true
343
331
  def attribute_present?(attribute)
344
332
  value = read_attribute(attribute)
345
333
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
346
334
  end
347
335
 
348
- # Returns the column object for the named attribute. Returns +nil+ if the
349
- # named attribute not exists.
350
- #
351
- # class Person < ActiveRecord::Base
352
- # end
353
- #
354
- # person = Person.new
355
- # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
356
- # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
357
- #
358
- # person.column_for_attribute(:nothing)
359
- # # => nil
360
- def column_for_attribute(name)
361
- # FIXME: should this return a null object for columns that don't exist?
362
- self.class.columns_hash[name.to_s]
363
- end
364
-
365
336
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
366
337
  # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
367
338
  # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
368
339
  #
340
+ # Note: +:id+ is always present.
341
+ #
369
342
  # Alias for the <tt>read_attribute</tt> method.
370
343
  #
371
344
  # class Person < ActiveRecord::Base
@@ -399,13 +372,6 @@ module ActiveRecord
399
372
 
400
373
  protected
401
374
 
402
- def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
403
- attribute_names.each do |name|
404
- attributes[name] = clone_attribute_value(reader_method, name)
405
- end
406
- attributes
407
- end
408
-
409
375
  def clone_attribute_value(reader_method, attribute_name) # :nodoc:
410
376
  value = send(reader_method, attribute_name)
411
377
  value.duplicable? ? value.clone : value
@@ -423,7 +389,7 @@ module ActiveRecord
423
389
 
424
390
  def attribute_method?(attr_name) # :nodoc:
425
391
  # We check defined? because Syck calls respond_to? before actually calling initialize.
426
- defined?(@attributes) && @attributes.include?(attr_name)
392
+ defined?(@attributes) && @attributes.key?(attr_name)
427
393
  end
428
394
 
429
395
  private
@@ -442,16 +408,16 @@ module ActiveRecord
442
408
 
443
409
  # Filters the primary keys and readonly attributes from the attribute names.
444
410
  def attributes_for_update(attribute_names)
445
- attribute_names.select do |name|
446
- column_for_attribute(name) && !readonly_attribute?(name)
411
+ attribute_names.reject do |name|
412
+ readonly_attribute?(name)
447
413
  end
448
414
  end
449
415
 
450
416
  # Filters out the primary keys, from the attribute names, when the primary
451
417
  # key is to be generated (e.g. the id attribute has no value).
452
418
  def attributes_for_create(attribute_names)
453
- attribute_names.select do |name|
454
- column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
419
+ attribute_names.reject do |name|
420
+ pk_attribute?(name) && id.nil?
455
421
  end
456
422
  end
457
423
 
@@ -460,13 +426,10 @@ module ActiveRecord
460
426
  end
461
427
 
462
428
  def pk_attribute?(name)
463
- column_for_attribute(name).primary
429
+ name == self.class.primary_key
464
430
  end
465
431
 
466
432
  def typecasted_attribute_value(name)
467
- # FIXME: we need @attributes to be used consistently.
468
- # If the values stored in @attributes were already typecasted, this code
469
- # could be simplified
470
433
  read_attribute(name)
471
434
  end
472
435
  end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ class AttributeSet # :nodoc:
3
+ class Builder # :nodoc:
4
+ attr_reader :types
5
+
6
+ def initialize(types)
7
+ @types = types
8
+ end
9
+
10
+ def build_from_database(values = {}, additional_types = {})
11
+ attributes = build_attributes_from_values(values, additional_types)
12
+ add_uninitialized_attributes(attributes)
13
+ AttributeSet.new(attributes)
14
+ end
15
+
16
+ private
17
+
18
+ def build_attributes_from_values(values, additional_types)
19
+ values.each_with_object({}) do |(name, value), hash|
20
+ type = additional_types.fetch(name, types[name])
21
+ hash[name] = Attribute.from_database(name, value, type)
22
+ end
23
+ end
24
+
25
+ def add_uninitialized_attributes(attributes)
26
+ types.except(*attributes.keys).each do |name, type|
27
+ attributes[name] = Attribute.uninitialized(name, type)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,77 @@
1
+ require 'active_record/attribute_set/builder'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ delegate :keys, to: :initialized_attributes
6
+
7
+ def initialize(attributes)
8
+ @attributes = attributes
9
+ end
10
+
11
+ def [](name)
12
+ attributes[name] || Attribute.null(name)
13
+ end
14
+
15
+ def values_before_type_cast
16
+ attributes.transform_values(&:value_before_type_cast)
17
+ end
18
+
19
+ def to_hash
20
+ initialized_attributes.transform_values(&:value)
21
+ end
22
+ alias_method :to_h, :to_hash
23
+
24
+ def key?(name)
25
+ attributes.key?(name) && self[name].initialized?
26
+ end
27
+
28
+ def fetch_value(name, &block)
29
+ self[name].value(&block)
30
+ end
31
+
32
+ def write_from_database(name, value)
33
+ attributes[name] = self[name].with_value_from_database(value)
34
+ end
35
+
36
+ def write_from_user(name, value)
37
+ attributes[name] = self[name].with_value_from_user(value)
38
+ end
39
+
40
+ def freeze
41
+ @attributes.freeze
42
+ super
43
+ end
44
+
45
+ def initialize_dup(_)
46
+ @attributes = attributes.transform_values(&:dup)
47
+ super
48
+ end
49
+
50
+ def initialize_clone(_)
51
+ @attributes = attributes.clone
52
+ super
53
+ end
54
+
55
+ def reset(key)
56
+ if key?(key)
57
+ write_from_database(key, nil)
58
+ end
59
+ end
60
+
61
+ def ensure_initialized(key)
62
+ unless self[key].initialized?
63
+ write_from_database(key, nil)
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ attr_reader :attributes
70
+
71
+ private
72
+
73
+ def initialized_attributes
74
+ attributes.select { |_, attr| attr.initialized? }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,122 @@
1
+ module ActiveRecord
2
+ module Attributes # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ Type = ActiveRecord::Type
6
+
7
+ included do
8
+ class_attribute :user_provided_columns, instance_accessor: false # :internal:
9
+ self.user_provided_columns = {}
10
+ end
11
+
12
+ module ClassMethods # :nodoc:
13
+ # Defines or overrides a attribute on this model. This allows customization of
14
+ # Active Record's type casting behavior, as well as adding support for user defined
15
+ # types.
16
+ #
17
+ # +name+ The name of the methods to define attribute methods for, and the column which
18
+ # this will persist to.
19
+ #
20
+ # +cast_type+ A type object that contains information about how to type cast the value.
21
+ # See the examples section for more information.
22
+ #
23
+ # ==== Options
24
+ # The options hash accepts the following options:
25
+ #
26
+ # +default+ is the default value that the column should use on a new record.
27
+ #
28
+ # ==== Examples
29
+ #
30
+ # The type detected by Active Record can be overridden.
31
+ #
32
+ # # db/schema.rb
33
+ # create_table :store_listings, force: true do |t|
34
+ # t.decimal :price_in_cents
35
+ # end
36
+ #
37
+ # # app/models/store_listing.rb
38
+ # class StoreListing < ActiveRecord::Base
39
+ # end
40
+ #
41
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
42
+ #
43
+ # # before
44
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
45
+ #
46
+ # class StoreListing < ActiveRecord::Base
47
+ # attribute :price_in_cents, Type::Integer.new
48
+ # end
49
+ #
50
+ # # after
51
+ # store_listing.price_in_cents # => 10
52
+ #
53
+ # Users may also define their own custom types, as long as they respond to the methods
54
+ # defined on the value type. The `type_cast` method on your type object will be called
55
+ # with values both from the database, and from your controllers. See
56
+ # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
57
+ # type objects inherit from an existing type, or the base value type.
58
+ #
59
+ # class MoneyType < ActiveRecord::Type::Integer
60
+ # def type_cast(value)
61
+ # if value.include?('$')
62
+ # price_in_dollars = value.gsub(/\$/, '').to_f
63
+ # price_in_dollars * 100
64
+ # else
65
+ # value.to_i
66
+ # end
67
+ # end
68
+ # end
69
+ #
70
+ # class StoreListing < ActiveRecord::Base
71
+ # attribute :price_in_cents, MoneyType.new
72
+ # end
73
+ #
74
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
75
+ # store_listing.price_in_cents # => 1000
76
+ def attribute(name, cast_type, options = {})
77
+ name = name.to_s
78
+ clear_caches_calculated_from_columns
79
+ # Assign a new hash to ensure that subclasses do not share a hash
80
+ self.user_provided_columns = user_provided_columns.merge(name => connection.new_column(name, options[:default], cast_type))
81
+ end
82
+
83
+ # Returns an array of column objects for the table associated with this class.
84
+ def columns
85
+ @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
86
+ end
87
+
88
+ # Returns a hash of column objects for the table associated with this class.
89
+ def columns_hash
90
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
91
+ end
92
+
93
+ def reset_column_information # :nodoc:
94
+ super
95
+ clear_caches_calculated_from_columns
96
+ end
97
+
98
+ private
99
+
100
+ def add_user_provided_columns(schema_columns)
101
+ existing_columns = schema_columns.map do |column|
102
+ user_provided_columns[column.name] || column
103
+ end
104
+
105
+ existing_column_names = existing_columns.map(&:name)
106
+ new_columns = user_provided_columns.except(*existing_column_names).values
107
+
108
+ existing_columns + new_columns
109
+ end
110
+
111
+ def clear_caches_calculated_from_columns
112
+ @attributes_builder = nil
113
+ @column_names = nil
114
+ @column_types = nil
115
+ @columns = nil
116
+ @columns_hash = nil
117
+ @content_columns = nil
118
+ @default_attributes = nil
119
+ end
120
+ end
121
+ end
122
+ end
@@ -177,15 +177,15 @@ module ActiveRecord
177
177
  # before actually defining them.
178
178
  def add_autosave_association_callbacks(reflection)
179
179
  save_method = :"autosave_associated_records_for_#{reflection.name}"
180
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
181
+ collection = reflection.collection?
180
182
 
181
- if reflection.collection?
183
+ if collection
182
184
  before_save :before_save_collection_association
183
185
 
184
186
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
185
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
186
- after_create save_method
187
- after_update save_method
188
- elsif reflection.macro == :has_one
187
+ after_save save_method
188
+ elsif reflection.has_one?
189
189
  define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
190
190
  # Configures two callbacks instead of a single after_save so that
191
191
  # the model may rely on their execution order relative to its
@@ -202,22 +202,9 @@ module ActiveRecord
202
202
  before_save save_method
203
203
  end
204
204
 
205
- define_autosave_validation_callbacks(reflection)
206
- end
207
-
208
- def define_autosave_validation_callbacks(reflection)
209
- validation_method = :"validate_associated_records_for_#{reflection.name}"
210
205
  if reflection.validate? && !method_defined?(validation_method)
211
- if reflection.collection?
212
- method = :validate_collection_association
213
- else
214
- method = :validate_single_association
215
- end
216
-
217
- define_non_cyclic_method(validation_method) do
218
- send(method, reflection)
219
- true
220
- end
206
+ method = (collection ? :validate_collection_association : :validate_single_association)
207
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
221
208
  validate validation_method
222
209
  end
223
210
  end
@@ -316,7 +303,8 @@ module ActiveRecord
316
303
  def association_valid?(reflection, record)
317
304
  return true if record.destroyed? || record.marked_for_destruction?
318
305
 
319
- unless valid = record.valid?
306
+ validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
307
+ unless valid = record.valid?(validation_context)
320
308
  if reflection.options[:autosave]
321
309
  record.errors.each do |attribute, message|
322
310
  attribute = "#{reflection.name}.#{attribute}"
@@ -350,6 +338,7 @@ module ActiveRecord
350
338
  autosave = reflection.options[:autosave]
351
339
 
352
340
  if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
341
+
353
342
  if autosave
354
343
  records_to_destroy = records.select(&:marked_for_destruction?)
355
344
  records_to_destroy.each { |record| association.destroy(record) }
@@ -373,6 +362,7 @@ module ActiveRecord
373
362
 
374
363
  raise ActiveRecord::Rollback unless saved
375
364
  end
365
+ @new_record_before_save = false
376
366
  end
377
367
 
378
368
  # reconstruct the scope now that we know the owner's id
@@ -9,16 +9,19 @@ require 'active_support/core_ext/class/delegating_attributes'
9
9
  require 'active_support/core_ext/array/extract_options'
10
10
  require 'active_support/core_ext/hash/deep_merge'
11
11
  require 'active_support/core_ext/hash/slice'
12
+ require 'active_support/core_ext/hash/transform_values'
12
13
  require 'active_support/core_ext/string/behavior'
13
14
  require 'active_support/core_ext/kernel/singleton_class'
14
15
  require 'active_support/core_ext/module/introspection'
15
16
  require 'active_support/core_ext/object/duplicable'
16
17
  require 'active_support/core_ext/class/subclasses'
17
18
  require 'arel'
19
+ require 'active_record/attribute_decorators'
18
20
  require 'active_record/errors'
19
21
  require 'active_record/log_subscriber'
20
22
  require 'active_record/explain_subscriber'
21
23
  require 'active_record/relation/delegation'
24
+ require 'active_record/attributes'
22
25
 
23
26
  module ActiveRecord #:nodoc:
24
27
  # = Active Record
@@ -138,6 +141,7 @@ module ActiveRecord #:nodoc:
138
141
  #
139
142
  # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
140
143
  # Query methods allow you to test whether an attribute value is present.
144
+ # For numeric values, present is defined as non-zero.
141
145
  #
142
146
  # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
143
147
  # to determine whether the user has a name:
@@ -217,25 +221,9 @@ module ActiveRecord #:nodoc:
217
221
  #
218
222
  # == Single table inheritance
219
223
  #
220
- # Active Record allows inheritance by storing the name of the class in a column that by
221
- # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
222
- # This means that an inheritance looking like this:
223
- #
224
- # class Company < ActiveRecord::Base; end
225
- # class Firm < Company; end
226
- # class Client < Company; end
227
- # class PriorityClient < Client; end
228
- #
229
- # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
230
- # the companies table with type = "Firm". You can then fetch this row again using
231
- # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
232
- #
233
- # If you don't have a type column defined in your table, single-table inheritance won't
234
- # be triggered. In that case, it'll work just like normal subclasses with no special magic
235
- # for differentiating between them or reloading the right type with find.
236
- #
237
- # Note, all the attributes for all the cases are kept in the same table. Read more:
238
- # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
224
+ # Active Record allows inheritance by storing the name of the class in a
225
+ # column that is named "type" by default. See ActiveRecord::Inheritance for
226
+ # more details.
239
227
  #
240
228
  # == Connection to multiple databases in different models
241
229
  #
@@ -306,6 +294,8 @@ module ActiveRecord #:nodoc:
306
294
  include Integration
307
295
  include Validations
308
296
  include CounterCache
297
+ include Attributes
298
+ include AttributeDecorators
309
299
  include Locking::Optimistic
310
300
  include Locking::Pessimistic
311
301
  include AttributeMethods