activerecord 5.0.7 → 5.1.7

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 (219) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -2080
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record/aggregations.rb +244 -244
  8. data/lib/active_record/association_relation.rb +5 -5
  9. data/lib/active_record/associations/alias_tracker.rb +10 -11
  10. data/lib/active_record/associations/association.rb +23 -5
  11. data/lib/active_record/associations/association_scope.rb +95 -81
  12. data/lib/active_record/associations/belongs_to_association.rb +7 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +30 -16
  14. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  16. data/lib/active_record/associations/collection_association.rb +36 -205
  17. data/lib/active_record/associations/collection_proxy.rb +132 -63
  18. data/lib/active_record/associations/has_many_association.rb +10 -19
  19. data/lib/active_record/associations/has_many_through_association.rb +12 -4
  20. data/lib/active_record/associations/has_one_association.rb +24 -28
  21. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  22. data/lib/active_record/associations/join_dependency/join_association.rb +4 -28
  23. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  24. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +121 -118
  26. data/lib/active_record/associations/preloader/association.rb +64 -64
  27. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  28. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  29. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  30. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  31. data/lib/active_record/associations/preloader/through_association.rb +41 -41
  32. data/lib/active_record/associations/preloader.rb +94 -94
  33. data/lib/active_record/associations/singular_association.rb +8 -25
  34. data/lib/active_record/associations/through_association.rb +2 -5
  35. data/lib/active_record/associations.rb +1591 -1562
  36. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute_assignment.rb +61 -61
  39. data/lib/active_record/attribute_decorators.rb +35 -13
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  41. data/lib/active_record/attribute_methods/dirty.rb +229 -46
  42. data/lib/active_record/attribute_methods/primary_key.rb +74 -73
  43. data/lib/active_record/attribute_methods/read.rb +39 -35
  44. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  46. data/lib/active_record/attribute_methods/write.rb +30 -33
  47. data/lib/active_record/attribute_methods.rb +56 -65
  48. data/lib/active_record/attribute_mutation_tracker.rb +63 -11
  49. data/lib/active_record/attribute_set/builder.rb +27 -33
  50. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  51. data/lib/active_record/attribute_set.rb +9 -6
  52. data/lib/active_record/attributes.rb +22 -22
  53. data/lib/active_record/autosave_association.rb +18 -13
  54. data/lib/active_record/base.rb +24 -22
  55. data/lib/active_record/callbacks.rb +56 -14
  56. data/lib/active_record/coders/yaml_column.rb +9 -11
  57. data/lib/active_record/collection_cache_key.rb +3 -4
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +330 -284
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +39 -37
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -27
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -51
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +10 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +74 -79
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +120 -100
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +49 -43
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -135
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +404 -424
  70. data/lib/active_record/connection_adapters/column.rb +26 -4
  71. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  72. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -28
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +7 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +23 -27
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +32 -53
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +19 -9
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  91. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +0 -10
  92. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +32 -30
  97. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  99. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -35
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +182 -222
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +6 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +7 -5
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +198 -167
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -19
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +32 -0
  116. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +184 -167
  117. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  118. data/lib/active_record/connection_handling.rb +14 -26
  119. data/lib/active_record/core.rb +109 -93
  120. data/lib/active_record/counter_cache.rb +60 -13
  121. data/lib/active_record/define_callbacks.rb +20 -0
  122. data/lib/active_record/dynamic_matchers.rb +80 -79
  123. data/lib/active_record/enum.rb +8 -6
  124. data/lib/active_record/errors.rb +64 -15
  125. data/lib/active_record/explain.rb +1 -2
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +7 -4
  128. data/lib/active_record/fixture_set/file.rb +11 -8
  129. data/lib/active_record/fixtures.rb +66 -53
  130. data/lib/active_record/gem_version.rb +1 -1
  131. data/lib/active_record/inheritance.rb +93 -79
  132. data/lib/active_record/integration.rb +7 -7
  133. data/lib/active_record/internal_metadata.rb +3 -16
  134. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  135. data/lib/active_record/locking/optimistic.rb +69 -74
  136. data/lib/active_record/locking/pessimistic.rb +10 -1
  137. data/lib/active_record/log_subscriber.rb +23 -28
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +100 -47
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/migration.rb +153 -155
  142. data/lib/active_record/model_schema.rb +94 -107
  143. data/lib/active_record/nested_attributes.rb +200 -199
  144. data/lib/active_record/null_relation.rb +11 -34
  145. data/lib/active_record/persistence.rb +65 -50
  146. data/lib/active_record/query_cache.rb +2 -6
  147. data/lib/active_record/querying.rb +3 -4
  148. data/lib/active_record/railtie.rb +16 -17
  149. data/lib/active_record/railties/controller_runtime.rb +6 -2
  150. data/lib/active_record/railties/databases.rake +105 -133
  151. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  152. data/lib/active_record/readonly_attributes.rb +2 -2
  153. data/lib/active_record/reflection.rb +154 -108
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  155. data/lib/active_record/relation/batches.rb +80 -51
  156. data/lib/active_record/relation/calculations.rb +169 -162
  157. data/lib/active_record/relation/delegation.rb +32 -31
  158. data/lib/active_record/relation/finder_methods.rb +197 -231
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  161. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  162. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  163. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  166. data/lib/active_record/relation/predicate_builder.rb +92 -89
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +255 -293
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +80 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/relation.rb +93 -119
  174. data/lib/active_record/result.rb +41 -32
  175. data/lib/active_record/runtime_registry.rb +3 -3
  176. data/lib/active_record/sanitization.rb +176 -192
  177. data/lib/active_record/schema.rb +3 -3
  178. data/lib/active_record/schema_dumper.rb +15 -38
  179. data/lib/active_record/schema_migration.rb +8 -4
  180. data/lib/active_record/scoping/default.rb +90 -90
  181. data/lib/active_record/scoping/named.rb +11 -11
  182. data/lib/active_record/scoping.rb +6 -6
  183. data/lib/active_record/secure_token.rb +2 -2
  184. data/lib/active_record/statement_cache.rb +13 -15
  185. data/lib/active_record/store.rb +31 -32
  186. data/lib/active_record/suppressor.rb +2 -1
  187. data/lib/active_record/table_metadata.rb +9 -5
  188. data/lib/active_record/tasks/database_tasks.rb +65 -55
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +76 -73
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +72 -47
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  192. data/lib/active_record/timestamp.rb +46 -25
  193. data/lib/active_record/touch_later.rb +1 -2
  194. data/lib/active_record/transactions.rb +97 -109
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +13 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/internal/abstract_json.rb +4 -0
  199. data/lib/active_record/type/serialized.rb +14 -8
  200. data/lib/active_record/type/text.rb +9 -0
  201. data/lib/active_record/type/time.rb +0 -1
  202. data/lib/active_record/type/type_map.rb +11 -15
  203. data/lib/active_record/type/unsigned_integer.rb +15 -0
  204. data/lib/active_record/type.rb +17 -13
  205. data/lib/active_record/type_caster/connection.rb +8 -6
  206. data/lib/active_record/type_caster/map.rb +3 -1
  207. data/lib/active_record/type_caster.rb +2 -2
  208. data/lib/active_record/validations/associated.rb +1 -1
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +8 -39
  211. data/lib/active_record/validations.rb +4 -4
  212. data/lib/active_record/version.rb +1 -1
  213. data/lib/active_record.rb +20 -20
  214. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  215. data/lib/rails/generators/active_record/migration.rb +1 -1
  216. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  217. data/lib/rails/generators/active_record.rb +4 -4
  218. metadata +24 -13
  219. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,9 +1,10 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_record/attribute_mutation_tracker'
1
+ # frozen_string_literal: true
2
+ require "active_support/core_ext/module/attribute_accessors"
3
+ require "active_record/attribute_mutation_tracker"
3
4
 
4
5
  module ActiveRecord
5
6
  module AttributeMethods
6
- module Dirty # :nodoc:
7
+ module Dirty
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  include ActiveModel::Dirty
@@ -15,6 +16,18 @@ module ActiveRecord
15
16
 
16
17
  class_attribute :partial_writes, instance_writer: false
17
18
  self.partial_writes = true
19
+
20
+ after_create { changes_internally_applied }
21
+ after_update { changes_internally_applied }
22
+
23
+ # Attribute methods for "changed in last call to save?"
24
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
25
+ attribute_method_prefix("saved_change_to_")
26
+ attribute_method_suffix("_before_last_save")
27
+
28
+ # Attribute methods for "will change if I call save?"
29
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
30
+ attribute_method_suffix("_change_to_be_saved", "_in_database")
18
31
  end
19
32
 
20
33
  # Attempts to +save+ the record and clears changed attributes if successful.
@@ -35,9 +48,9 @@ module ActiveRecord
35
48
  # <tt>reload</tt> the record and clears changed attributes.
36
49
  def reload(*)
37
50
  super.tap do
38
- @mutation_tracker = nil
39
51
  @previous_mutation_tracker = nil
40
- @changed_attributes = HashWithIndifferentAccess.new
52
+ clear_mutation_trackers
53
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
41
54
  end
42
55
  end
43
56
 
@@ -46,106 +59,276 @@ module ActiveRecord
46
59
  @attributes = self.class._default_attributes.map do |attr|
47
60
  attr.with_value_from_user(@attributes.fetch_value(attr.name))
48
61
  end
49
- @mutation_tracker = nil
62
+ clear_mutation_trackers
50
63
  end
51
64
 
52
- def changes_applied
65
+ def changes_internally_applied # :nodoc:
66
+ @mutations_before_last_save = mutations_from_database
67
+ forget_attribute_assignments
68
+ @mutations_from_database = AttributeMutationTracker.new(@attributes)
69
+ end
70
+
71
+ def changes_applied # :nodoc:
53
72
  @previous_mutation_tracker = mutation_tracker
54
- @changed_attributes = HashWithIndifferentAccess.new
55
- store_original_attributes
73
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
74
+ @mutation_tracker = nil
75
+ @mutations_from_database = nil
56
76
  end
57
77
 
58
- def clear_changes_information
78
+ def clear_changes_information # :nodoc:
59
79
  @previous_mutation_tracker = nil
60
- @changed_attributes = HashWithIndifferentAccess.new
61
- store_original_attributes
80
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
81
+ forget_attribute_assignments
82
+ clear_mutation_trackers
62
83
  end
63
84
 
64
- def raw_write_attribute(attr_name, *)
85
+ def raw_write_attribute(attr_name, *) # :nodoc:
65
86
  result = super
66
87
  clear_attribute_change(attr_name)
67
88
  result
68
89
  end
69
90
 
70
- def clear_attribute_changes(attr_names)
91
+ def clear_attribute_changes(attr_names) # :nodoc:
71
92
  super
72
93
  attr_names.each do |attr_name|
73
94
  clear_attribute_change(attr_name)
74
95
  end
75
96
  end
76
97
 
77
- def changed_attributes
98
+ def changed_attributes # :nodoc:
78
99
  # This should only be set by methods which will call changed_attributes
79
100
  # multiple times when it is known that the computed value cannot change.
80
101
  if defined?(@cached_changed_attributes)
81
102
  @cached_changed_attributes
82
103
  else
104
+ emit_warning_if_needed("changed_attributes", "saved_changes.transform_values(&:first)")
83
105
  super.reverse_merge(mutation_tracker.changed_values).freeze
84
106
  end
85
107
  end
86
108
 
87
- def changes
109
+ def changes # :nodoc:
88
110
  cache_changed_attributes do
111
+ emit_warning_if_needed("changes", "saved_changes")
89
112
  super
90
113
  end
91
114
  end
92
115
 
93
- def previous_changes
116
+ def previous_changes # :nodoc:
117
+ unless previous_mutation_tracker.equal?(mutations_before_last_save)
118
+ ActiveSupport::Deprecation.warn(<<-EOW.strip_heredoc)
119
+ The behavior of `previous_changes` inside of after callbacks is
120
+ deprecated without replacement. In the next release of Rails,
121
+ this method inside of `after_save` will return the changes that
122
+ were just saved.
123
+ EOW
124
+ end
94
125
  previous_mutation_tracker.changes
95
126
  end
96
127
 
97
- def attribute_changed_in_place?(attr_name)
128
+ def attribute_changed_in_place?(attr_name) # :nodoc:
98
129
  mutation_tracker.changed_in_place?(attr_name)
99
130
  end
100
131
 
101
- private
132
+ # Did this attribute change when we last saved? This method can be invoked
133
+ # as `saved_change_to_name?` instead of `saved_change_to_attribute?("name")`.
134
+ # Behaves similarly to +attribute_changed?+. This method is useful in
135
+ # after callbacks to determine if the call to save changed a certain
136
+ # attribute.
137
+ #
138
+ # ==== Options
139
+ #
140
+ # +from+ When passed, this method will return false unless the original
141
+ # value is equal to the given option
142
+ #
143
+ # +to+ When passed, this method will return false unless the value was
144
+ # changed to the given value
145
+ def saved_change_to_attribute?(attr_name, **options)
146
+ mutations_before_last_save.changed?(attr_name, **options)
147
+ end
102
148
 
103
- def mutation_tracker
104
- unless defined?(@mutation_tracker)
105
- @mutation_tracker = nil
106
- end
107
- @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
149
+ # Returns the change to an attribute during the last save. If the
150
+ # attribute was changed, the result will be an array containing the
151
+ # original value and the saved value.
152
+ #
153
+ # Behaves similarly to +attribute_change+. This method is useful in after
154
+ # callbacks, to see the change in an attribute that just occurred
155
+ #
156
+ # This method can be invoked as `saved_change_to_name` in instead of
157
+ # `saved_change_to_attribute("name")`
158
+ def saved_change_to_attribute(attr_name)
159
+ mutations_before_last_save.change_to_attribute(attr_name)
108
160
  end
109
161
 
110
- def changes_include?(attr_name)
111
- super || mutation_tracker.changed?(attr_name)
162
+ # Returns the original value of an attribute before the last save.
163
+ # Behaves similarly to +attribute_was+. This method is useful in after
164
+ # callbacks to get the original value of an attribute before the save that
165
+ # just occurred
166
+ def attribute_before_last_save(attr_name)
167
+ mutations_before_last_save.original_value(attr_name)
112
168
  end
113
169
 
114
- def clear_attribute_change(attr_name)
115
- mutation_tracker.forget_change(attr_name)
170
+ # Did the last call to `save` have any changes to change?
171
+ def saved_changes?
172
+ mutations_before_last_save.any_changes?
116
173
  end
117
174
 
118
- def _update_record(*)
119
- partial_writes? ? super(keys_for_partial_write) : super
175
+ # Returns a hash containing all the changes that were just saved.
176
+ def saved_changes
177
+ mutations_before_last_save.changes
120
178
  end
121
179
 
122
- def _create_record(*)
123
- partial_writes? ? super(keys_for_partial_write) : super
180
+ # Alias for `attribute_changed?`
181
+ def will_save_change_to_attribute?(attr_name, **options)
182
+ mutations_from_database.changed?(attr_name, **options)
124
183
  end
125
184
 
126
- def keys_for_partial_write
127
- changed & self.class.column_names
185
+ # Alias for `attribute_change`
186
+ def attribute_change_to_be_saved(attr_name)
187
+ mutations_from_database.change_to_attribute(attr_name)
128
188
  end
129
189
 
130
- def store_original_attributes
131
- @attributes = @attributes.map(&:forgetting_assignment)
132
- @mutation_tracker = nil
190
+ # Alias for `attribute_was`
191
+ def attribute_in_database(attr_name)
192
+ mutations_from_database.original_value(attr_name)
193
+ end
194
+
195
+ # Alias for `changed?`
196
+ def has_changes_to_save?
197
+ mutations_from_database.any_changes?
133
198
  end
134
199
 
135
- def previous_mutation_tracker
136
- @previous_mutation_tracker ||= NullMutationTracker.instance
200
+ # Alias for `changes`
201
+ def changes_to_save
202
+ mutations_from_database.changes
137
203
  end
138
204
 
139
- def cache_changed_attributes
140
- @cached_changed_attributes = changed_attributes
141
- yield
142
- ensure
143
- clear_changed_attributes_cache
205
+ # Alias for `changed`
206
+ def changed_attribute_names_to_save
207
+ mutations_from_database.changed_attribute_names
144
208
  end
145
209
 
146
- def clear_changed_attributes_cache
147
- remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
210
+ # Alias for `changed_attributes`
211
+ def attributes_in_database
212
+ mutations_from_database.changed_values
148
213
  end
214
+
215
+ def attribute_was(*)
216
+ emit_warning_if_needed("attribute_was", "attribute_before_last_save")
217
+ super
218
+ end
219
+
220
+ def attribute_change(*)
221
+ emit_warning_if_needed("attribute_change", "saved_change_to_attribute")
222
+ super
223
+ end
224
+
225
+ def attribute_changed?(*)
226
+ emit_warning_if_needed("attribute_changed?", "saved_change_to_attribute?")
227
+ super
228
+ end
229
+
230
+ def changed?(*)
231
+ emit_warning_if_needed("changed?", "saved_changes?")
232
+ super
233
+ end
234
+
235
+ def changed(*)
236
+ emit_warning_if_needed("changed", "saved_changes.keys")
237
+ super
238
+ end
239
+
240
+ private
241
+
242
+ def mutation_tracker
243
+ unless defined?(@mutation_tracker)
244
+ @mutation_tracker = nil
245
+ end
246
+ @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
247
+ end
248
+
249
+ def emit_warning_if_needed(method_name, new_method_name)
250
+ unless mutation_tracker.equal?(mutations_from_database)
251
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
252
+ The behavior of `#{method_name}` inside of after callbacks will
253
+ be changing in the next version of Rails. The new return value will reflect the
254
+ behavior of calling the method after `save` returned (e.g. the opposite of what
255
+ it returns now). To maintain the current behavior, use `#{new_method_name}`
256
+ instead.
257
+ EOW
258
+ end
259
+ end
260
+
261
+ def mutations_from_database
262
+ unless defined?(@mutations_from_database)
263
+ @mutations_from_database = nil
264
+ end
265
+ @mutations_from_database ||= mutation_tracker
266
+ end
267
+
268
+ def changes_include?(attr_name)
269
+ super || mutation_tracker.changed?(attr_name)
270
+ end
271
+
272
+ def clear_attribute_change(attr_name)
273
+ mutation_tracker.forget_change(attr_name)
274
+ mutations_from_database.forget_change(attr_name)
275
+ end
276
+
277
+ def attribute_will_change!(attr_name)
278
+ super
279
+ if self.class.has_attribute?(attr_name)
280
+ mutations_from_database.force_change(attr_name)
281
+ else
282
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
283
+ #{attr_name} is not an attribute known to Active Record.
284
+ This behavior is deprecated and will be removed in the next
285
+ version of Rails. If you'd like #{attr_name} to be managed
286
+ by Active Record, add `attribute :#{attr_name} to your class.
287
+ EOW
288
+ mutations_from_database.deprecated_force_change(attr_name)
289
+ end
290
+ end
291
+
292
+ def _update_record(*)
293
+ partial_writes? ? super(keys_for_partial_write) : super
294
+ end
295
+
296
+ def _create_record(*)
297
+ partial_writes? ? super(keys_for_partial_write) : super
298
+ end
299
+
300
+ def keys_for_partial_write
301
+ changed_attribute_names_to_save & self.class.column_names
302
+ end
303
+
304
+ def forget_attribute_assignments
305
+ @attributes = @attributes.map(&:forgetting_assignment)
306
+ end
307
+
308
+ def clear_mutation_trackers
309
+ @mutation_tracker = nil
310
+ @mutations_from_database = nil
311
+ @mutations_before_last_save = nil
312
+ end
313
+
314
+ def previous_mutation_tracker
315
+ @previous_mutation_tracker ||= NullMutationTracker.instance
316
+ end
317
+
318
+ def mutations_before_last_save
319
+ @mutations_before_last_save ||= previous_mutation_tracker
320
+ end
321
+
322
+ def cache_changed_attributes
323
+ @cached_changed_attributes = changed_attributes
324
+ yield
325
+ ensure
326
+ clear_changed_attributes_cache
327
+ end
328
+
329
+ def clear_changed_attributes_cache
330
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
331
+ end
149
332
  end
150
333
  end
151
334
  end
@@ -1,4 +1,4 @@
1
- require 'set'
1
+ require "set"
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # available.
10
10
  def to_key
11
11
  sync_with_transaction_state
12
- key = self.id
12
+ key = id
13
13
  [key] if key
14
14
  end
15
15
 
@@ -45,97 +45,98 @@ module ActiveRecord
45
45
  attribute_was(self.class.primary_key)
46
46
  end
47
47
 
48
- protected
49
-
50
- def attribute_method?(attr_name)
51
- attr_name == 'id' || super
48
+ def id_in_database
49
+ sync_with_transaction_state
50
+ attribute_in_database(self.class.primary_key)
52
51
  end
53
52
 
54
- module ClassMethods
55
- def define_method_attribute(attr_name)
56
- super
53
+ private
57
54
 
58
- if attr_name == primary_key && attr_name != 'id'
59
- generated_attribute_methods.send(:alias_method, :id, primary_key)
60
- end
55
+ def attribute_method?(attr_name)
56
+ attr_name == "id" || super
61
57
  end
62
58
 
63
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
59
+ module ClassMethods
60
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
64
61
 
65
- def dangerous_attribute_method?(method_name)
66
- super && !ID_ATTRIBUTE_METHODS.include?(method_name)
67
- end
62
+ def instance_method_already_implemented?(method_name)
63
+ super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
64
+ end
68
65
 
69
- # Defines the primary key field -- can be overridden in subclasses.
70
- # Overwriting will negate any effect of the +primary_key_prefix_type+
71
- # setting, though.
72
- def primary_key
73
- @primary_key = reset_primary_key unless defined? @primary_key
74
- @primary_key
75
- end
66
+ def dangerous_attribute_method?(method_name)
67
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
68
+ end
76
69
 
77
- # Returns a quoted version of the primary key name, used to construct
78
- # SQL statements.
79
- def quoted_primary_key
80
- @quoted_primary_key ||= connection.quote_column_name(primary_key)
81
- end
70
+ # Defines the primary key field -- can be overridden in subclasses.
71
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
72
+ # setting, though.
73
+ def primary_key
74
+ @primary_key = reset_primary_key unless defined? @primary_key
75
+ @primary_key
76
+ end
82
77
 
83
- def reset_primary_key #:nodoc:
84
- if self == base_class
85
- self.primary_key = get_primary_key(base_class.name)
86
- else
87
- self.primary_key = base_class.primary_key
78
+ # Returns a quoted version of the primary key name, used to construct
79
+ # SQL statements.
80
+ def quoted_primary_key
81
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
88
82
  end
89
- end
90
83
 
91
- def get_primary_key(base_name) #:nodoc:
92
- if base_name && primary_key_prefix_type == :table_name
93
- base_name.foreign_key(false)
94
- elsif base_name && primary_key_prefix_type == :table_name_with_underscore
95
- base_name.foreign_key
96
- else
97
- if ActiveRecord::Base != self && table_exists?
98
- pk = connection.schema_cache.primary_keys(table_name)
99
- suppress_composite_primary_key(pk)
84
+ def reset_primary_key #:nodoc:
85
+ if self == base_class
86
+ self.primary_key = get_primary_key(base_class.name)
100
87
  else
101
- 'id'
88
+ self.primary_key = base_class.primary_key
102
89
  end
103
90
  end
104
- end
105
91
 
106
- # Sets the name of the primary key column.
107
- #
108
- # class Project < ActiveRecord::Base
109
- # self.primary_key = 'sysid'
110
- # end
111
- #
112
- # You can also define the #primary_key method yourself:
113
- #
114
- # class Project < ActiveRecord::Base
115
- # def self.primary_key
116
- # 'foo_' + super
117
- # end
118
- # end
119
- #
120
- # Project.primary_key # => "foo_id"
121
- def primary_key=(value)
122
- @primary_key = value && value.to_s
123
- @quoted_primary_key = nil
124
- @attributes_builder = nil
125
- end
92
+ def get_primary_key(base_name) #:nodoc:
93
+ if base_name && primary_key_prefix_type == :table_name
94
+ base_name.foreign_key(false)
95
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
96
+ base_name.foreign_key
97
+ else
98
+ if ActiveRecord::Base != self && table_exists?
99
+ pk = connection.schema_cache.primary_keys(table_name)
100
+ suppress_composite_primary_key(pk)
101
+ else
102
+ "id"
103
+ end
104
+ end
105
+ end
126
106
 
127
- private
107
+ # Sets the name of the primary key column.
108
+ #
109
+ # class Project < ActiveRecord::Base
110
+ # self.primary_key = 'sysid'
111
+ # end
112
+ #
113
+ # You can also define the #primary_key method yourself:
114
+ #
115
+ # class Project < ActiveRecord::Base
116
+ # def self.primary_key
117
+ # 'foo_' + super
118
+ # end
119
+ # end
120
+ #
121
+ # Project.primary_key # => "foo_id"
122
+ def primary_key=(value)
123
+ @primary_key = value && value.to_s
124
+ @quoted_primary_key = nil
125
+ @attributes_builder = nil
126
+ end
127
+
128
+ private
128
129
 
129
- def suppress_composite_primary_key(pk)
130
- return pk unless pk.is_a?(Array)
130
+ def suppress_composite_primary_key(pk)
131
+ return pk unless pk.is_a?(Array)
131
132
 
132
- warn <<-WARNING.strip_heredoc
133
- WARNING: Active Record does not support composite primary key.
133
+ warn <<-WARNING.strip_heredoc
134
+ WARNING: Active Record does not support composite primary key.
134
135
 
135
- #{table_name} has composite primary key. Composite primary key is ignored.
136
- WARNING
136
+ #{table_name} has composite primary key. Composite primary key is ignored.
137
+ WARNING
138
+ end
137
139
  end
138
- end
139
140
  end
140
141
  end
141
142
  end
@@ -4,51 +4,56 @@ module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  module ClassMethods
7
- protected
7
+ private
8
8
 
9
- # We want to generate the methods via module_eval rather than
10
- # define_method, because define_method is slower on dispatch.
11
- # Evaluating many similar methods may use more memory as the instruction
12
- # sequences are duplicated and cached (in MRI). define_method may
13
- # be slower on dispatch, but if you're careful about the closure
14
- # created, then define_method will consume much less memory.
15
- #
16
- # But sometimes the database might return columns with
17
- # characters that are not allowed in normal method names (like
18
- # 'my_column(omg)'. So to work around this we first define with
19
- # the __temp__ identifier, and then use alias method to rename
20
- # it to what we want.
21
- #
22
- # We are also defining a constant to hold the frozen string of
23
- # the attribute name. Using a constant means that we do not have
24
- # to allocate an object on each call to the attribute method.
25
- # Making it frozen means that it doesn't get duped when used to
26
- # key the @attributes in read_attribute.
27
- def define_method_attribute(name)
28
- safe_name = name.unpack('h*'.freeze).first
29
- temp_method = "__temp__#{safe_name}"
9
+ # We want to generate the methods via module_eval rather than
10
+ # define_method, because define_method is slower on dispatch.
11
+ # Evaluating many similar methods may use more memory as the instruction
12
+ # sequences are duplicated and cached (in MRI). define_method may
13
+ # be slower on dispatch, but if you're careful about the closure
14
+ # created, then define_method will consume much less memory.
15
+ #
16
+ # But sometimes the database might return columns with
17
+ # characters that are not allowed in normal method names (like
18
+ # 'my_column(omg)'. So to work around this we first define with
19
+ # the __temp__ identifier, and then use alias method to rename
20
+ # it to what we want.
21
+ #
22
+ # We are also defining a constant to hold the frozen string of
23
+ # the attribute name. Using a constant means that we do not have
24
+ # to allocate an object on each call to the attribute method.
25
+ # Making it frozen means that it doesn't get duped when used to
26
+ # key the @attributes in read_attribute.
27
+ def define_method_attribute(name)
28
+ safe_name = name.unpack("h*".freeze).first
29
+ temp_method = "__temp__#{safe_name}"
30
30
 
31
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
31
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
32
32
 
33
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
34
- def #{temp_method}
35
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
36
- _read_attribute(name) { |n| missing_attribute(n, caller) }
37
- end
38
- STR
33
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
34
+ def #{temp_method}
35
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
36
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
37
+ end
38
+ STR
39
39
 
40
- generated_attribute_methods.module_eval do
41
- alias_method name, temp_method
42
- undef_method temp_method
40
+ generated_attribute_methods.module_eval do
41
+ alias_method name, temp_method
42
+ undef_method temp_method
43
+ end
43
44
  end
44
- end
45
45
  end
46
46
 
47
47
  # Returns the value of the attribute identified by <tt>attr_name</tt> after
48
48
  # it has been typecast (for example, "2004-12-12" in a date column is cast
49
49
  # to a date object, like Date.new(2004, 12, 12)).
50
50
  def read_attribute(attr_name, &block)
51
- name = attr_name.to_s
51
+ name = if self.class.attribute_alias?(attr_name)
52
+ self.class.attribute_alias(attr_name).to_s
53
+ else
54
+ attr_name.to_s
55
+ end
56
+
52
57
  name = self.class.primary_key if name == "id".freeze && self.class.primary_key
53
58
  _read_attribute(name, &block)
54
59
  end
@@ -69,7 +74,6 @@ module ActiveRecord
69
74
 
70
75
  alias :attribute :_read_attribute
71
76
  private :attribute
72
-
73
77
  end
74
78
  end
75
79
  end
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  # ==== Parameters
27
27
  #
28
28
  # * +attr_name+ - The field name that should be serialized.
29
- # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
29
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
30
30
  # or a class name that the object type should be equal to.
31
31
  #
32
32
  # ==== Example
@@ -50,12 +50,12 @@ module ActiveRecord
50
50
  # to ensure special objects (e.g. Active Record models) are dumped correctly
51
51
  # using the #as_json hook.
52
52
  coder = if class_name_or_coder == ::JSON
53
- Coders::JSON
54
- elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
55
- class_name_or_coder
56
- else
57
- Coders::YAMLColumn.new(class_name_or_coder)
58
- end
53
+ Coders::JSON
54
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
55
+ class_name_or_coder
56
+ else
57
+ Coders::YAMLColumn.new(attr_name, class_name_or_coder)
58
+ end
59
59
 
60
60
  decorate_attribute_type(attr_name, :serialize) do |type|
61
61
  Type::Serialized.new(type, coder)