activerecord 5.2.6 → 6.0.0

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +609 -622
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +52 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/association.rb +14 -18
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  13. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  15. data/lib/active_record/associations/builder/has_many.rb +2 -0
  16. data/lib/active_record/associations/builder/has_one.rb +35 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  18. data/lib/active_record/associations/collection_association.rb +6 -21
  19. data/lib/active_record/associations/collection_proxy.rb +12 -15
  20. data/lib/active_record/associations/foreign_association.rb +7 -0
  21. data/lib/active_record/associations/has_many_association.rb +2 -10
  22. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  23. data/lib/active_record/associations/has_one_association.rb +28 -30
  24. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  25. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  26. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  27. data/lib/active_record/associations/join_dependency.rb +24 -28
  28. data/lib/active_record/associations/preloader/association.rb +38 -36
  29. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  30. data/lib/active_record/associations/preloader.rb +40 -32
  31. data/lib/active_record/associations/singular_association.rb +2 -16
  32. data/lib/active_record/associations.rb +19 -14
  33. data/lib/active_record/attribute_assignment.rb +7 -10
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  35. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  36. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  37. data/lib/active_record/attribute_methods/query.rb +2 -3
  38. data/lib/active_record/attribute_methods/read.rb +15 -53
  39. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  41. data/lib/active_record/attribute_methods/write.rb +17 -24
  42. data/lib/active_record/attribute_methods.rb +28 -100
  43. data/lib/active_record/attributes.rb +13 -0
  44. data/lib/active_record/autosave_association.rb +5 -9
  45. data/lib/active_record/base.rb +2 -3
  46. data/lib/active_record/callbacks.rb +5 -19
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +94 -16
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +95 -123
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +180 -47
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +128 -194
  59. data/lib/active_record/connection_adapters/column.rb +17 -13
  60. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  61. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +73 -13
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  68. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -1
  72. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  79. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  80. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  81. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  85. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +160 -74
  87. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  88. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -141
  93. data/lib/active_record/connection_handling.rb +149 -27
  94. data/lib/active_record/core.rb +100 -60
  95. data/lib/active_record/counter_cache.rb +4 -29
  96. data/lib/active_record/database_configurations/database_config.rb +37 -0
  97. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  98. data/lib/active_record/database_configurations/url_config.rb +79 -0
  99. data/lib/active_record/database_configurations.rb +233 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/enum.rb +37 -7
  102. data/lib/active_record/errors.rb +15 -7
  103. data/lib/active_record/explain.rb +1 -1
  104. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  105. data/lib/active_record/fixture_set/render_context.rb +17 -0
  106. data/lib/active_record/fixture_set/table_row.rb +153 -0
  107. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  108. data/lib/active_record/fixtures.rb +145 -472
  109. data/lib/active_record/gem_version.rb +3 -3
  110. data/lib/active_record/inheritance.rb +13 -3
  111. data/lib/active_record/insert_all.rb +179 -0
  112. data/lib/active_record/integration.rb +68 -16
  113. data/lib/active_record/internal_metadata.rb +10 -2
  114. data/lib/active_record/locking/optimistic.rb +5 -6
  115. data/lib/active_record/locking/pessimistic.rb +3 -3
  116. data/lib/active_record/log_subscriber.rb +7 -26
  117. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  118. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/migration/command_recorder.rb +50 -6
  121. data/lib/active_record/migration/compatibility.rb +76 -49
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/model_schema.rb +30 -9
  124. data/lib/active_record/nested_attributes.rb +2 -2
  125. data/lib/active_record/no_touching.rb +7 -0
  126. data/lib/active_record/persistence.rb +228 -24
  127. data/lib/active_record/query_cache.rb +11 -4
  128. data/lib/active_record/querying.rb +32 -20
  129. data/lib/active_record/railtie.rb +80 -43
  130. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  131. data/lib/active_record/railties/controller_runtime.rb +30 -35
  132. data/lib/active_record/railties/databases.rake +196 -46
  133. data/lib/active_record/reflection.rb +32 -30
  134. data/lib/active_record/relation/batches.rb +13 -10
  135. data/lib/active_record/relation/calculations.rb +53 -47
  136. data/lib/active_record/relation/delegation.rb +26 -43
  137. data/lib/active_record/relation/finder_methods.rb +13 -26
  138. data/lib/active_record/relation/merger.rb +11 -20
  139. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  140. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  141. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  142. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  143. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  144. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  145. data/lib/active_record/relation/predicate_builder.rb +4 -6
  146. data/lib/active_record/relation/query_attribute.rb +13 -8
  147. data/lib/active_record/relation/query_methods.rb +189 -63
  148. data/lib/active_record/relation/spawn_methods.rb +1 -1
  149. data/lib/active_record/relation/where_clause.rb +14 -10
  150. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  151. data/lib/active_record/relation.rb +310 -80
  152. data/lib/active_record/result.rb +30 -11
  153. data/lib/active_record/sanitization.rb +32 -40
  154. data/lib/active_record/schema.rb +2 -11
  155. data/lib/active_record/schema_dumper.rb +22 -7
  156. data/lib/active_record/schema_migration.rb +5 -1
  157. data/lib/active_record/scoping/default.rb +4 -5
  158. data/lib/active_record/scoping/named.rb +19 -15
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/statement_cache.rb +30 -3
  161. data/lib/active_record/store.rb +87 -8
  162. data/lib/active_record/table_metadata.rb +10 -17
  163. data/lib/active_record/tasks/database_tasks.rb +194 -25
  164. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  165. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  166. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  167. data/lib/active_record/test_databases.rb +23 -0
  168. data/lib/active_record/test_fixtures.rb +224 -0
  169. data/lib/active_record/timestamp.rb +39 -25
  170. data/lib/active_record/touch_later.rb +4 -2
  171. data/lib/active_record/transactions.rb +57 -66
  172. data/lib/active_record/translation.rb +1 -1
  173. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  174. data/lib/active_record/type.rb +3 -4
  175. data/lib/active_record/type_caster/connection.rb +15 -14
  176. data/lib/active_record/type_caster/map.rb +1 -4
  177. data/lib/active_record/validations/uniqueness.rb +15 -27
  178. data/lib/active_record/validations.rb +1 -0
  179. data/lib/active_record.rb +9 -2
  180. data/lib/arel/alias_predication.rb +9 -0
  181. data/lib/arel/attributes/attribute.rb +37 -0
  182. data/lib/arel/attributes.rb +22 -0
  183. data/lib/arel/collectors/bind.rb +24 -0
  184. data/lib/arel/collectors/composite.rb +31 -0
  185. data/lib/arel/collectors/plain_string.rb +20 -0
  186. data/lib/arel/collectors/sql_string.rb +20 -0
  187. data/lib/arel/collectors/substitute_binds.rb +28 -0
  188. data/lib/arel/crud.rb +42 -0
  189. data/lib/arel/delete_manager.rb +18 -0
  190. data/lib/arel/errors.rb +9 -0
  191. data/lib/arel/expressions.rb +29 -0
  192. data/lib/arel/factory_methods.rb +49 -0
  193. data/lib/arel/insert_manager.rb +49 -0
  194. data/lib/arel/math.rb +45 -0
  195. data/lib/arel/nodes/and.rb +32 -0
  196. data/lib/arel/nodes/ascending.rb +23 -0
  197. data/lib/arel/nodes/binary.rb +52 -0
  198. data/lib/arel/nodes/bind_param.rb +36 -0
  199. data/lib/arel/nodes/case.rb +55 -0
  200. data/lib/arel/nodes/casted.rb +50 -0
  201. data/lib/arel/nodes/comment.rb +29 -0
  202. data/lib/arel/nodes/count.rb +12 -0
  203. data/lib/arel/nodes/delete_statement.rb +45 -0
  204. data/lib/arel/nodes/descending.rb +23 -0
  205. data/lib/arel/nodes/equality.rb +18 -0
  206. data/lib/arel/nodes/extract.rb +24 -0
  207. data/lib/arel/nodes/false.rb +16 -0
  208. data/lib/arel/nodes/full_outer_join.rb +8 -0
  209. data/lib/arel/nodes/function.rb +44 -0
  210. data/lib/arel/nodes/grouping.rb +8 -0
  211. data/lib/arel/nodes/in.rb +8 -0
  212. data/lib/arel/nodes/infix_operation.rb +80 -0
  213. data/lib/arel/nodes/inner_join.rb +8 -0
  214. data/lib/arel/nodes/insert_statement.rb +37 -0
  215. data/lib/arel/nodes/join_source.rb +20 -0
  216. data/lib/arel/nodes/matches.rb +18 -0
  217. data/lib/arel/nodes/named_function.rb +23 -0
  218. data/lib/arel/nodes/node.rb +50 -0
  219. data/lib/arel/nodes/node_expression.rb +13 -0
  220. data/lib/arel/nodes/outer_join.rb +8 -0
  221. data/lib/arel/nodes/over.rb +15 -0
  222. data/lib/arel/nodes/regexp.rb +16 -0
  223. data/lib/arel/nodes/right_outer_join.rb +8 -0
  224. data/lib/arel/nodes/select_core.rb +67 -0
  225. data/lib/arel/nodes/select_statement.rb +41 -0
  226. data/lib/arel/nodes/sql_literal.rb +16 -0
  227. data/lib/arel/nodes/string_join.rb +11 -0
  228. data/lib/arel/nodes/table_alias.rb +27 -0
  229. data/lib/arel/nodes/terminal.rb +16 -0
  230. data/lib/arel/nodes/true.rb +16 -0
  231. data/lib/arel/nodes/unary.rb +45 -0
  232. data/lib/arel/nodes/unary_operation.rb +20 -0
  233. data/lib/arel/nodes/unqualified_column.rb +22 -0
  234. data/lib/arel/nodes/update_statement.rb +41 -0
  235. data/lib/arel/nodes/values_list.rb +9 -0
  236. data/lib/arel/nodes/window.rb +126 -0
  237. data/lib/arel/nodes/with.rb +11 -0
  238. data/lib/arel/nodes.rb +68 -0
  239. data/lib/arel/order_predications.rb +13 -0
  240. data/lib/arel/predications.rb +257 -0
  241. data/lib/arel/select_manager.rb +271 -0
  242. data/lib/arel/table.rb +110 -0
  243. data/lib/arel/tree_manager.rb +72 -0
  244. data/lib/arel/update_manager.rb +34 -0
  245. data/lib/arel/visitors/depth_first.rb +204 -0
  246. data/lib/arel/visitors/dot.rb +297 -0
  247. data/lib/arel/visitors/ibm_db.rb +34 -0
  248. data/lib/arel/visitors/informix.rb +62 -0
  249. data/lib/arel/visitors/mssql.rb +157 -0
  250. data/lib/arel/visitors/mysql.rb +83 -0
  251. data/lib/arel/visitors/oracle.rb +159 -0
  252. data/lib/arel/visitors/oracle12.rb +66 -0
  253. data/lib/arel/visitors/postgresql.rb +110 -0
  254. data/lib/arel/visitors/sqlite.rb +39 -0
  255. data/lib/arel/visitors/to_sql.rb +889 -0
  256. data/lib/arel/visitors/visitor.rb +46 -0
  257. data/lib/arel/visitors/where_sql.rb +23 -0
  258. data/lib/arel/visitors.rb +20 -0
  259. data/lib/arel/window_predications.rb +9 -0
  260. data/lib/arel.rb +51 -0
  261. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  262. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  263. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  264. data/lib/rails/generators/active_record/migration.rb +14 -1
  265. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  266. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  267. metadata +108 -26
  268. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -29,18 +29,17 @@ module ActiveRecord
29
29
  # <tt>reload</tt> the record and clears changed attributes.
30
30
  def reload(*)
31
31
  super.tap do
32
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
33
32
  @mutations_before_last_save = nil
34
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
35
33
  @mutations_from_database = nil
36
34
  end
37
35
  end
38
36
 
39
- # Did this attribute change when we last saved? This method can be invoked
40
- # as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
41
- # Behaves similarly to +attribute_changed?+. This method is useful in
42
- # after callbacks to determine if the call to save changed a certain
43
- # attribute.
37
+ # Did this attribute change when we last saved?
38
+ #
39
+ # This method is useful in after callbacks to determine if an attribute
40
+ # was changed during the save that triggered the callbacks to run. It can
41
+ # be invoked as +saved_change_to_name?+ instead of
42
+ # <tt>saved_change_to_attribute?("name")</tt>.
44
43
  #
45
44
  # ==== Options
46
45
  #
@@ -50,28 +49,29 @@ module ActiveRecord
50
49
  # +to+ When passed, this method will return false unless the value was
51
50
  # changed to the given value
52
51
  def saved_change_to_attribute?(attr_name, **options)
53
- mutations_before_last_save.changed?(attr_name, **options)
52
+ mutations_before_last_save.changed?(attr_name.to_s, options)
54
53
  end
55
54
 
56
55
  # Returns the change to an attribute during the last save. If the
57
56
  # attribute was changed, the result will be an array containing the
58
57
  # original value and the saved value.
59
58
  #
60
- # Behaves similarly to +attribute_change+. This method is useful in after
61
- # callbacks, to see the change in an attribute that just occurred
62
- #
63
- # This method can be invoked as +saved_change_to_name+ in instead of
64
- # <tt>saved_change_to_attribute("name")</tt>
59
+ # This method is useful in after callbacks, to see the change in an
60
+ # attribute during the save that triggered the callbacks to run. It can be
61
+ # invoked as +saved_change_to_name+ instead of
62
+ # <tt>saved_change_to_attribute("name")</tt>.
65
63
  def saved_change_to_attribute(attr_name)
66
- mutations_before_last_save.change_to_attribute(attr_name)
64
+ mutations_before_last_save.change_to_attribute(attr_name.to_s)
67
65
  end
68
66
 
69
67
  # Returns the original value of an attribute before the last save.
70
- # Behaves similarly to +attribute_was+. This method is useful in after
71
- # callbacks to get the original value of an attribute before the save that
72
- # just occurred
68
+ #
69
+ # This method is useful in after callbacks to get the original value of an
70
+ # attribute before the save that triggered the callbacks to run. It can be
71
+ # invoked as +name_before_last_save+ instead of
72
+ # <tt>attribute_before_last_save("name")</tt>.
73
73
  def attribute_before_last_save(attr_name)
74
- mutations_before_last_save.original_value(attr_name)
74
+ mutations_before_last_save.original_value(attr_name.to_s)
75
75
  end
76
76
 
77
77
  # Did the last call to +save+ have any changes to change?
@@ -84,66 +84,137 @@ module ActiveRecord
84
84
  mutations_before_last_save.changes
85
85
  end
86
86
 
87
- # Alias for +attribute_changed?+
87
+ # Will this attribute change the next time we save?
88
+ #
89
+ # This method is useful in validations and before callbacks to determine
90
+ # if the next call to +save+ will change a particular attribute. It can be
91
+ # invoked as +will_save_change_to_name?+ instead of
92
+ # <tt>will_save_change_to_attribute("name")</tt>.
93
+ #
94
+ # ==== Options
95
+ #
96
+ # +from+ When passed, this method will return false unless the original
97
+ # value is equal to the given option
98
+ #
99
+ # +to+ When passed, this method will return false unless the value will be
100
+ # changed to the given value
88
101
  def will_save_change_to_attribute?(attr_name, **options)
89
- mutations_from_database.changed?(attr_name, **options)
102
+ mutations_from_database.changed?(attr_name.to_s, options)
90
103
  end
91
104
 
92
- # Alias for +attribute_change+
105
+ # Returns the change to an attribute that will be persisted during the
106
+ # next save.
107
+ #
108
+ # This method is useful in validations and before callbacks, to see the
109
+ # change to an attribute that will occur when the record is saved. It can
110
+ # be invoked as +name_change_to_be_saved+ instead of
111
+ # <tt>attribute_change_to_be_saved("name")</tt>.
112
+ #
113
+ # If the attribute will change, the result will be an array containing the
114
+ # original value and the new value about to be saved.
93
115
  def attribute_change_to_be_saved(attr_name)
94
- mutations_from_database.change_to_attribute(attr_name)
116
+ mutations_from_database.change_to_attribute(attr_name.to_s)
95
117
  end
96
118
 
97
- # Alias for +attribute_was+
119
+ # Returns the value of an attribute in the database, as opposed to the
120
+ # in-memory value that will be persisted the next time the record is
121
+ # saved.
122
+ #
123
+ # This method is useful in validations and before callbacks, to see the
124
+ # original value of an attribute prior to any changes about to be
125
+ # saved. It can be invoked as +name_in_database+ instead of
126
+ # <tt>attribute_in_database("name")</tt>.
98
127
  def attribute_in_database(attr_name)
99
- mutations_from_database.original_value(attr_name)
128
+ mutations_from_database.original_value(attr_name.to_s)
100
129
  end
101
130
 
102
- # Alias for +changed?+
131
+ # Will the next call to +save+ have any changes to persist?
103
132
  def has_changes_to_save?
104
133
  mutations_from_database.any_changes?
105
134
  end
106
135
 
107
- # Alias for +changes+
136
+ # Returns a hash containing all the changes that will be persisted during
137
+ # the next save.
108
138
  def changes_to_save
109
139
  mutations_from_database.changes
110
140
  end
111
141
 
112
- # Alias for +changed+
142
+ # Returns an array of the names of any attributes that will change when
143
+ # the record is next saved.
113
144
  def changed_attribute_names_to_save
114
145
  mutations_from_database.changed_attribute_names
115
146
  end
116
147
 
117
- # Alias for +changed_attributes+
148
+ # Returns a hash of the attributes that will change when the record is
149
+ # next saved.
150
+ #
151
+ # The hash keys are the attribute names, and the hash values are the
152
+ # original attribute values in the database (as opposed to the in-memory
153
+ # values about to be saved).
118
154
  def attributes_in_database
119
155
  mutations_from_database.changed_values
120
156
  end
121
157
 
122
158
  private
159
+ def mutations_from_database
160
+ sync_with_transaction_state if @transaction_state&.finalized?
161
+ super
162
+ end
163
+
164
+ def mutations_before_last_save
165
+ sync_with_transaction_state if @transaction_state&.finalized?
166
+ super
167
+ end
168
+
123
169
  def write_attribute_without_type_cast(attr_name, value)
124
- name = attr_name.to_s
125
- if self.class.attribute_alias?(name)
126
- name = self.class.attribute_alias(name)
127
- end
128
- result = super(name, value)
129
- clear_attribute_change(name)
170
+ result = super
171
+ clear_attribute_change(attr_name)
130
172
  result
131
173
  end
132
174
 
133
- def _update_record(*)
134
- affected_rows = partial_writes? ? super(keys_for_partial_write) : super
175
+ def _touch_row(attribute_names, time)
176
+ @_touch_attr_names = Set.new(attribute_names)
177
+
178
+ affected_rows = super
179
+
180
+ if @_skip_dirty_tracking ||= false
181
+ clear_attribute_changes(@_touch_attr_names)
182
+ return affected_rows
183
+ end
184
+
185
+ changes = {}
186
+ @attributes.keys.each do |attr_name|
187
+ next if @_touch_attr_names.include?(attr_name)
188
+
189
+ if attribute_changed?(attr_name)
190
+ changes[attr_name] = _read_attribute(attr_name)
191
+ _write_attribute(attr_name, attribute_was(attr_name))
192
+ clear_attribute_change(attr_name)
193
+ end
194
+ end
195
+
196
+ changes_applied
197
+ changes.each { |attr_name, value| _write_attribute(attr_name, value) }
198
+
199
+ affected_rows
200
+ ensure
201
+ @_touch_attr_names, @_skip_dirty_tracking = nil, nil
202
+ end
203
+
204
+ def _update_record(attribute_names = attribute_names_for_partial_writes)
205
+ affected_rows = super
135
206
  changes_applied
136
207
  affected_rows
137
208
  end
138
209
 
139
- def _create_record(*)
140
- id = partial_writes? ? super(keys_for_partial_write) : super
210
+ def _create_record(attribute_names = attribute_names_for_partial_writes)
211
+ id = super
141
212
  changes_applied
142
213
  id
143
214
  end
144
215
 
145
- def keys_for_partial_write
146
- changed_attribute_names_to_save & self.class.column_names
216
+ def attribute_names_for_partial_writes
217
+ partial_writes? ? changed_attribute_names_to_save : attribute_names
147
218
  end
148
219
  end
149
220
  end
@@ -14,41 +14,34 @@ module ActiveRecord
14
14
  [key] if key
15
15
  end
16
16
 
17
- # Returns the primary key value.
17
+ # Returns the primary key column's value.
18
18
  def id
19
- sync_with_transaction_state
20
- primary_key = self.class.primary_key
21
- _read_attribute(primary_key) if primary_key
19
+ _read_attribute(@primary_key)
22
20
  end
23
21
 
24
- # Sets the primary key value.
22
+ # Sets the primary key column's value.
25
23
  def id=(value)
26
- sync_with_transaction_state
27
- primary_key = self.class.primary_key
28
- _write_attribute(primary_key, value) if primary_key
24
+ _write_attribute(@primary_key, value)
29
25
  end
30
26
 
31
- # Queries the primary key value.
27
+ # Queries the primary key column's value.
32
28
  def id?
33
- sync_with_transaction_state
34
- query_attribute(self.class.primary_key)
29
+ query_attribute(@primary_key)
35
30
  end
36
31
 
37
- # Returns the primary key value before type cast.
32
+ # Returns the primary key column's value before type cast.
38
33
  def id_before_type_cast
39
- sync_with_transaction_state
40
- read_attribute_before_type_cast(self.class.primary_key)
34
+ read_attribute_before_type_cast(@primary_key)
41
35
  end
42
36
 
43
- # Returns the primary key previous value.
37
+ # Returns the primary key column's previous value.
44
38
  def id_was
45
- sync_with_transaction_state
46
- attribute_was(self.class.primary_key)
39
+ attribute_was(@primary_key)
47
40
  end
48
41
 
42
+ # Returns the primary key column's value from the database.
49
43
  def id_in_database
50
- sync_with_transaction_state
51
- attribute_in_database(self.class.primary_key)
44
+ attribute_in_database(@primary_key)
52
45
  end
53
46
 
54
47
  private
@@ -83,7 +76,7 @@ module ActiveRecord
83
76
  end
84
77
 
85
78
  def reset_primary_key #:nodoc:
86
- if self == base_class
79
+ if base_class?
87
80
  self.primary_key = get_primary_key(base_class.name)
88
81
  else
89
82
  self.primary_key = base_class.primary_key
@@ -121,7 +114,7 @@ module ActiveRecord
121
114
  #
122
115
  # Project.primary_key # => "foo_id"
123
116
  def primary_key=(value)
124
- @primary_key = value && value.to_s
117
+ @primary_key = value && -value.to_s
125
118
  @quoted_primary_key = nil
126
119
  @attributes_builder = nil
127
120
  end
@@ -131,7 +124,7 @@ module ActiveRecord
131
124
  def suppress_composite_primary_key(pk)
132
125
  return pk unless pk.is_a?(Array)
133
126
 
134
- warn <<-WARNING.strip_heredoc
127
+ warn <<~WARNING
135
128
  WARNING: Active Record does not support composite primary key.
136
129
 
137
130
  #{table_name} has composite primary key. Composite primary key is ignored.
@@ -16,8 +16,7 @@ module ActiveRecord
16
16
  when true then true
17
17
  when false, nil then false
18
18
  else
19
- column = self.class.columns_hash[attr_name]
20
- if column.nil?
19
+ if !type_for_attribute(attr_name) { false }
21
20
  if Numeric === value || value !~ /[^0-9]/
22
21
  !value.to_i.zero?
23
22
  else
@@ -33,7 +32,7 @@ module ActiveRecord
33
32
  end
34
33
 
35
34
  private
36
- # Handle *? for method_missing.
35
+ # Dispatch target for <tt>*?</tt> attribute methods.
37
36
  def attribute?(attribute_name)
38
37
  query_attribute(attribute_name)
39
38
  end
@@ -8,42 +8,16 @@ module ActiveRecord
8
8
  module ClassMethods # :nodoc:
9
9
  private
10
10
 
11
- # We want to generate the methods via module_eval rather than
12
- # define_method, because define_method is slower on dispatch.
13
- # Evaluating many similar methods may use more memory as the instruction
14
- # sequences are duplicated and cached (in MRI). define_method may
15
- # be slower on dispatch, but if you're careful about the closure
16
- # created, then define_method will consume much less memory.
17
- #
18
- # But sometimes the database might return columns with
19
- # characters that are not allowed in normal method names (like
20
- # 'my_column(omg)'. So to work around this we first define with
21
- # the __temp__ identifier, and then use alias method to rename
22
- # it to what we want.
23
- #
24
- # We are also defining a constant to hold the frozen string of
25
- # the attribute name. Using a constant means that we do not have
26
- # to allocate an object on each call to the attribute method.
27
- # Making it frozen means that it doesn't get duped when used to
28
- # key the @attributes in read_attribute.
29
11
  def define_method_attribute(name)
30
- safe_name = name.unpack("h*".freeze).first
31
- temp_method = "__temp__#{safe_name}"
32
-
33
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
34
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
35
-
36
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
37
- def #{temp_method}
38
- #{sync_with_transaction_state}
39
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
40
- _read_attribute(name) { |n| missing_attribute(n, caller) }
41
- end
42
- STR
43
-
44
- generated_attribute_methods.module_eval do
45
- alias_method name, temp_method
46
- undef_method temp_method
12
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
13
+ generated_attribute_methods, name
14
+ ) do |temp_method_name, attr_name_expr|
15
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
16
+ def #{temp_method_name}
17
+ name = #{attr_name_expr}
18
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
19
+ end
20
+ RUBY
47
21
  end
48
22
  end
49
23
  end
@@ -52,30 +26,18 @@ module ActiveRecord
52
26
  # it has been typecast (for example, "2004-12-12" in a date column is cast
53
27
  # to a date object, like Date.new(2004, 12, 12)).
54
28
  def read_attribute(attr_name, &block)
55
- name = if self.class.attribute_alias?(attr_name)
56
- self.class.attribute_alias(attr_name).to_s
57
- else
58
- attr_name.to_s
59
- end
29
+ name = attr_name.to_s
30
+ name = self.class.attribute_aliases[name] || name
60
31
 
61
- primary_key = self.class.primary_key
62
- name = primary_key if name == "id".freeze && primary_key
63
- sync_with_transaction_state if name == primary_key
32
+ name = @primary_key if name == "id" && @primary_key
64
33
  _read_attribute(name, &block)
65
34
  end
66
35
 
67
36
  # This method exists to avoid the expensive primary_key check internally, without
68
37
  # breaking compatibility with the read_attribute API
69
- if defined?(JRUBY_VERSION)
70
- # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
71
- # https://github.com/jruby/jruby/pull/2562
72
- def _read_attribute(attr_name, &block) # :nodoc:
73
- @attributes.fetch_value(attr_name.to_s, &block)
74
- end
75
- else
76
- def _read_attribute(attr_name) # :nodoc:
77
- @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
78
- end
38
+ def _read_attribute(attr_name, &block) # :nodoc
39
+ sync_with_transaction_state if @transaction_state&.finalized?
40
+ @attributes.fetch_value(attr_name.to_s, &block)
79
41
  end
80
42
 
81
43
  alias :attribute :_read_attribute
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
 
8
8
  class ColumnNotSerializableError < StandardError
9
9
  def initialize(name, type)
10
- super <<-EOS.strip_heredoc
10
+ super <<~EOS
11
11
  Column `#{name}` of type #{type.class} does not support `serialize` feature.
12
12
  Usually it means that you are trying to use `serialize`
13
13
  on a column that already implements serialization natively.
@@ -73,7 +73,7 @@ module ActiveRecord
73
73
  # `skip_time_zone_conversion_for_attributes` would not be picked up.
74
74
  subclass.class_eval do
75
75
  matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
76
- decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
76
+ decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
77
77
  TimeZoneConverter.new(type)
78
78
  end
79
79
  end
@@ -13,19 +13,16 @@ module ActiveRecord
13
13
  private
14
14
 
15
15
  def define_method_attribute=(name)
16
- safe_name = name.unpack("h*".freeze).first
17
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
18
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
19
-
20
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
21
- def __temp__#{safe_name}=(value)
22
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
23
- #{sync_with_transaction_state}
24
- _write_attribute(name, value)
25
- end
26
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
27
- undef_method :__temp__#{safe_name}=
28
- STR
16
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
17
+ generated_attribute_methods, name, writer: true,
18
+ ) do |temp_method_name, attr_name_expr|
19
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ def #{temp_method_name}(value)
21
+ name = #{attr_name_expr}
22
+ _write_attribute(name, value)
23
+ end
24
+ RUBY
25
+ end
29
26
  end
30
27
  end
31
28
 
@@ -33,33 +30,29 @@ module ActiveRecord
33
30
  # specified +value+. Empty strings for Integer and Float columns are
34
31
  # turned into +nil+.
35
32
  def write_attribute(attr_name, value)
36
- name = if self.class.attribute_alias?(attr_name)
37
- self.class.attribute_alias(attr_name).to_s
38
- else
39
- attr_name.to_s
40
- end
33
+ name = attr_name.to_s
34
+ name = self.class.attribute_aliases[name] || name
41
35
 
42
- primary_key = self.class.primary_key
43
- name = primary_key if name == "id".freeze && primary_key
44
- sync_with_transaction_state if name == primary_key
36
+ name = @primary_key if name == "id" && @primary_key
45
37
  _write_attribute(name, value)
46
38
  end
47
39
 
48
40
  # This method exists to avoid the expensive primary_key check internally, without
49
41
  # breaking compatibility with the write_attribute API
50
42
  def _write_attribute(attr_name, value) # :nodoc:
43
+ sync_with_transaction_state if @transaction_state&.finalized?
51
44
  @attributes.write_from_user(attr_name.to_s, value)
52
45
  value
53
46
  end
54
47
 
55
48
  private
56
49
  def write_attribute_without_type_cast(attr_name, value)
57
- name = attr_name.to_s
58
- @attributes.write_cast_value(name, value)
50
+ sync_with_transaction_state if @transaction_state&.finalized?
51
+ @attributes.write_cast_value(attr_name.to_s, value)
59
52
  value
60
53
  end
61
54
 
62
- # Handle *= for method_missing.
55
+ # Dispatch target for <tt>*=</tt> attribute methods.
63
56
  def attribute=(attribute_name, value)
64
57
  _write_attribute(attribute_name, value)
65
58
  end