activerecord 4.1.15 → 4.2.11.3

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 (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1792
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +4 -0
  5. data/lib/active_record/aggregations.rb +15 -8
  6. data/lib/active_record/association_relation.rb +13 -0
  7. data/lib/active_record/associations.rb +158 -49
  8. data/lib/active_record/associations/alias_tracker.rb +3 -12
  9. data/lib/active_record/associations/association.rb +16 -4
  10. data/lib/active_record/associations/association_scope.rb +83 -38
  11. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  12. data/lib/active_record/associations/builder/association.rb +15 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  16. data/lib/active_record/associations/builder/has_many.rb +1 -1
  17. data/lib/active_record/associations/builder/has_one.rb +2 -2
  18. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  19. data/lib/active_record/associations/collection_association.rb +63 -27
  20. data/lib/active_record/associations/collection_proxy.rb +29 -35
  21. data/lib/active_record/associations/foreign_association.rb +11 -0
  22. data/lib/active_record/associations/has_many_association.rb +83 -22
  23. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  24. data/lib/active_record/associations/has_one_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  27. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/preloader/association.rb +14 -11
  30. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  31. data/lib/active_record/associations/singular_association.rb +17 -2
  32. data/lib/active_record/associations/through_association.rb +5 -12
  33. data/lib/active_record/attribute.rb +163 -0
  34. data/lib/active_record/attribute_assignment.rb +19 -11
  35. data/lib/active_record/attribute_decorators.rb +66 -0
  36. data/lib/active_record/attribute_methods.rb +56 -94
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  39. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  40. data/lib/active_record/attribute_methods/query.rb +1 -1
  41. data/lib/active_record/attribute_methods/read.rb +22 -59
  42. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  44. data/lib/active_record/attribute_methods/write.rb +9 -24
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attribute_set/builder.rb +106 -0
  47. data/lib/active_record/attributes.rb +147 -0
  48. data/lib/active_record/autosave_association.rb +19 -12
  49. data/lib/active_record/base.rb +13 -24
  50. data/lib/active_record/callbacks.rb +6 -6
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  63. data/lib/active_record/connection_adapters/column.rb +29 -240
  64. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  68. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  71. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  99. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  100. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  101. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  102. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  104. data/lib/active_record/connection_handling.rb +1 -1
  105. data/lib/active_record/core.rb +163 -39
  106. data/lib/active_record/counter_cache.rb +60 -6
  107. data/lib/active_record/enum.rb +9 -11
  108. data/lib/active_record/errors.rb +53 -30
  109. data/lib/active_record/explain.rb +1 -1
  110. data/lib/active_record/explain_subscriber.rb +1 -1
  111. data/lib/active_record/fixtures.rb +55 -69
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +35 -10
  114. data/lib/active_record/integration.rb +4 -4
  115. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  116. data/lib/active_record/locking/optimistic.rb +46 -26
  117. data/lib/active_record/migration.rb +71 -46
  118. data/lib/active_record/migration/command_recorder.rb +19 -2
  119. data/lib/active_record/migration/join_table.rb +1 -1
  120. data/lib/active_record/model_schema.rb +52 -58
  121. data/lib/active_record/nested_attributes.rb +5 -5
  122. data/lib/active_record/no_touching.rb +1 -1
  123. data/lib/active_record/persistence.rb +46 -26
  124. data/lib/active_record/query_cache.rb +3 -3
  125. data/lib/active_record/querying.rb +10 -7
  126. data/lib/active_record/railtie.rb +18 -11
  127. data/lib/active_record/railties/databases.rake +50 -51
  128. data/lib/active_record/readonly_attributes.rb +0 -1
  129. data/lib/active_record/reflection.rb +273 -114
  130. data/lib/active_record/relation.rb +57 -25
  131. data/lib/active_record/relation/batches.rb +0 -2
  132. data/lib/active_record/relation/calculations.rb +41 -37
  133. data/lib/active_record/relation/finder_methods.rb +70 -47
  134. data/lib/active_record/relation/merger.rb +39 -29
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  137. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  138. data/lib/active_record/relation/query_methods.rb +114 -65
  139. data/lib/active_record/relation/spawn_methods.rb +3 -0
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -6
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +53 -27
  155. data/lib/active_record/type.rb +23 -0
  156. data/lib/active_record/type/big_integer.rb +13 -0
  157. data/lib/active_record/type/binary.rb +50 -0
  158. data/lib/active_record/type/boolean.rb +31 -0
  159. data/lib/active_record/type/date.rb +50 -0
  160. data/lib/active_record/type/date_time.rb +54 -0
  161. data/lib/active_record/type/decimal.rb +64 -0
  162. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  163. data/lib/active_record/type/decorator.rb +14 -0
  164. data/lib/active_record/type/float.rb +19 -0
  165. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  166. data/lib/active_record/type/integer.rb +59 -0
  167. data/lib/active_record/type/mutable.rb +16 -0
  168. data/lib/active_record/type/numeric.rb +36 -0
  169. data/lib/active_record/type/serialized.rb +62 -0
  170. data/lib/active_record/type/string.rb +40 -0
  171. data/lib/active_record/type/text.rb +11 -0
  172. data/lib/active_record/type/time.rb +26 -0
  173. data/lib/active_record/type/time_value.rb +38 -0
  174. data/lib/active_record/type/type_map.rb +64 -0
  175. data/lib/active_record/type/unsigned_integer.rb +15 -0
  176. data/lib/active_record/type/value.rb +110 -0
  177. data/lib/active_record/validations.rb +25 -19
  178. data/lib/active_record/validations/associated.rb +5 -3
  179. data/lib/active_record/validations/presence.rb +5 -3
  180. data/lib/active_record/validations/uniqueness.rb +25 -29
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -0,0 +1,66 @@
1
+ module ActiveRecord
2
+ module AttributeDecorators # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
7
+ self.attribute_type_decorations = TypeDecorator.new
8
+ end
9
+
10
+ module ClassMethods # :nodoc:
11
+ def decorate_attribute_type(column_name, decorator_name, &block)
12
+ matcher = ->(name, _) { name == column_name.to_s }
13
+ key = "_#{column_name}_#{decorator_name}"
14
+ decorate_matching_attribute_types(matcher, key, &block)
15
+ end
16
+
17
+ def decorate_matching_attribute_types(matcher, decorator_name, &block)
18
+ clear_caches_calculated_from_columns
19
+ decorator_name = decorator_name.to_s
20
+
21
+ # Create new hashes so we don't modify parent classes
22
+ self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
23
+ end
24
+
25
+ private
26
+
27
+ def add_user_provided_columns(*)
28
+ super.map do |column|
29
+ decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
30
+ column.with_type(decorated_type)
31
+ end
32
+ end
33
+ end
34
+
35
+ class TypeDecorator # :nodoc:
36
+ delegate :clear, to: :@decorations
37
+
38
+ def initialize(decorations = {})
39
+ @decorations = decorations
40
+ end
41
+
42
+ def merge(*args)
43
+ TypeDecorator.new(@decorations.merge(*args))
44
+ end
45
+
46
+ def apply(name, type)
47
+ decorations = decorators_for(name, type)
48
+ decorations.inject(type) do |new_type, block|
49
+ block.call(new_type)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def decorators_for(name, type)
56
+ matching(name, type).map(&:last)
57
+ end
58
+
59
+ def matching(name, type)
60
+ @decorations.values.select do |(matcher, _)|
61
+ matcher.call(name, type)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/enumerable'
2
+ require 'active_support/core_ext/string/filters'
2
3
  require 'mutex_m'
3
4
  require 'thread_safe'
4
5
 
@@ -18,6 +19,8 @@ module ActiveRecord
18
19
  include TimeZoneConversion
19
20
  include Dirty
20
21
  include Serialization
22
+
23
+ delegate :column_for_attribute, to: :class
21
24
  end
22
25
 
23
26
  AttrNames = Module.new {
@@ -48,7 +51,11 @@ module ActiveRecord
48
51
  end
49
52
 
50
53
  private
51
- def method_body; raise NotImplementedError; end
54
+
55
+ # Override this method in the subclasses for method body.
56
+ def method_body(method_name, const_name)
57
+ raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
58
+ end
52
59
  end
53
60
 
54
61
  class GeneratedAttributeMethods < Module; end # :nodoc:
@@ -71,7 +78,7 @@ module ActiveRecord
71
78
  # accessors, mutators and query methods.
72
79
  def define_attribute_methods # :nodoc:
73
80
  return false if @attribute_methods_generated
74
- # Use a mutex; we don't want two thread simultaneously trying to define
81
+ # Use a mutex; we don't want two threads simultaneously trying to define
75
82
  # attribute methods.
76
83
  generated_attribute_methods.synchronize do
77
84
  return false if @attribute_methods_generated
@@ -155,16 +162,6 @@ module ActiveRecord
155
162
  end
156
163
  end
157
164
 
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
165
  # Returns +true+ if +attribute+ is an attribute method and table exists,
169
166
  # +false+ otherwise.
170
167
  #
@@ -193,24 +190,29 @@ module ActiveRecord
193
190
  []
194
191
  end
195
192
  end
196
- end
197
193
 
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)
194
+ # Returns the column object for the named attribute.
195
+ # Returns nil if the named attribute does not exist.
196
+ #
197
+ # class Person < ActiveRecord::Base
198
+ # end
199
+ #
200
+ # person = Person.new
201
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
202
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
203
+ #
204
+ # person.column_for_attribute(:nothing)
205
+ # # => nil
206
+ def column_for_attribute(name)
207
+ column = columns_hash[name.to_s]
208
+ if column.nil?
209
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
210
+ `#column_for_attribute` will return a null object for non-existent
211
+ columns in Rails 5. Use `#has_attribute?` if you need to check for
212
+ an attribute's existence.
213
+ MSG
211
214
  end
212
- else
213
- super
215
+ column
214
216
  end
215
217
  end
216
218
 
@@ -231,18 +233,14 @@ module ActiveRecord
231
233
  # person.respond_to('age?') # => true
232
234
  # person.respond_to(:nothing) # => false
233
235
  def respond_to?(name, include_private = false)
236
+ return false unless super
234
237
  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
238
 
241
239
  # If the result is true then check for the select case.
242
240
  # For queries selecting a subset of columns, return false for unselected columns.
243
241
  # We check defined?(@attributes) not to issue warnings if called on objects that
244
242
  # have been allocated but not yet initialized.
245
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
243
+ if defined?(@attributes) && self.class.column_names.include?(name)
246
244
  return has_attribute?(name)
247
245
  end
248
246
 
@@ -259,7 +257,7 @@ module ActiveRecord
259
257
  # person.has_attribute?('age') # => true
260
258
  # person.has_attribute?(:nothing) # => false
261
259
  def has_attribute?(attr_name)
262
- @attributes.has_key?(attr_name.to_s)
260
+ @attributes.key?(attr_name.to_s)
263
261
  end
264
262
 
265
263
  # Returns an array of names for the attributes available on this object.
@@ -283,22 +281,14 @@ module ActiveRecord
283
281
  # person.attributes
284
282
  # # => {"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
283
  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
284
+ @attributes.to_hash
294
285
  end
295
286
 
296
287
  # Returns an <tt>#inspect</tt>-like string for the value of the
297
- # attribute +attr_name+. String attributes are truncated upto 50
288
+ # attribute +attr_name+. String attributes are truncated up to 50
298
289
  # characters, Date and Time attributes are returned in the
299
- # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
300
- # Other attributes return the value of <tt>#inspect</tt> without
301
- # modification.
290
+ # <tt>:db</tt> format. Other attributes return the value of
291
+ # <tt>#inspect</tt> without modification.
302
292
  #
303
293
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
304
294
  #
@@ -309,7 +299,7 @@ module ActiveRecord
309
299
  # # => "\"2012-10-22 00:15:07\""
310
300
  #
311
301
  # person.attribute_for_inspect(:tag_ids)
312
- # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
302
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
313
303
  def attribute_for_inspect(attr_name)
314
304
  value = read_attribute(attr_name)
315
305
 
@@ -317,9 +307,6 @@ module ActiveRecord
317
307
  "#{value[0, 50]}...".inspect
318
308
  elsif value.is_a?(Date) || value.is_a?(Time)
319
309
  %("#{value.to_s(:db)}")
320
- elsif value.is_a?(Array) && value.size > 10
321
- inspected = value.first(10).inspect
322
- %(#{inspected[0...-1]}, ...])
323
310
  else
324
311
  value.inspect
325
312
  end
@@ -333,39 +320,24 @@ module ActiveRecord
333
320
  # class Task < ActiveRecord::Base
334
321
  # end
335
322
  #
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
323
+ # task = Task.new(title: '', is_done: false)
324
+ # task.attribute_present?(:title) # => false
325
+ # task.attribute_present?(:is_done) # => true
326
+ # task.title = 'Buy milk'
327
+ # task.is_done = true
328
+ # task.attribute_present?(:title) # => true
329
+ # task.attribute_present?(:is_done) # => true
343
330
  def attribute_present?(attribute)
344
- value = read_attribute(attribute)
331
+ value = _read_attribute(attribute)
345
332
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
346
333
  end
347
334
 
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
335
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
366
336
  # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
367
337
  # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
368
338
  #
339
+ # Note: +:id+ is always present.
340
+ #
369
341
  # Alias for the <tt>read_attribute</tt> method.
370
342
  #
371
343
  # class Person < ActiveRecord::Base
@@ -392,20 +364,13 @@ module ActiveRecord
392
364
  # person = Person.new
393
365
  # person[:age] = '22'
394
366
  # person[:age] # => 22
395
- # person[:age] # => Fixnum
367
+ # person[:age].class # => Integer
396
368
  def []=(attr_name, value)
397
369
  write_attribute(attr_name, value)
398
370
  end
399
371
 
400
372
  protected
401
373
 
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
374
  def clone_attribute_value(reader_method, attribute_name) # :nodoc:
410
375
  value = send(reader_method, attribute_name)
411
376
  value.duplicable? ? value.clone : value
@@ -423,7 +388,7 @@ module ActiveRecord
423
388
 
424
389
  def attribute_method?(attr_name) # :nodoc:
425
390
  # We check defined? because Syck calls respond_to? before actually calling initialize.
426
- defined?(@attributes) && @attributes.include?(attr_name)
391
+ defined?(@attributes) && @attributes.key?(attr_name)
427
392
  end
428
393
 
429
394
  private
@@ -442,16 +407,16 @@ module ActiveRecord
442
407
 
443
408
  # Filters the primary keys and readonly attributes from the attribute names.
444
409
  def attributes_for_update(attribute_names)
445
- attribute_names.select do |name|
446
- column_for_attribute(name) && !readonly_attribute?(name)
410
+ attribute_names.reject do |name|
411
+ readonly_attribute?(name)
447
412
  end
448
413
  end
449
414
 
450
415
  # Filters out the primary keys, from the attribute names, when the primary
451
416
  # key is to be generated (e.g. the id attribute has no value).
452
417
  def attributes_for_create(attribute_names)
453
- attribute_names.select do |name|
454
- column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
418
+ attribute_names.reject do |name|
419
+ pk_attribute?(name) && id.nil?
455
420
  end
456
421
  end
457
422
 
@@ -460,14 +425,11 @@ module ActiveRecord
460
425
  end
461
426
 
462
427
  def pk_attribute?(name)
463
- column_for_attribute(name).primary
428
+ name == self.class.primary_key
464
429
  end
465
430
 
466
431
  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
- read_attribute(name)
432
+ _read_attribute(name)
471
433
  end
472
434
  end
473
435
  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
@@ -43,7 +44,7 @@ module ActiveRecord
43
44
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
44
45
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
45
46
  def read_attribute_before_type_cast(attr_name)
46
- @attributes[attr_name.to_s]
47
+ @attributes[attr_name.to_s].value_before_type_cast
47
48
  end
48
49
 
49
50
  # Returns a hash of attributes before typecasting and deserialization.
@@ -57,7 +58,7 @@ module ActiveRecord
57
58
  # task.attributes_before_type_cast
58
59
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
59
60
  def attributes_before_type_cast
60
- @attributes
61
+ @attributes.values_before_type_cast
61
62
  end
62
63
 
63
64
  private
@@ -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
@@ -34,28 +34,57 @@ module ActiveRecord
34
34
  # <tt>reload</tt> the record and clears changed attributes.
35
35
  def reload(*)
36
36
  super.tap do
37
- reset_changes
37
+ clear_changes_information
38
38
  end
39
39
  end
40
40
 
41
- def initialize_dup(other) # :nodoc:
42
- super
43
- init_changed_attributes
44
- end
41
+ def initialize_dup(other) # :nodoc:
42
+ super
43
+ @original_raw_attributes = nil
44
+ calculate_changes_from_defaults
45
+ end
46
+
47
+ def changes_applied
48
+ super
49
+ store_original_raw_attributes
50
+ end
45
51
 
46
- private
47
- def initialize_internals_callback
52
+ def clear_changes_information
48
53
  super
49
- init_changed_attributes
54
+ original_raw_attributes.clear
55
+ end
56
+
57
+ def changed_attributes
58
+ # This should only be set by methods which will call changed_attributes
59
+ # multiple times when it is known that the computed value cannot change.
60
+ if defined?(@cached_changed_attributes)
61
+ @cached_changed_attributes
62
+ else
63
+ super.reverse_merge(attributes_changed_in_place).freeze
64
+ end
50
65
  end
51
66
 
52
- def init_changed_attributes
67
+ def changes
68
+ cache_changed_attributes do
69
+ super
70
+ end
71
+ end
72
+
73
+ def attribute_changed_in_place?(attr_name)
74
+ old_value = original_raw_attribute(attr_name)
75
+ @attributes[attr_name].changed_in_place_from?(old_value)
76
+ end
77
+
78
+ private
79
+
80
+ def changes_include?(attr_name)
81
+ super || attribute_changed_in_place?(attr_name)
82
+ end
83
+
84
+ def calculate_changes_from_defaults
53
85
  @changed_attributes = nil
54
- # Intentionally avoid using #column_defaults since overridden defaults (as is done in
55
- # optimistic locking) won't get written unless they get marked as changed
56
- self.class.columns.each do |c|
57
- attr, orig_value = c.name, c.default
58
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
86
+ self.class.column_defaults.each do |attr, orig_value|
87
+ set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
59
88
  end
60
89
  end
61
90
 
@@ -63,19 +92,36 @@ module ActiveRecord
63
92
  def write_attribute(attr, value)
64
93
  attr = attr.to_s
65
94
 
66
- save_changed_attribute(attr, value)
95
+ old_value = old_attribute_value(attr)
96
+
97
+ result = super
98
+ store_original_raw_attribute(attr)
99
+ save_changed_attribute(attr, old_value)
100
+ result
101
+ end
102
+
103
+ def raw_write_attribute(attr, value)
104
+ attr = attr.to_s
105
+
106
+ result = super
107
+ original_raw_attributes[attr] = value
108
+ result
109
+ end
67
110
 
68
- super(attr, value)
111
+ def save_changed_attribute(attr, old_value)
112
+ clear_changed_attributes_cache
113
+ if attribute_changed_by_setter?(attr)
114
+ clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
115
+ else
116
+ set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
117
+ end
69
118
  end
70
119
 
71
- def save_changed_attribute(attr, value)
72
- # The attribute already has an unsaved change.
120
+ def old_attribute_value(attr)
73
121
  if attribute_changed?(attr)
74
- old = changed_attributes[attr]
75
- changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
122
+ changed_attributes[attr]
76
123
  else
77
- old = clone_attribute_value(:read_attribute, attr)
78
- changed_attributes[attr] = old if _field_changed?(attr, old, value)
124
+ clone_attribute_value(:_read_attribute, attr)
79
125
  end
80
126
  end
81
127
 
@@ -90,37 +136,55 @@ module ActiveRecord
90
136
  # Serialized attributes should always be written in case they've been
91
137
  # changed in place.
92
138
  def keys_for_partial_write
93
- changed
139
+ changed & persistable_attribute_names
94
140
  end
95
141
 
96
- def _field_changed?(attr, old, value)
97
- if column = column_for_attribute(attr)
98
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
99
- changes_from_zero_to_string?(old, value))
100
- value = nil
101
- else
102
- value = column.type_cast(value)
103
- end
142
+ def _field_changed?(attr, old_value)
143
+ @attributes[attr].changed_from?(old_value)
144
+ end
145
+
146
+ def attributes_changed_in_place
147
+ changed_in_place.each_with_object({}) do |attr_name, h|
148
+ orig = @attributes[attr_name].original_value
149
+ h[attr_name] = orig
104
150
  end
151
+ end
105
152
 
106
- old != value
153
+ def changed_in_place
154
+ self.class.attribute_names.select do |attr_name|
155
+ attribute_changed_in_place?(attr_name)
156
+ end
107
157
  end
108
158
 
109
- def changes_from_nil_to_empty_string?(column, old, value)
110
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
111
- # Hence we don't record it as a change if the value changes from nil to ''.
112
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
113
- # be typecast back to 0 (''.to_i => 0)
114
- column.null && (old.nil? || old == 0) && value.blank?
159
+ def original_raw_attribute(attr_name)
160
+ original_raw_attributes.fetch(attr_name) do
161
+ read_attribute_before_type_cast(attr_name)
162
+ end
163
+ end
164
+
165
+ def original_raw_attributes
166
+ @original_raw_attributes ||= {}
167
+ end
168
+
169
+ def store_original_raw_attribute(attr_name)
170
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
171
+ end
172
+
173
+ def store_original_raw_attributes
174
+ attribute_names.each do |attr|
175
+ store_original_raw_attribute(attr)
176
+ end
115
177
  end
116
178
 
117
- def changes_from_zero_to_string?(old, value)
118
- # For columns with old 0 and value non-empty string
119
- old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
179
+ def cache_changed_attributes
180
+ @cached_changed_attributes = changed_attributes
181
+ yield
182
+ ensure
183
+ clear_changed_attributes_cache
120
184
  end
121
185
 
122
- def non_zero?(value)
123
- value !~ /\A0+(\.0+)?\z/
186
+ def clear_changed_attributes_cache
187
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
124
188
  end
125
189
  end
126
190
  end