activerecord 4.1.16 → 4.2.11.3

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

Potentially problematic release.


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

Files changed (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1801
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +83 -38
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +63 -27
  18. data/lib/active_record/associations/collection_proxy.rb +29 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/preloader/association.rb +14 -11
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +5 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +19 -11
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +56 -94
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +19 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -39
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +9 -11
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +55 -69
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +71 -46
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +5 -5
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +46 -26
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +18 -11
  126. data/lib/active_record/railties/databases.rake +50 -51
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +273 -114
  129. data/lib/active_record/relation/batches.rb +0 -2
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/finder_methods.rb +70 -47
  132. data/lib/active_record/relation/merger.rb +39 -29
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/query_methods.rb +114 -65
  137. data/lib/active_record/relation/spawn_methods.rb +3 -0
  138. data/lib/active_record/relation.rb +57 -25
  139. data/lib/active_record/result.rb +18 -7
  140. data/lib/active_record/sanitization.rb +12 -2
  141. data/lib/active_record/schema.rb +0 -1
  142. data/lib/active_record/schema_dumper.rb +59 -28
  143. data/lib/active_record/schema_migration.rb +5 -4
  144. data/lib/active_record/scoping/default.rb +6 -4
  145. data/lib/active_record/scoping/named.rb +4 -0
  146. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  147. data/lib/active_record/statement_cache.rb +95 -10
  148. data/lib/active_record/store.rb +5 -5
  149. data/lib/active_record/tasks/database_tasks.rb +61 -6
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  152. data/lib/active_record/timestamp.rb +9 -7
  153. data/lib/active_record/transactions.rb +53 -27
  154. data/lib/active_record/type/big_integer.rb +13 -0
  155. data/lib/active_record/type/binary.rb +50 -0
  156. data/lib/active_record/type/boolean.rb +31 -0
  157. data/lib/active_record/type/date.rb +50 -0
  158. data/lib/active_record/type/date_time.rb +54 -0
  159. data/lib/active_record/type/decimal.rb +64 -0
  160. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  161. data/lib/active_record/type/decorator.rb +14 -0
  162. data/lib/active_record/type/float.rb +19 -0
  163. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  164. data/lib/active_record/type/integer.rb +59 -0
  165. data/lib/active_record/type/mutable.rb +16 -0
  166. data/lib/active_record/type/numeric.rb +36 -0
  167. data/lib/active_record/type/serialized.rb +62 -0
  168. data/lib/active_record/type/string.rb +40 -0
  169. data/lib/active_record/type/text.rb +11 -0
  170. data/lib/active_record/type/time.rb +26 -0
  171. data/lib/active_record/type/time_value.rb +38 -0
  172. data/lib/active_record/type/type_map.rb +64 -0
  173. data/lib/active_record/type/unsigned_integer.rb +15 -0
  174. data/lib/active_record/type/value.rb +110 -0
  175. data/lib/active_record/type.rb +23 -0
  176. data/lib/active_record/validations/associated.rb +5 -3
  177. data/lib/active_record/validations/presence.rb +5 -3
  178. data/lib/active_record/validations/uniqueness.rb +25 -29
  179. data/lib/active_record/validations.rb +25 -19
  180. data/lib/active_record.rb +4 -0
  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
@@ -11,6 +11,15 @@ module ActiveRecord
11
11
  # If the passed hash responds to <tt>permitted?</tt> method and the return value
12
12
  # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
13
  # exception is raised.
14
+ #
15
+ # cat = Cat.new(name: "Gorby", status: "yawning")
16
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
17
+ # cat.assign_attributes(status: "sleeping")
18
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
19
+ #
20
+ # New attributes will be persisted in the database when the object is saved.
21
+ #
22
+ # Aliased to <tt>attributes=</tt>.
14
23
  def assign_attributes(new_attributes)
15
24
  if !new_attributes.respond_to?(:stringify_keys)
16
25
  raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
@@ -60,7 +69,7 @@ module ActiveRecord
60
69
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
61
70
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
62
71
  # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
63
- # 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
64
73
  # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
65
74
  def assign_multiparameter_attributes(pairs)
66
75
  execute_callstack_for_multiparameter_attributes(
@@ -106,7 +115,7 @@ module ActiveRecord
106
115
  end
107
116
 
108
117
  class MultiparameterAttribute #:nodoc:
109
- attr_reader :object, :name, :values, :column
118
+ attr_reader :object, :name, :values, :cast_type
110
119
 
111
120
  def initialize(object, name, values)
112
121
  @object = object
@@ -117,22 +126,22 @@ module ActiveRecord
117
126
  def read_value
118
127
  return if values.values.compact.empty?
119
128
 
120
- @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
121
- klass = column.klass
129
+ @cast_type = object.type_for_attribute(name)
130
+ klass = cast_type.klass
122
131
 
123
132
  if klass == Time
124
133
  read_time
125
134
  elsif klass == Date
126
135
  read_date
127
136
  else
128
- read_other(klass)
137
+ read_other
129
138
  end
130
139
  end
131
140
 
132
141
  private
133
142
 
134
143
  def instantiate_time_object(set_values)
135
- if object.class.send(:create_time_zone_conversion_attribute?, name, column)
144
+ if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
136
145
  Time.zone.local(*set_values)
137
146
  else
138
147
  Time.send(object.class.default_timezone, *set_values)
@@ -140,9 +149,9 @@ module ActiveRecord
140
149
  end
141
150
 
142
151
  def read_time
143
- # If column is a :time (and not :date or :timestamp) there is no need to validate if
152
+ # If column is a :time (and not :date or :datetime) there is no need to validate if
144
153
  # there are year/month/day fields
145
- if column.type == :time
154
+ if cast_type.type == :time
146
155
  # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
147
156
  { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
148
157
  values[key] ||= value
@@ -172,13 +181,12 @@ module ActiveRecord
172
181
  end
173
182
  end
174
183
 
175
- def read_other(klass)
184
+ def read_other
176
185
  max_position = extract_max_param
177
186
  positions = (1..max_position)
178
187
  validate_required_parameters!(positions)
179
188
 
180
- set_values = values.values_at(*positions)
181
- klass.new(*set_values)
189
+ values.slice(*positions)
182
190
  end
183
191
 
184
192
  # Checks whether some blank date parameter exists. Note that this is different
@@ -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
@@ -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
@@ -15,9 +15,10 @@ module ActiveRecord
15
15
 
16
16
  # Returns the primary key value.
17
17
  def id
18
- return unless self.class.primary_key
19
- sync_with_transaction_state
20
- read_attribute(self.class.primary_key)
18
+ if pk = self.class.primary_key
19
+ sync_with_transaction_state
20
+ _read_attribute(pk)
21
+ end
21
22
  end
22
23
 
23
24
  # Sets the primary key value.
@@ -88,12 +89,9 @@ module ActiveRecord
88
89
  end
89
90
 
90
91
  def get_primary_key(base_name) #:nodoc:
91
- return 'id' if base_name.blank?
92
-
93
- case primary_key_prefix_type
94
- when :table_name
92
+ if base_name && primary_key_prefix_type == :table_name
95
93
  base_name.foreign_key(false)
96
- when :table_name_with_underscore
94
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
97
95
  base_name.foreign_key
98
96
  else
99
97
  if ActiveRecord::Base != self && table_exists?
@@ -122,6 +120,7 @@ module ActiveRecord
122
120
  def primary_key=(value)
123
121
  @primary_key = value && value.to_s
124
122
  @quoted_primary_key = nil
123
+ @attributes_builder = nil
125
124
  end
126
125
  end
127
126
  end
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def query_attribute(attr_name)
11
- value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
11
+ value = self[attr_name]
12
12
 
13
13
  case value
14
14
  when true then true
@@ -22,12 +22,12 @@ module ActiveRecord
22
22
  # the attribute name. Using a constant means that we do not have
23
23
  # to allocate an object on each call to the attribute method.
24
24
  # Making it frozen means that it doesn't get duped when used to
25
- # key the @attributes_cache in read_attribute.
25
+ # key the @attributes in read_attribute.
26
26
  def method_body(method_name, const_name)
27
27
  <<-EOMETHOD
28
28
  def #{method_name}
29
29
  name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
30
- read_attribute(name) { |n| missing_attribute(n, caller) }
30
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
31
31
  end
32
32
  EOMETHOD
33
33
  end
@@ -35,35 +35,20 @@ module ActiveRecord
35
35
 
36
36
  extend ActiveSupport::Concern
37
37
 
38
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
39
-
40
- included do
41
- class_attribute :attribute_types_cached_by_default, instance_writer: false
42
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
43
- end
44
-
45
38
  module ClassMethods
46
- # +cache_attributes+ allows you to declare which converted attribute
47
- # values should be cached. Usually caching only pays off for attributes
48
- # with expensive conversion methods, like time related columns (e.g.
49
- # +created_at+, +updated_at+).
50
- def cache_attributes(*attribute_names)
51
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
39
+ [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
40
+ define_method method_name do |*|
41
+ cached_attributes_deprecation_warning(method_name)
42
+ true
43
+ end
52
44
  end
53
45
 
54
- # Returns the attributes which are cached. By default time related columns
55
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
56
- def cached_attributes
57
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
58
- end
46
+ protected
59
47
 
60
- # Returns +true+ if the provided attribute is being cached.
61
- def cache_attribute?(attr_name)
62
- cached_attributes.include?(attr_name)
48
+ def cached_attributes_deprecation_warning(method_name)
49
+ ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
63
50
  end
64
51
 
65
- protected
66
-
67
52
  if Module.methods_transplantable?
68
53
  def define_method_attribute(name)
69
54
  method = ReaderMethodCache[name]
@@ -79,7 +64,7 @@ module ActiveRecord
79
64
  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
80
65
  def #{temp_method}
81
66
  name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
82
- read_attribute(name) { |n| missing_attribute(n, caller) }
67
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
83
68
  end
84
69
  STR
85
70
 
@@ -89,51 +74,29 @@ module ActiveRecord
89
74
  end
90
75
  end
91
76
  end
92
-
93
- private
94
-
95
- def cacheable_column?(column)
96
- if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
97
- ! serialized_attributes.include? column.name
98
- else
99
- attribute_types_cached_by_default.include?(column.type)
100
- end
101
- end
102
77
  end
103
78
 
79
+ ID = 'id'.freeze
80
+
104
81
  # Returns the value of the attribute identified by <tt>attr_name</tt> after
105
82
  # it has been typecast (for example, "2004-12-12" in a date column is cast
106
83
  # to a date object, like Date.new(2004, 12, 12)).
107
- def read_attribute(attr_name)
108
- # If it's cached, just return it
109
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
84
+ def read_attribute(attr_name, &block)
110
85
  name = attr_name.to_s
111
- @attributes_cache[name] || @attributes_cache.fetch(name) {
112
- column = @column_types_override[name] if @column_types_override
113
- column ||= @column_types[name]
114
-
115
- return @attributes.fetch(name) {
116
- if name == 'id' && self.class.primary_key != name
117
- read_attribute(self.class.primary_key)
118
- end
119
- } unless column
120
-
121
- value = @attributes.fetch(name) {
122
- return block_given? ? yield(name) : nil
123
- }
86
+ name = self.class.primary_key if name == ID
87
+ _read_attribute(name, &block)
88
+ end
124
89
 
125
- if self.class.cache_attribute?(name)
126
- @attributes_cache[name] = column.type_cast(value)
127
- else
128
- column.type_cast value
129
- end
130
- }
90
+ # This method exists to avoid the expensive primary_key check internally, without
91
+ # breaking compatibility with the read_attribute API
92
+ def _read_attribute(attr_name) # :nodoc:
93
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
131
94
  end
132
95
 
133
96
  private
134
97
 
135
98
  def attribute(attribute_name)
136
- read_attribute(attribute_name)
99
+ _read_attribute(attribute_name)
137
100
  end
138
101
  end
139
102
  end