activerecord 6.1.4.1 → 7.0.1

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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1132 -936
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +33 -17
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +34 -27
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/join_dependency.rb +6 -2
  25. data/lib/active_record/associations/preloader/association.rb +187 -55
  26. data/lib/active_record/associations/preloader/batch.rb +48 -0
  27. data/lib/active_record/associations/preloader/branch.rb +147 -0
  28. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  29. data/lib/active_record/associations/preloader.rb +39 -113
  30. data/lib/active_record/associations/singular_association.rb +8 -2
  31. data/lib/active_record/associations/through_association.rb +3 -3
  32. data/lib/active_record/associations.rb +118 -90
  33. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  34. data/lib/active_record/attribute_assignment.rb +1 -1
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  37. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  38. data/lib/active_record/attribute_methods/query.rb +2 -2
  39. data/lib/active_record/attribute_methods/read.rb +7 -5
  40. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  42. data/lib/active_record/attribute_methods/write.rb +7 -10
  43. data/lib/active_record/attribute_methods.rb +13 -14
  44. data/lib/active_record/attributes.rb +24 -35
  45. data/lib/active_record/autosave_association.rb +6 -21
  46. data/lib/active_record/base.rb +19 -1
  47. data/lib/active_record/callbacks.rb +2 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +34 -9
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +78 -22
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  62. data/lib/active_record/connection_adapters/column.rb +4 -0
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +37 -23
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  65. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +5 -1
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  67. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  68. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -12
  70. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  73. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +29 -18
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +205 -105
  85. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  86. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +27 -19
  87. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  88. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +16 -14
  89. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +89 -30
  90. data/lib/active_record/connection_adapters.rb +6 -5
  91. data/lib/active_record/connection_handling.rb +47 -53
  92. data/lib/active_record/core.rb +122 -132
  93. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  94. data/lib/active_record/database_configurations/database_config.rb +12 -9
  95. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  96. data/lib/active_record/database_configurations/url_config.rb +2 -2
  97. data/lib/active_record/database_configurations.rb +16 -32
  98. data/lib/active_record/delegated_type.rb +52 -11
  99. data/lib/active_record/destroy_association_async_job.rb +1 -1
  100. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  101. data/lib/active_record/dynamic_matchers.rb +1 -1
  102. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  103. data/lib/active_record/encryption/cipher.rb +53 -0
  104. data/lib/active_record/encryption/config.rb +44 -0
  105. data/lib/active_record/encryption/configurable.rb +61 -0
  106. data/lib/active_record/encryption/context.rb +35 -0
  107. data/lib/active_record/encryption/contexts.rb +72 -0
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  109. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  110. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  111. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  112. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  113. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  114. data/lib/active_record/encryption/encryptor.rb +155 -0
  115. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  116. data/lib/active_record/encryption/errors.rb +15 -0
  117. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  118. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  119. data/lib/active_record/encryption/key.rb +28 -0
  120. data/lib/active_record/encryption/key_generator.rb +42 -0
  121. data/lib/active_record/encryption/key_provider.rb +46 -0
  122. data/lib/active_record/encryption/message.rb +33 -0
  123. data/lib/active_record/encryption/message_serializer.rb +90 -0
  124. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  125. data/lib/active_record/encryption/properties.rb +76 -0
  126. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  127. data/lib/active_record/encryption/scheme.rb +99 -0
  128. data/lib/active_record/encryption.rb +55 -0
  129. data/lib/active_record/enum.rb +49 -42
  130. data/lib/active_record/errors.rb +67 -4
  131. data/lib/active_record/explain_registry.rb +11 -6
  132. data/lib/active_record/fixture_set/file.rb +15 -1
  133. data/lib/active_record/fixture_set/table_row.rb +41 -6
  134. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  135. data/lib/active_record/fixtures.rb +17 -20
  136. data/lib/active_record/future_result.rb +139 -0
  137. data/lib/active_record/gem_version.rb +4 -4
  138. data/lib/active_record/inheritance.rb +55 -17
  139. data/lib/active_record/insert_all.rb +80 -14
  140. data/lib/active_record/integration.rb +4 -3
  141. data/lib/active_record/internal_metadata.rb +3 -5
  142. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  143. data/lib/active_record/locking/optimistic.rb +10 -9
  144. data/lib/active_record/locking/pessimistic.rb +9 -3
  145. data/lib/active_record/log_subscriber.rb +14 -3
  146. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  147. data/lib/active_record/middleware/database_selector.rb +8 -3
  148. data/lib/active_record/middleware/shard_selector.rb +60 -0
  149. data/lib/active_record/migration/command_recorder.rb +4 -4
  150. data/lib/active_record/migration/compatibility.rb +107 -3
  151. data/lib/active_record/migration/join_table.rb +1 -1
  152. data/lib/active_record/migration.rb +109 -79
  153. data/lib/active_record/model_schema.rb +45 -58
  154. data/lib/active_record/nested_attributes.rb +13 -12
  155. data/lib/active_record/no_touching.rb +3 -3
  156. data/lib/active_record/null_relation.rb +2 -6
  157. data/lib/active_record/persistence.rb +219 -52
  158. data/lib/active_record/query_cache.rb +2 -2
  159. data/lib/active_record/query_logs.rb +138 -0
  160. data/lib/active_record/querying.rb +15 -5
  161. data/lib/active_record/railtie.rb +127 -17
  162. data/lib/active_record/railties/controller_runtime.rb +1 -1
  163. data/lib/active_record/railties/databases.rake +66 -129
  164. data/lib/active_record/readonly_attributes.rb +11 -0
  165. data/lib/active_record/reflection.rb +67 -50
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  167. data/lib/active_record/relation/batches.rb +3 -3
  168. data/lib/active_record/relation/calculations.rb +43 -38
  169. data/lib/active_record/relation/delegation.rb +6 -6
  170. data/lib/active_record/relation/finder_methods.rb +31 -35
  171. data/lib/active_record/relation/merger.rb +20 -13
  172. data/lib/active_record/relation/predicate_builder.rb +1 -6
  173. data/lib/active_record/relation/query_attribute.rb +5 -11
  174. data/lib/active_record/relation/query_methods.rb +243 -61
  175. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  176. data/lib/active_record/relation/spawn_methods.rb +2 -2
  177. data/lib/active_record/relation/where_clause.rb +10 -19
  178. data/lib/active_record/relation.rb +184 -84
  179. data/lib/active_record/result.rb +17 -7
  180. data/lib/active_record/runtime_registry.rb +9 -13
  181. data/lib/active_record/sanitization.rb +11 -7
  182. data/lib/active_record/schema_dumper.rb +10 -3
  183. data/lib/active_record/schema_migration.rb +4 -4
  184. data/lib/active_record/scoping/default.rb +61 -12
  185. data/lib/active_record/scoping/named.rb +3 -11
  186. data/lib/active_record/scoping.rb +64 -34
  187. data/lib/active_record/serialization.rb +1 -1
  188. data/lib/active_record/signed_id.rb +1 -1
  189. data/lib/active_record/suppressor.rb +11 -15
  190. data/lib/active_record/tasks/database_tasks.rb +120 -58
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  193. data/lib/active_record/test_databases.rb +1 -1
  194. data/lib/active_record/test_fixtures.rb +4 -4
  195. data/lib/active_record/timestamp.rb +3 -4
  196. data/lib/active_record/transactions.rb +9 -14
  197. data/lib/active_record/translation.rb +2 -2
  198. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  199. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  200. data/lib/active_record/type/internal/timezone.rb +2 -2
  201. data/lib/active_record/type/serialized.rb +1 -1
  202. data/lib/active_record/type/type_map.rb +17 -20
  203. data/lib/active_record/type.rb +1 -2
  204. data/lib/active_record/validations/associated.rb +1 -1
  205. data/lib/active_record/validations/uniqueness.rb +1 -1
  206. data/lib/active_record.rb +204 -28
  207. data/lib/arel/attributes/attribute.rb +0 -8
  208. data/lib/arel/crud.rb +28 -22
  209. data/lib/arel/delete_manager.rb +18 -4
  210. data/lib/arel/filter_predications.rb +9 -0
  211. data/lib/arel/insert_manager.rb +2 -3
  212. data/lib/arel/nodes/casted.rb +1 -1
  213. data/lib/arel/nodes/delete_statement.rb +12 -13
  214. data/lib/arel/nodes/filter.rb +10 -0
  215. data/lib/arel/nodes/function.rb +1 -0
  216. data/lib/arel/nodes/insert_statement.rb +2 -2
  217. data/lib/arel/nodes/select_core.rb +2 -2
  218. data/lib/arel/nodes/select_statement.rb +2 -2
  219. data/lib/arel/nodes/update_statement.rb +8 -3
  220. data/lib/arel/nodes.rb +1 -0
  221. data/lib/arel/predications.rb +11 -3
  222. data/lib/arel/select_manager.rb +10 -4
  223. data/lib/arel/table.rb +0 -1
  224. data/lib/arel/tree_manager.rb +0 -12
  225. data/lib/arel/update_manager.rb +18 -4
  226. data/lib/arel/visitors/dot.rb +80 -90
  227. data/lib/arel/visitors/mysql.rb +8 -2
  228. data/lib/arel/visitors/postgresql.rb +0 -10
  229. data/lib/arel/visitors/to_sql.rb +58 -2
  230. data/lib/arel.rb +2 -1
  231. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  232. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  233. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  234. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  235. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  236. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  237. metadata +59 -14
@@ -8,8 +8,6 @@ require "active_support/core_ext/array/wrap"
8
8
 
9
9
  module ActiveRecord
10
10
  module QueryMethods
11
- extend ActiveSupport::Concern
12
-
13
11
  include ActiveModel::ForbiddenAttributesProtection
14
12
 
15
13
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
@@ -50,6 +48,34 @@ module ActiveRecord
50
48
  @scope
51
49
  end
52
50
 
51
+ # Returns a new relation with joins and where clause to identify
52
+ # associated relations.
53
+ #
54
+ # For example, posts that are associated to a related author:
55
+ #
56
+ # Post.where.associated(:author)
57
+ # # SELECT "posts".* FROM "posts"
58
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
59
+ # # WHERE "authors"."id" IS NOT NULL
60
+ #
61
+ # Additionally, multiple relations can be combined. This will return posts
62
+ # associated to both an author and any comments:
63
+ #
64
+ # Post.where.associated(:author, :comments)
65
+ # # SELECT "posts".* FROM "posts"
66
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
67
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
68
+ # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
69
+ def associated(*associations)
70
+ associations.each do |association|
71
+ reflection = @scope.klass._reflect_on_association(association)
72
+ @scope.joins!(association)
73
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
74
+ end
75
+
76
+ @scope
77
+ end
78
+
53
79
  # Returns a new relation with left outer joins and where clause to identify
54
80
  # missing relations.
55
81
  #
@@ -68,12 +94,14 @@ module ActiveRecord
68
94
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
95
  # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
96
  # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
71
- def missing(*args)
72
- args.each do |arg|
73
- reflection = @scope.klass._reflect_on_association(arg)
74
- opts = { reflection.table_name => { reflection.association_primary_key => nil } }
75
- @scope.left_outer_joins!(arg)
76
- @scope.where!(opts)
97
+ def missing(*associations)
98
+ associations.each do |association|
99
+ reflection = @scope.klass._reflect_on_association(association)
100
+ unless reflection
101
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
102
+ end
103
+ @scope.left_outer_joins!(association)
104
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
77
105
  end
78
106
 
79
107
  @scope
@@ -148,7 +176,7 @@ module ActiveRecord
148
176
  #
149
177
  # User.includes(:posts).where(posts: { name: 'example' })
150
178
  def includes(*args)
151
- check_if_method_has_arguments!(:includes, args)
179
+ check_if_method_has_arguments!(__callee__, args)
152
180
  spawn.includes!(*args)
153
181
  end
154
182
 
@@ -164,7 +192,7 @@ module ActiveRecord
164
192
  # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
165
193
  # # "users"."id"
166
194
  def eager_load(*args)
167
- check_if_method_has_arguments!(:eager_load, args)
195
+ check_if_method_has_arguments!(__callee__, args)
168
196
  spawn.eager_load!(*args)
169
197
  end
170
198
 
@@ -178,7 +206,7 @@ module ActiveRecord
178
206
  # User.preload(:posts)
179
207
  # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
180
208
  def preload(*args)
181
- check_if_method_has_arguments!(:preload, args)
209
+ check_if_method_has_arguments!(__callee__, args)
182
210
  spawn.preload!(*args)
183
211
  end
184
212
 
@@ -211,7 +239,7 @@ module ActiveRecord
211
239
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
212
240
  # # Query now knows the string references posts, so adds a JOIN
213
241
  def references(*table_names)
214
- check_if_method_has_arguments!(:references, table_names)
242
+ check_if_method_has_arguments!(__callee__, table_names)
215
243
  spawn.references!(*table_names)
216
244
  end
217
245
 
@@ -269,7 +297,7 @@ module ActiveRecord
269
297
  return super()
270
298
  end
271
299
 
272
- check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
300
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
273
301
  spawn._select!(*fields)
274
302
  end
275
303
 
@@ -289,7 +317,7 @@ module ActiveRecord
289
317
  # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
318
  # Note that we're unscoping the entire select statement.
291
319
  def reselect(*args)
292
- check_if_method_has_arguments!(:reselect, args)
320
+ check_if_method_has_arguments!(__callee__, args)
293
321
  spawn.reselect!(*args)
294
322
  end
295
323
 
@@ -320,7 +348,7 @@ module ActiveRecord
320
348
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
321
349
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
322
350
  def group(*args)
323
- check_if_method_has_arguments!(:group, args)
351
+ check_if_method_has_arguments!(__callee__, args)
324
352
  spawn.group!(*args)
325
353
  end
326
354
 
@@ -329,17 +357,37 @@ module ActiveRecord
329
357
  self
330
358
  end
331
359
 
332
- # Allows to specify an order attribute:
360
+ # Applies an <code>ORDER BY</code> clause to a query.
361
+ #
362
+ # #order accepts arguments in one of several formats.
363
+ #
364
+ # === symbols
365
+ #
366
+ # The symbol represents the name of the column you want to order the results by.
333
367
  #
334
368
  # User.order(:name)
335
369
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
336
370
  #
371
+ # By default, the order is ascending. If you want descending order, you can
372
+ # map the column name symbol to +:desc+.
373
+ #
337
374
  # User.order(email: :desc)
338
375
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
339
376
  #
377
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
378
+ #
340
379
  # User.order(:name, email: :desc)
341
380
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
342
381
  #
382
+ # === strings
383
+ #
384
+ # Strings are passed directly to the database, allowing you to specify
385
+ # simple SQL expressions.
386
+ #
387
+ # This could be a source of SQL injection, so only strings composed of plain
388
+ # column names and simple <code>function(column_name)</code> expressions
389
+ # with optional +ASC+/+DESC+ modifiers are allowed.
390
+ #
343
391
  # User.order('name')
344
392
  # # SELECT "users".* FROM "users" ORDER BY name
345
393
  #
@@ -348,8 +396,21 @@ module ActiveRecord
348
396
  #
349
397
  # User.order('name DESC, email')
350
398
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
399
+ #
400
+ # === Arel
401
+ #
402
+ # If you need to pass in complicated expressions that you have verified
403
+ # are safe for the database, you can use Arel.
404
+ #
405
+ # User.order(Arel.sql('end_date - start_date'))
406
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
407
+ #
408
+ # Custom query syntax, like JSON columns for Postgres, is supported in this way.
409
+ #
410
+ # User.order(Arel.sql("payload->>'kind'"))
411
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
351
412
  def order(*args)
352
- check_if_method_has_arguments!(:order, args) do
413
+ check_if_method_has_arguments!(__callee__, args) do
353
414
  sanitize_order_arguments(args)
354
415
  end
355
416
  spawn.order!(*args)
@@ -362,6 +423,29 @@ module ActiveRecord
362
423
  self
363
424
  end
364
425
 
426
+ # Allows to specify an order by a specific set of values. Depending on your
427
+ # adapter this will either use a CASE statement or a built-in function.
428
+ #
429
+ # User.in_order_of(:id, [1, 5, 3])
430
+ # # SELECT "users".* FROM "users"
431
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
432
+ # # WHERE "users"."id" IN (1, 5, 3)
433
+ #
434
+ def in_order_of(column, values)
435
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
436
+ return spawn.none! if values.empty?
437
+
438
+ references = column_references([column])
439
+ self.references_values |= references unless references.empty?
440
+
441
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
442
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
443
+
444
+ spawn
445
+ .order!(connection.field_ordered_value(arel_column, values))
446
+ .where!(arel_column.in(values))
447
+ end
448
+
365
449
  # Replaces any existing order defined on the relation with the specified order.
366
450
  #
367
451
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -372,15 +456,15 @@ module ActiveRecord
372
456
  #
373
457
  # generates a query with 'ORDER BY id ASC, name ASC'.
374
458
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
459
+ check_if_method_has_arguments!(__callee__, args) do
460
+ sanitize_order_arguments(args)
377
461
  end
378
462
  spawn.reorder!(*args)
379
463
  end
380
464
 
381
465
  # Same as #reorder but operates on relation in-place instead of copying.
382
466
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
467
+ preprocess_order_args(args)
384
468
  args.uniq!
385
469
  self.reordering_value = true
386
470
  self.order_values = args
@@ -425,7 +509,7 @@ module ActiveRecord
425
509
  # has_many :comments, -> { unscope(where: :trashed) }
426
510
  #
427
511
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
512
+ check_if_method_has_arguments!(__callee__, args)
429
513
  spawn.unscope!(*args)
430
514
  end
431
515
 
@@ -458,7 +542,7 @@ module ActiveRecord
458
542
  self
459
543
  end
460
544
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
545
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
546
  # the association(s).
463
547
  #
464
548
  # User.joins(:posts)
@@ -487,7 +571,7 @@ module ActiveRecord
487
571
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
572
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
573
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
574
+ check_if_method_has_arguments!(__callee__, args)
491
575
  spawn.joins!(*args)
492
576
  end
493
577
 
@@ -496,7 +580,7 @@ module ActiveRecord
496
580
  self
497
581
  end
498
582
 
499
- # Performs a left outer joins on +args+:
583
+ # Performs LEFT OUTER JOINs on +args+:
500
584
  #
501
585
  # User.left_outer_joins(:posts)
502
586
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@@ -578,13 +662,13 @@ module ActiveRecord
578
662
  #
579
663
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
664
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
665
+ # User.where(name: "Joe", email: "joe@example.com")
582
666
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
667
  #
584
- # User.where({ name: ["Alice", "Bob"]})
668
+ # User.where(name: ["Alice", "Bob"])
585
669
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
670
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
671
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
672
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
673
  #
590
674
  # In the case of a belongs_to relationship, an association key can be used
@@ -614,8 +698,8 @@ module ActiveRecord
614
698
  #
615
699
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
700
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
701
+ # User.joins(:posts).where("posts.published" => true)
702
+ # User.joins(:posts).where(posts: { published: true })
619
703
  #
620
704
  # === no argument
621
705
  #
@@ -668,6 +752,59 @@ module ActiveRecord
668
752
  scope
669
753
  end
670
754
 
755
+ # Allows you to invert an entire where clause instead of manually applying conditions.
756
+ #
757
+ # class User
758
+ # scope :active, -> { where(accepted: true, locked: false) }
759
+ # end
760
+ #
761
+ # User.where(accepted: true)
762
+ # # WHERE `accepted` = 1
763
+ #
764
+ # User.where(accepted: true).invert_where
765
+ # # WHERE `accepted` != 1
766
+ #
767
+ # User.active
768
+ # # WHERE `accepted` = 1 AND `locked` = 0
769
+ #
770
+ # User.active.invert_where
771
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
772
+ #
773
+ # Be careful because this inverts all conditions before +invert_where+ call.
774
+ #
775
+ # class User
776
+ # scope :active, -> { where(accepted: true, locked: false) }
777
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
778
+ # end
779
+ #
780
+ # # It also inverts `where(role: 'admin')` unexpectedly.
781
+ # User.where(role: 'admin').inactive
782
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
783
+ #
784
+ def invert_where
785
+ spawn.invert_where!
786
+ end
787
+
788
+ def invert_where! # :nodoc:
789
+ self.where_clause = where_clause.invert
790
+ self
791
+ end
792
+
793
+ # Checks whether the given relation is structurally compatible with this relation, to determine
794
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
795
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
796
+ # #where (if no #group has been defined) or #having (if a #group is present).
797
+ #
798
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
799
+ # # => true
800
+ #
801
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
802
+ # # => false
803
+ #
804
+ def structurally_compatible?(other)
805
+ structurally_incompatible_values_for(other).empty?
806
+ end
807
+
671
808
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
809
  # as an argument.
673
810
  #
@@ -886,7 +1023,7 @@ module ActiveRecord
886
1023
  self
887
1024
  end
888
1025
 
889
- # Specifies table from which the records will be fetched. For example:
1026
+ # Specifies the table from which the records will be fetched. For example:
890
1027
  #
891
1028
  # Topic.select('title').from('posts')
892
1029
  # # SELECT title FROM posts
@@ -896,9 +1033,26 @@ module ActiveRecord
896
1033
  # Topic.select('title').from(Topic.approved)
897
1034
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1035
  #
1036
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1037
+ #
899
1038
  # Topic.select('a.title').from(Topic.approved, :a)
900
1039
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1040
  #
1041
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1042
+ #
1043
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1044
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1045
+ #
1046
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1047
+ #
1048
+ # color = "red"
1049
+ # Color
1050
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1051
+ # .where("colorvalue->>'color' = ?", color)
1052
+ # .select("c.*").to_a
1053
+ # # SELECT c.*
1054
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1055
+ # # WHERE (colorvalue->>'color' = 'red')
902
1056
  def from(value, subquery_name = nil)
903
1057
  spawn.from!(value, subquery_name)
904
1058
  end
@@ -994,7 +1148,7 @@ module ActiveRecord
994
1148
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1149
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1150
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1151
+ check_if_method_has_arguments!(__callee__, args)
998
1152
  spawn.optimizer_hints!(*args)
999
1153
  end
1000
1154
 
@@ -1036,7 +1190,7 @@ module ActiveRecord
1036
1190
  #
1037
1191
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1038
1192
  def annotate(*args)
1039
- check_if_method_has_arguments!(:annotate, args)
1193
+ check_if_method_has_arguments!(__callee__, args)
1040
1194
  spawn.annotate!(*args)
1041
1195
  end
1042
1196
 
@@ -1054,6 +1208,47 @@ module ActiveRecord
1054
1208
  self
1055
1209
  end
1056
1210
 
1211
+ # Excludes the specified record (or collection of records) from the resulting
1212
+ # relation. For example:
1213
+ #
1214
+ # Post.excluding(post)
1215
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1216
+ #
1217
+ # Post.excluding(post_one, post_two)
1218
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1219
+ #
1220
+ # This can also be called on associations. As with the above example, either
1221
+ # a single record of collection thereof may be specified:
1222
+ #
1223
+ # post = Post.find(1)
1224
+ # comment = Comment.find(2)
1225
+ # post.comments.excluding(comment)
1226
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1227
+ #
1228
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1229
+ #
1230
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1231
+ # specified, or if any of the records in the collection (if a collection
1232
+ # is passed in) are not instances of the same model that the relation is
1233
+ # scoping.
1234
+ def excluding(*records)
1235
+ records.flatten!(1)
1236
+ records.compact!
1237
+
1238
+ unless records.all?(klass)
1239
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1240
+ end
1241
+
1242
+ spawn.excluding!(records)
1243
+ end
1244
+ alias :without :excluding
1245
+
1246
+ def excluding!(records) # :nodoc:
1247
+ predicates = [ predicate_builder[primary_key, records].invert ]
1248
+ self.where_clause += Relation::WhereClause.new(predicates)
1249
+ self
1250
+ end
1251
+
1057
1252
  # Returns the Arel object associated with the relation.
1058
1253
  def arel(aliases = nil) # :nodoc:
1059
1254
  @arel ||= build_arel(aliases)
@@ -1109,11 +1304,9 @@ module ActiveRecord
1109
1304
  nil
1110
1305
  end
1111
1306
 
1112
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1307
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1113
1308
  join_dependencies.each do |join_dependency|
1114
- join_dependency.each do |join|
1115
- yield join
1116
- end
1309
+ join_dependency.each(&block)
1117
1310
  end
1118
1311
  end
1119
1312
 
@@ -1155,14 +1348,6 @@ module ActiveRecord
1155
1348
  unless annotate_values.empty?
1156
1349
  annotates = annotate_values
1157
1350
  annotates = annotates.uniq if annotates.size > 1
1158
- unless annotates == annotate_values
1159
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
1160
- Duplicated query annotations are no longer shown in queries in Rails 6.2.
1161
- To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1162
- (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1163
- MSG
1164
- annotates = annotate_values
1165
- end
1166
1351
  arel.comment(*annotates)
1167
1352
  end
1168
1353
 
@@ -1170,8 +1355,7 @@ module ActiveRecord
1170
1355
  end
1171
1356
 
1172
1357
  def build_cast_value(name, value)
1173
- cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1174
- Arel::Nodes::BindParam.new(cast_value)
1358
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1175
1359
  end
1176
1360
 
1177
1361
  def build_from
@@ -1282,7 +1466,7 @@ module ActiveRecord
1282
1466
  def build_select(arel)
1283
1467
  if select_values.any?
1284
1468
  arel.project(*arel_columns(select_values))
1285
- elsif klass.ignored_columns.any?
1469
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1286
1470
  arel.project(*klass.column_names.map { |field| table[field] })
1287
1471
  else
1288
1472
  arel.project(table[Arel.star])
@@ -1423,12 +1607,17 @@ module ActiveRecord
1423
1607
  order_args.map! do |arg|
1424
1608
  klass.sanitize_sql_for_order(arg)
1425
1609
  end
1426
- order_args.flatten!
1427
- order_args.compact_blank!
1428
1610
  end
1429
1611
 
1430
1612
  def column_references(order_args)
1431
- references = order_args.grep(String)
1613
+ references = order_args.flat_map do |arg|
1614
+ case arg
1615
+ when String, Symbol
1616
+ arg
1617
+ when Hash
1618
+ arg.keys
1619
+ end
1620
+ end
1432
1621
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
1622
  references
1434
1623
  end
@@ -1476,19 +1665,19 @@ module ActiveRecord
1476
1665
  # Post.references() # raises an error
1477
1666
  # Post.references([]) # does not raise an error
1478
1667
  #
1479
- # This particular method should be called with a method_name and the args
1668
+ # This particular method should be called with a method_name (__callee__) and the args
1480
1669
  # passed into that method as an input. For example:
1481
1670
  #
1482
1671
  # def references(*args)
1483
- # check_if_method_has_arguments!("references", args)
1672
+ # check_if_method_has_arguments!(__callee__, args)
1484
1673
  # ...
1485
1674
  # end
1486
1675
  def check_if_method_has_arguments!(method_name, args, message = nil)
1487
1676
  if args.blank?
1488
1677
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
- elsif block_given?
1490
- yield args
1491
1678
  else
1679
+ yield args if block_given?
1680
+
1492
1681
  args.flatten!
1493
1682
  args.compact_blank!
1494
1683
  end
@@ -1512,11 +1701,4 @@ module ActiveRecord
1512
1701
  end
1513
1702
  end
1514
1703
  end
1515
-
1516
- class Relation # :nodoc:
1517
- # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1518
- # TODO: Remove the class once Rails 6.1 has released.
1519
- class WhereClauseFactory # :nodoc:
1520
- end
1521
- end
1522
1704
  end
@@ -17,8 +17,8 @@ module ActiveRecord
17
17
  QueryRegistry.reset
18
18
 
19
19
  super.tap do |records|
20
- if logger && warn_on_records_fetched_greater_than
21
- if records.length > warn_on_records_fetched_greater_than
20
+ if logger && ActiveRecord.warn_on_records_fetched_greater_than
21
+ if records.length > ActiveRecord.warn_on_records_fetched_greater_than
22
22
  logger.warn "Query fetched #{records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
23
23
  end
24
24
  end
@@ -31,17 +31,15 @@ module ActiveRecord
31
31
  end
32
32
  # :startdoc:
33
33
 
34
- class QueryRegistry # :nodoc:
35
- extend ActiveSupport::PerThreadRegistry
34
+ module QueryRegistry # :nodoc:
35
+ extend self
36
36
 
37
- attr_reader :queries
38
-
39
- def initialize
40
- @queries = []
37
+ def queries
38
+ ActiveSupport::IsolatedExecutionState[:active_record_query_registry] ||= []
41
39
  end
42
40
 
43
41
  def reset
44
- @queries.clear
42
+ queries.clear
45
43
  end
46
44
  end
47
45
  end
@@ -7,8 +7,8 @@ require "active_record/relation/merger"
7
7
  module ActiveRecord
8
8
  module SpawnMethods
9
9
  # This is overridden by Associations::CollectionProxy
10
- def spawn #:nodoc:
11
- already_in_scope? ? klass.all : clone
10
+ def spawn # :nodoc:
11
+ already_in_scope?(klass.scope_registry) ? klass.all : clone
12
12
  end
13
13
 
14
14
  # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
@@ -76,6 +76,11 @@ module ActiveRecord
76
76
  other.is_a?(WhereClause) &&
77
77
  predicates == other.predicates
78
78
  end
79
+ alias :eql? :==
80
+
81
+ def hash
82
+ [self.class, predicates].hash
83
+ end
79
84
 
80
85
  def invert
81
86
  if predicates.size == 1
@@ -158,21 +163,8 @@ module ActiveRecord
158
163
  attr = extract_attribute(node) || begin
159
164
  node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
160
165
  end
161
- next false unless attr
162
-
163
- ref = referenced_columns[attr]
164
- next false unless ref
165
-
166
- if equality_node?(node) && equality_node?(ref) || node == ref
167
- true
168
- else
169
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
170
- Merging (#{node.to_sql}) and (#{ref.to_sql}) no longer maintain
171
- both conditions, and will be replaced by the latter in Rails 6.2.
172
- To migrate to Rails 6.2's behavior, use `relation.merge(other, rewhere: true)`.
173
- MSG
174
- false
175
- end
166
+
167
+ attr && referenced_columns[attr]
176
168
  end
177
169
  end
178
170
 
@@ -227,11 +219,10 @@ module ActiveRecord
227
219
  end
228
220
 
229
221
  def extract_node_value(node)
230
- case node
231
- when Array
232
- node.map { |v| extract_node_value(v) }
233
- when Arel::Nodes::BindParam, Arel::Nodes::Casted, Arel::Nodes::Quoted
222
+ if node.respond_to?(:value_before_type_cast)
234
223
  node.value_before_type_cast
224
+ elsif Array === node
225
+ node.map { |v| extract_node_value(v) }
235
226
  end
236
227
  end
237
228
  end