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
@@ -5,10 +5,11 @@ module ActiveRecord
5
5
  class Relation
6
6
  MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
7
7
  :order, :joins, :left_outer_joins, :references,
8
- :extending, :unscope]
8
+ :extending, :unscope, :optimizer_hints, :annotate]
9
9
 
10
10
  SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
11
11
  :reverse_order, :distinct, :create_with, :skip_query_cache]
12
+
12
13
  CLAUSE_METHODS = [:where, :having, :from]
13
14
  INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
14
15
 
@@ -18,6 +19,7 @@ module ActiveRecord
18
19
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
19
20
 
20
21
  attr_reader :table, :klass, :loaded, :predicate_builder
22
+ attr_accessor :skip_preloading_value
21
23
  alias :model :klass
22
24
  alias :loaded? :loaded
23
25
  alias :locked? :lock_value
@@ -41,6 +43,17 @@ module ActiveRecord
41
43
  klass.arel_attribute(name, table)
42
44
  end
43
45
 
46
+ def bind_attribute(name, value) # :nodoc:
47
+ if reflection = klass._reflect_on_association(name)
48
+ name = reflection.foreign_key
49
+ value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
50
+ end
51
+
52
+ attr = arel_attribute(name)
53
+ bind = predicate_builder.build_bind_attribute(attr.name, value)
54
+ yield attr, bind
55
+ end
56
+
44
57
  # Initializes new record from relation while maintaining the current
45
58
  # scope.
46
59
  #
@@ -54,7 +67,8 @@ module ActiveRecord
54
67
  # user = users.new { |user| user.name = 'Oscar' }
55
68
  # user.name # => Oscar
56
69
  def new(attributes = nil, &block)
57
- scoping { klass.new(values_for_create(attributes), &block) }
70
+ block = _deprecated_scope_block("new", &block)
71
+ scoping { klass.new(attributes, &block) }
58
72
  end
59
73
 
60
74
  alias build new
@@ -82,7 +96,8 @@ module ActiveRecord
82
96
  if attributes.is_a?(Array)
83
97
  attributes.collect { |attr| create(attr, &block) }
84
98
  else
85
- scoping { klass.create(values_for_create(attributes), &block) }
99
+ block = _deprecated_scope_block("create", &block)
100
+ scoping { klass.create(attributes, &block) }
86
101
  end
87
102
  end
88
103
 
@@ -96,7 +111,8 @@ module ActiveRecord
96
111
  if attributes.is_a?(Array)
97
112
  attributes.collect { |attr| create!(attr, &block) }
98
113
  else
99
- scoping { klass.create!(values_for_create(attributes), &block) }
114
+ block = _deprecated_scope_block("create!", &block)
115
+ scoping { klass.create!(attributes, &block) }
100
116
  end
101
117
  end
102
118
 
@@ -143,23 +159,12 @@ module ActiveRecord
143
159
  # failed due to validation errors it won't be persisted, you get what
144
160
  # #create returns in such situation.
145
161
  #
146
- # Please note *this method is not atomic*, it runs first a SELECT, and if
162
+ # Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
147
163
  # there are no results an INSERT is attempted. If there are other threads
148
164
  # or processes there is a race condition between both calls and it could
149
165
  # be the case that you end up with two similar records.
150
166
  #
151
- # Whether that is a problem or not depends on the logic of the
152
- # application, but in the particular case in which rows have a UNIQUE
153
- # constraint an exception may be raised, just retry:
154
- #
155
- # begin
156
- # CreditAccount.transaction(requires_new: true) do
157
- # CreditAccount.find_or_create_by(user_id: user.id)
158
- # end
159
- # rescue ActiveRecord::RecordNotUnique
160
- # retry
161
- # end
162
- #
167
+ # If this might be a problem for your application, please see #create_or_find_by.
163
168
  def find_or_create_by(attributes, &block)
164
169
  find_by(attributes) || create(attributes, &block)
165
170
  end
@@ -171,6 +176,51 @@ module ActiveRecord
171
176
  find_by(attributes) || create!(attributes, &block)
172
177
  end
173
178
 
179
+ # Attempts to create a record with the given attributes in a table that has a unique constraint
180
+ # on one or several of its columns. If a row already exists with one or several of these
181
+ # unique constraints, the exception such an insertion would normally raise is caught,
182
+ # and the existing record with those attributes is found using #find_by!.
183
+ #
184
+ # This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
185
+ # and the INSERT, as that method needs to first query the table, then attempt to insert a row
186
+ # if none is found.
187
+ #
188
+ # There are several drawbacks to #create_or_find_by, though:
189
+ #
190
+ # * The underlying table must have the relevant columns defined with unique constraints.
191
+ # * A unique constraint violation may be triggered by only one, or at least less than all,
192
+ # of the given attributes. This means that the subsequent #find_by! may fail to find a
193
+ # matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
194
+ # rather than a record with the given attributes.
195
+ # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
196
+ # we actually have another race condition between INSERT -> SELECT, which can be triggered
197
+ # if a DELETE between those two statements is run by another client. But for most applications,
198
+ # that's a significantly less likely condition to hit.
199
+ # * It relies on exception handling to handle control flow, which may be marginally slower.
200
+ # * The primary key may auto-increment on each create, even if it fails. This can accelerate
201
+ # the problem of running out of integers, if the underlying table is still stuck on a primary
202
+ # key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
203
+ # to this problem).
204
+ #
205
+ # This method will return a record if all given attributes are covered by unique constraints
206
+ # (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
207
+ # and failed due to validation errors it won't be persisted, you get what #create returns in
208
+ # such situation.
209
+ def create_or_find_by(attributes, &block)
210
+ transaction(requires_new: true) { create(attributes, &block) }
211
+ rescue ActiveRecord::RecordNotUnique
212
+ find_by!(attributes)
213
+ end
214
+
215
+ # Like #create_or_find_by, but calls
216
+ # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
217
+ # is raised if the created record is invalid.
218
+ def create_or_find_by!(attributes, &block)
219
+ transaction(requires_new: true) { create!(attributes, &block) }
220
+ rescue ActiveRecord::RecordNotUnique
221
+ find_by!(attributes)
222
+ end
223
+
174
224
  # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
175
225
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
176
226
  def find_or_initialize_by(attributes, &block)
@@ -185,7 +235,7 @@ module ActiveRecord
185
235
  # are needed by the next ones when eager loading is going on.
186
236
  #
187
237
  # Please see further details in the
188
- # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
238
+ # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
189
239
  def explain
190
240
  exec_explain(collecting_queries_for_explain { exec_queries })
191
241
  end
@@ -241,32 +291,100 @@ module ActiveRecord
241
291
  limit_value ? records.many? : size > 1
242
292
  end
243
293
 
244
- # Returns a cache key that can be used to identify the records fetched by
245
- # this query. The cache key is built with a fingerprint of the sql query,
246
- # the number of records matched by the query and a timestamp of the last
247
- # updated record. When a new record comes to match the query, or any of
248
- # the existing records is updated or deleted, the cache key changes.
294
+ # Returns a stable cache key that can be used to identify this query.
295
+ # The cache key is built with a fingerprint of the SQL query.
249
296
  #
250
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
251
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
297
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
298
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
252
299
  #
253
- # If the collection is loaded, the method will iterate through the records
254
- # to generate the timestamp, otherwise it will trigger one SQL query like:
300
+ # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
301
+ # in Rails 6.0 and earlier, the cache key will also include a version.
255
302
  #
256
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
303
+ # ActiveRecord::Base.collection_cache_versioning = false
304
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
305
+ # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
257
306
  #
258
307
  # You can also pass a custom timestamp column to fetch the timestamp of the
259
308
  # last updated record.
260
309
  #
261
310
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
262
- #
263
- # You can customize the strategy to generate the key on a per model basis
264
- # overriding ActiveRecord::Base#collection_cache_key.
265
311
  def cache_key(timestamp_column = :updated_at)
266
312
  @cache_keys ||= {}
267
- @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
313
+ @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
314
+ end
315
+
316
+ def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
317
+ query_signature = ActiveSupport::Digest.hexdigest(to_sql)
318
+ key = "#{klass.model_name.cache_key}/query-#{query_signature}"
319
+
320
+ if cache_version(timestamp_column)
321
+ key
322
+ else
323
+ "#{key}-#{compute_cache_version(timestamp_column)}"
324
+ end
325
+ end
326
+ private :compute_cache_key
327
+
328
+ # Returns a cache version that can be used together with the cache key to form
329
+ # a recyclable caching scheme. The cache version is built with the number of records
330
+ # matching the query, and the timestamp of the last updated record. When a new record
331
+ # comes to match the query, or any of the existing records is updated or deleted,
332
+ # the cache version changes.
333
+ #
334
+ # If the collection is loaded, the method will iterate through the records
335
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
336
+ #
337
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
338
+ def cache_version(timestamp_column = :updated_at)
339
+ if collection_cache_versioning
340
+ @cache_versions ||= {}
341
+ @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
342
+ end
268
343
  end
269
344
 
345
+ def compute_cache_version(timestamp_column) # :nodoc:
346
+ if loaded? || distinct_value
347
+ size = records.size
348
+ if size > 0
349
+ timestamp = max_by(&timestamp_column)._read_attribute(timestamp_column)
350
+ end
351
+ else
352
+ collection = eager_loading? ? apply_join_dependency : self
353
+
354
+ column = connection.visitor.compile(arel_attribute(timestamp_column))
355
+ select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
356
+
357
+ if collection.has_limit_or_offset?
358
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
359
+ subquery_alias = "subquery_for_cache_key"
360
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
361
+ arel = query.build_subquery(subquery_alias, select_values % subquery_column)
362
+ else
363
+ query = collection.unscope(:order)
364
+ query.select_values = [select_values % column]
365
+ arel = query.arel
366
+ end
367
+
368
+ result = connection.select_one(arel, nil)
369
+
370
+ if result
371
+ column_type = klass.type_for_attribute(timestamp_column)
372
+ timestamp = column_type.deserialize(result["timestamp"])
373
+ size = result["size"]
374
+ else
375
+ timestamp = nil
376
+ size = 0
377
+ end
378
+ end
379
+
380
+ if timestamp
381
+ "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
382
+ else
383
+ "#{size}"
384
+ end
385
+ end
386
+ private :compute_cache_version
387
+
270
388
  # Scope all queries to the current scope.
271
389
  #
272
390
  # Comment.where(post_id: 1).scoping do
@@ -277,15 +395,12 @@ module ActiveRecord
277
395
  # Please check unscoped if you want to remove all previous scopes (including
278
396
  # the default_scope) during the execution of a block.
279
397
  def scoping
280
- previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
281
- yield
282
- ensure
283
- klass.current_scope = previous unless @delegate_to_klass
398
+ already_in_scope? ? yield : _scoping(self) { yield }
284
399
  end
285
400
 
286
- def _exec_scope(*args, &block) # :nodoc:
401
+ def _exec_scope(name, *args, &block) # :nodoc:
287
402
  @delegate_to_klass = true
288
- instance_exec(*args, &block) || self
403
+ _scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
289
404
  ensure
290
405
  @delegate_to_klass = false
291
406
  end
@@ -295,6 +410,8 @@ module ActiveRecord
295
410
  # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
296
411
  # Active Record's normal type casting and serialization.
297
412
  #
413
+ # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
414
+ #
298
415
  # ==== Parameters
299
416
  #
300
417
  # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
@@ -321,17 +438,23 @@ module ActiveRecord
321
438
  end
322
439
 
323
440
  stmt = Arel::UpdateManager.new
324
-
325
- stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
326
- stmt.table(table)
327
-
328
- if has_join_values? || offset_value
329
- @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
441
+ stmt.table(arel.join_sources.empty? ? table : arel.source)
442
+ stmt.key = arel_attribute(primary_key)
443
+ stmt.take(arel.limit)
444
+ stmt.offset(arel.offset)
445
+ stmt.order(*arel.orders)
446
+ stmt.wheres = arel.constraints
447
+
448
+ if updates.is_a?(Hash)
449
+ if klass.locking_enabled? &&
450
+ !updates.key?(klass.locking_column) &&
451
+ !updates.key?(klass.locking_column.to_sym)
452
+ attr = arel_attribute(klass.locking_column)
453
+ updates[attr.name] = _increment_attribute(attr)
454
+ end
455
+ stmt.set _substitute_values(updates)
330
456
  else
331
- stmt.key = arel_attribute(primary_key)
332
- stmt.take(arel.limit)
333
- stmt.order(*arel.orders)
334
- stmt.wheres = arel.constraints
457
+ stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
335
458
  end
336
459
 
337
460
  @klass.connection.update stmt, "#{@klass} Update All"
@@ -345,6 +468,51 @@ module ActiveRecord
345
468
  end
346
469
  end
347
470
 
471
+ def update_counters(counters) # :nodoc:
472
+ touch = counters.delete(:touch)
473
+
474
+ updates = {}
475
+ counters.each do |counter_name, value|
476
+ attr = arel_attribute(counter_name)
477
+ updates[attr.name] = _increment_attribute(attr, value)
478
+ end
479
+
480
+ if touch
481
+ names = touch if touch != true
482
+ touch_updates = klass.touch_attributes_with_time(*names)
483
+ updates.merge!(touch_updates) unless touch_updates.empty?
484
+ end
485
+
486
+ update_all updates
487
+ end
488
+
489
+ # Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
490
+ # set to the current time or the time specified.
491
+ # This method can be passed attribute names and an optional time argument.
492
+ # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
493
+ # If no time argument is passed, the current time is used as default.
494
+ #
495
+ # === Examples
496
+ #
497
+ # # Touch all records
498
+ # Person.all.touch_all
499
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
500
+ #
501
+ # # Touch multiple records with a custom attribute
502
+ # Person.all.touch_all(:created_at)
503
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
504
+ #
505
+ # # Touch multiple records with a specified time
506
+ # Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
507
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
508
+ #
509
+ # # Touch records with scope
510
+ # Person.where(name: 'David').touch_all
511
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
512
+ def touch_all(*names, time: nil)
513
+ update_all klass.touch_attributes_with_time(*names, time: time)
514
+ end
515
+
348
516
  # Destroys the records by instantiating each
349
517
  # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
350
518
  # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
@@ -385,8 +553,8 @@ module ActiveRecord
385
553
  # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
386
554
  def delete_all
387
555
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
388
- value = get_value(method)
389
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
556
+ value = @values[method]
557
+ method == :distinct ? value : value&.any?
390
558
  end
391
559
  if invalid_methods.any?
392
560
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
@@ -398,13 +566,12 @@ module ActiveRecord
398
566
  end
399
567
 
400
568
  stmt = Arel::DeleteManager.new
401
- stmt.from(table)
402
-
403
- if has_join_values? || has_limit_or_offset?
404
- @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
405
- else
406
- stmt.wheres = arel.constraints
407
- end
569
+ stmt.from(arel.join_sources.empty? ? table : arel.source)
570
+ stmt.key = arel_attribute(primary_key)
571
+ stmt.take(arel.limit)
572
+ stmt.offset(arel.offset)
573
+ stmt.order(*arel.orders)
574
+ stmt.wheres = arel.constraints
408
575
 
409
576
  affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
410
577
 
@@ -412,6 +579,32 @@ module ActiveRecord
412
579
  affected
413
580
  end
414
581
 
582
+ # Finds and destroys all records matching the specified conditions.
583
+ # This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
584
+ # Returns the collection of objects that were destroyed.
585
+ #
586
+ # If no record is found, returns empty array.
587
+ #
588
+ # Person.destroy_by(id: 13)
589
+ # Person.destroy_by(name: 'Spartacus', rating: 4)
590
+ # Person.destroy_by("published_at < ?", 2.weeks.ago)
591
+ def destroy_by(*args)
592
+ where(*args).destroy_all
593
+ end
594
+
595
+ # Finds and deletes all records matching the specified conditions.
596
+ # This is short-hand for <tt>relation.where(condition).delete_all</tt>.
597
+ # Returns the number of rows affected.
598
+ #
599
+ # If no record is found, returns <tt>0</tt> as zero rows were affected.
600
+ #
601
+ # Person.delete_by(id: 13)
602
+ # Person.delete_by(name: 'Spartacus', rating: 4)
603
+ # Person.delete_by("published_at < ?", 2.weeks.ago)
604
+ def delete_by(*args)
605
+ where(*args).delete_all
606
+ end
607
+
415
608
  # Causes the records to be loaded from the database if they have not
416
609
  # been loaded already. You can use this if for some reason you need
417
610
  # to explicitly load some records before actually using them. The
@@ -432,6 +625,7 @@ module ActiveRecord
432
625
 
433
626
  def reset
434
627
  @delegate_to_klass = false
628
+ @_deprecated_scope_source = nil
435
629
  @to_sql = @arel = @loaded = @should_eager_load = nil
436
630
  @records = [].freeze
437
631
  @offsets = {}
@@ -530,17 +724,72 @@ module ActiveRecord
530
724
  ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
531
725
  end
532
726
 
727
+ def preload_associations(records) # :nodoc:
728
+ preload = preload_values
729
+ preload += includes_values unless eager_loading?
730
+ preloader = nil
731
+ preload.each do |associations|
732
+ preloader ||= build_preloader
733
+ preloader.preload records, associations
734
+ end
735
+ end
736
+
737
+ attr_reader :_deprecated_scope_source # :nodoc:
738
+
533
739
  protected
740
+ attr_writer :_deprecated_scope_source # :nodoc:
534
741
 
535
742
  def load_records(records)
536
743
  @records = records.freeze
537
744
  @loaded = true
538
745
  end
539
746
 
747
+ def null_relation? # :nodoc:
748
+ is_a?(NullRelation)
749
+ end
750
+
540
751
  private
752
+ def already_in_scope?
753
+ @delegate_to_klass && begin
754
+ scope = klass.current_scope(true)
755
+ scope && !scope._deprecated_scope_source
756
+ end
757
+ end
758
+
759
+ def _deprecated_spawn(name)
760
+ spawn.tap { |scope| scope._deprecated_scope_source = name }
761
+ end
762
+
763
+ def _deprecated_scope_block(name, &block)
764
+ -> record do
765
+ klass.current_scope = _deprecated_spawn(name)
766
+ yield record if block_given?
767
+ end
768
+ end
541
769
 
542
- def has_join_values?
543
- joins_values.any? || left_outer_joins_values.any?
770
+ def _scoping(scope)
771
+ previous, klass.current_scope = klass.current_scope(true), scope
772
+ yield
773
+ ensure
774
+ klass.current_scope = previous
775
+ end
776
+
777
+ def _substitute_values(values)
778
+ values.map do |name, value|
779
+ attr = arel_attribute(name)
780
+ unless Arel.arel_node?(value)
781
+ type = klass.type_for_attribute(attr.name)
782
+ value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
783
+ end
784
+ [attr, value]
785
+ end
786
+ end
787
+
788
+ def _increment_attribute(attribute, value = 1)
789
+ bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
790
+ expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
791
+ expr = value < 0 ? expr - bind : expr + bind
792
+ expr.expr
544
793
  end
545
794
 
546
795
  def exec_queries(&block)
@@ -548,7 +797,7 @@ module ActiveRecord
548
797
  @records =
549
798
  if eager_loading?
550
799
  apply_join_dependency do |relation, join_dependency|
551
- if ActiveRecord::NullRelation === relation
800
+ if relation.null_relation?
552
801
  []
553
802
  else
554
803
  relation = join_dependency.apply_column_aliases(relation)
@@ -560,13 +809,7 @@ module ActiveRecord
560
809
  klass.find_by_sql(arel, &block).freeze
561
810
  end
562
811
 
563
- preload = preload_values
564
- preload += includes_values unless eager_loading?
565
- preloader = nil
566
- preload.each do |associations|
567
- preloader ||= build_preloader
568
- preloader.preload @records, associations
569
- end
812
+ preload_associations(@records) unless skip_preloading_value
570
813
 
571
814
  @records.each(&:readonly!) if readonly_value
572
815
 
@@ -612,18 +855,5 @@ module ActiveRecord
612
855
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
613
856
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
614
857
  end
615
-
616
- def values_for_create(attributes = nil)
617
- result = attributes ? where_values_hash.merge!(attributes) : where_values_hash
618
-
619
- # NOTE: if there are same keys in both create_with and result, create_with should be used.
620
- # This is to make sure nested attributes don't get passed to the klass.new,
621
- # while keeping the precedence of the duplicate keys in create_with.
622
- create_with_value.stringify_keys.each do |k, v|
623
- result[k] = v if result.key?(k)
624
- end
625
-
626
- result
627
- end
628
858
  end
629
859
  end
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  # ]
22
22
  #
23
23
  # # Get an array of hashes representing the result (column => value):
24
- # result.to_hash
24
+ # result.to_a
25
25
  # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
26
26
  # {"id" => 2, "title" => "title_2", "body" => "body_2"},
27
27
  # ...
@@ -43,6 +43,11 @@ module ActiveRecord
43
43
  @column_types = column_types
44
44
  end
45
45
 
46
+ # Returns true if this result set includes the column named +name+
47
+ def includes_column?(name)
48
+ @columns.include? name
49
+ end
50
+
46
51
  # Returns the number of elements in the rows array.
47
52
  def length
48
53
  @rows.length
@@ -60,9 +65,12 @@ module ActiveRecord
60
65
  end
61
66
  end
62
67
 
63
- # Returns an array of hashes representing each row record.
64
68
  def to_hash
65
- hash_rows
69
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
70
+ `ActiveRecord::Result#to_hash` has been renamed to `to_a`.
71
+ `to_hash` is deprecated and will be removed in Rails 6.1.
72
+ MSG
73
+ to_a
66
74
  end
67
75
 
68
76
  alias :map! :map
@@ -78,6 +86,8 @@ module ActiveRecord
78
86
  hash_rows
79
87
  end
80
88
 
89
+ alias :to_a :to_ary
90
+
81
91
  def [](idx)
82
92
  hash_rows[idx]
83
93
  end
@@ -97,12 +107,21 @@ module ActiveRecord
97
107
  end
98
108
 
99
109
  def cast_values(type_overrides = {}) # :nodoc:
100
- types = columns.map { |name| column_type(name, type_overrides) }
101
- result = rows.map do |values|
102
- types.zip(values).map { |type, value| type.deserialize(value) }
103
- end
110
+ if columns.one?
111
+ # Separated to avoid allocating an array per row
112
+
113
+ type = column_type(columns.first, type_overrides)
104
114
 
105
- columns.one? ? result.map!(&:first) : result
115
+ rows.map do |(value)|
116
+ type.deserialize(value)
117
+ end
118
+ else
119
+ types = columns.map { |name| column_type(name, type_overrides) }
120
+
121
+ rows.map do |values|
122
+ Array.new(values.size) { |i| types[i].deserialize(values[i]) }
123
+ end
124
+ end
106
125
  end
107
126
 
108
127
  def initialize_copy(other)
@@ -125,7 +144,9 @@ module ActiveRecord
125
144
  begin
126
145
  # We freeze the strings to prevent them getting duped when
127
146
  # used as keys in ActiveRecord::Base's @attributes hash
128
- columns = @columns.map { |c| c.dup.freeze }
147
+ columns = @columns.map(&:-@)
148
+ length = columns.length
149
+
129
150
  @rows.map { |row|
130
151
  # In the past we used Hash[columns.zip(row)]
131
152
  # though elegant, the verbose way is much more efficient
@@ -134,8 +155,6 @@ module ActiveRecord
134
155
  hash = {}
135
156
 
136
157
  index = 0
137
- length = columns.length
138
-
139
158
  while index < length
140
159
  hash[columns[index]] = row[index]
141
160
  index += 1