activerecord 6.1.4.6 → 7.0.2.3

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1188 -932
  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 +119 -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 +8 -23
  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 +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +78 -22
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +38 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +5 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +35 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +27 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +16 -14
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +89 -30
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +47 -53
  94. data/lib/active_record/core.rb +122 -132
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +16 -32
  100. data/lib/active_record/delegated_type.rb +52 -11
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +61 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +49 -42
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +17 -20
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +3 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +9 -3
  147. data/lib/active_record/log_subscriber.rb +14 -3
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +8 -3
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +4 -4
  152. data/lib/active_record/migration/compatibility.rb +107 -3
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +109 -79
  155. data/lib/active_record/model_schema.rb +45 -58
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +219 -52
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +15 -5
  163. data/lib/active_record/railtie.rb +127 -17
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +66 -129
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +67 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +3 -3
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +249 -61
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +184 -84
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +11 -7
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +61 -12
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +1 -1
  191. data/lib/active_record/signed_id.rb +1 -1
  192. data/lib/active_record/suppressor.rb +11 -15
  193. data/lib/active_record/tasks/database_tasks.rb +120 -58
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  196. data/lib/active_record/test_databases.rb +1 -1
  197. data/lib/active_record/test_fixtures.rb +4 -4
  198. data/lib/active_record/timestamp.rb +3 -4
  199. data/lib/active_record/transactions.rb +9 -14
  200. data/lib/active_record/translation.rb +2 -2
  201. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  202. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  203. data/lib/active_record/type/internal/timezone.rb +2 -2
  204. data/lib/active_record/type/serialized.rb +1 -1
  205. data/lib/active_record/type/type_map.rb +17 -20
  206. data/lib/active_record/type.rb +1 -2
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/uniqueness.rb +1 -1
  209. data/lib/active_record.rb +204 -28
  210. data/lib/arel/attributes/attribute.rb +0 -8
  211. data/lib/arel/crud.rb +28 -22
  212. data/lib/arel/delete_manager.rb +18 -4
  213. data/lib/arel/filter_predications.rb +9 -0
  214. data/lib/arel/insert_manager.rb +2 -3
  215. data/lib/arel/nodes/casted.rb +1 -1
  216. data/lib/arel/nodes/delete_statement.rb +12 -13
  217. data/lib/arel/nodes/filter.rb +10 -0
  218. data/lib/arel/nodes/function.rb +1 -0
  219. data/lib/arel/nodes/insert_statement.rb +2 -2
  220. data/lib/arel/nodes/select_core.rb +2 -2
  221. data/lib/arel/nodes/select_statement.rb +2 -2
  222. data/lib/arel/nodes/update_statement.rb +8 -3
  223. data/lib/arel/nodes.rb +1 -0
  224. data/lib/arel/predications.rb +11 -3
  225. data/lib/arel/select_manager.rb +10 -4
  226. data/lib/arel/table.rb +0 -1
  227. data/lib/arel/tree_manager.rb +0 -12
  228. data/lib/arel/update_manager.rb +18 -4
  229. data/lib/arel/visitors/dot.rb +80 -90
  230. data/lib/arel/visitors/mysql.rb +8 -2
  231. data/lib/arel/visitors/postgresql.rb +0 -10
  232. data/lib/arel/visitors/to_sql.rb +58 -2
  233. data/lib/arel.rb +2 -1
  234. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  235. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  236. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  237. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  238. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  239. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  240. metadata +56 -11
@@ -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_association_reflection(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,16 +94,24 @@ 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_association_reflection(association)
100
+ @scope.left_outer_joins!(association)
101
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
77
102
  end
78
103
 
79
104
  @scope
80
105
  end
106
+
107
+ private
108
+ def scope_association_reflection(association)
109
+ reflection = @scope.klass._reflect_on_association(association)
110
+ unless reflection
111
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
112
+ end
113
+ reflection
114
+ end
81
115
  end
82
116
 
83
117
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -148,7 +182,7 @@ module ActiveRecord
148
182
  #
149
183
  # User.includes(:posts).where(posts: { name: 'example' })
150
184
  def includes(*args)
151
- check_if_method_has_arguments!(:includes, args)
185
+ check_if_method_has_arguments!(__callee__, args)
152
186
  spawn.includes!(*args)
153
187
  end
154
188
 
@@ -164,7 +198,7 @@ module ActiveRecord
164
198
  # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
165
199
  # # "users"."id"
166
200
  def eager_load(*args)
167
- check_if_method_has_arguments!(:eager_load, args)
201
+ check_if_method_has_arguments!(__callee__, args)
168
202
  spawn.eager_load!(*args)
169
203
  end
170
204
 
@@ -178,7 +212,7 @@ module ActiveRecord
178
212
  # User.preload(:posts)
179
213
  # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
180
214
  def preload(*args)
181
- check_if_method_has_arguments!(:preload, args)
215
+ check_if_method_has_arguments!(__callee__, args)
182
216
  spawn.preload!(*args)
183
217
  end
184
218
 
@@ -211,7 +245,7 @@ module ActiveRecord
211
245
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
212
246
  # # Query now knows the string references posts, so adds a JOIN
213
247
  def references(*table_names)
214
- check_if_method_has_arguments!(:references, table_names)
248
+ check_if_method_has_arguments!(__callee__, table_names)
215
249
  spawn.references!(*table_names)
216
250
  end
217
251
 
@@ -269,7 +303,7 @@ module ActiveRecord
269
303
  return super()
270
304
  end
271
305
 
272
- check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
306
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
273
307
  spawn._select!(*fields)
274
308
  end
275
309
 
@@ -289,7 +323,7 @@ module ActiveRecord
289
323
  # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
324
  # Note that we're unscoping the entire select statement.
291
325
  def reselect(*args)
292
- check_if_method_has_arguments!(:reselect, args)
326
+ check_if_method_has_arguments!(__callee__, args)
293
327
  spawn.reselect!(*args)
294
328
  end
295
329
 
@@ -320,7 +354,7 @@ module ActiveRecord
320
354
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
321
355
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
322
356
  def group(*args)
323
- check_if_method_has_arguments!(:group, args)
357
+ check_if_method_has_arguments!(__callee__, args)
324
358
  spawn.group!(*args)
325
359
  end
326
360
 
@@ -329,17 +363,37 @@ module ActiveRecord
329
363
  self
330
364
  end
331
365
 
332
- # Allows to specify an order attribute:
366
+ # Applies an <code>ORDER BY</code> clause to a query.
367
+ #
368
+ # #order accepts arguments in one of several formats.
369
+ #
370
+ # === symbols
371
+ #
372
+ # The symbol represents the name of the column you want to order the results by.
333
373
  #
334
374
  # User.order(:name)
335
375
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
336
376
  #
377
+ # By default, the order is ascending. If you want descending order, you can
378
+ # map the column name symbol to +:desc+.
379
+ #
337
380
  # User.order(email: :desc)
338
381
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
339
382
  #
383
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
384
+ #
340
385
  # User.order(:name, email: :desc)
341
386
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
342
387
  #
388
+ # === strings
389
+ #
390
+ # Strings are passed directly to the database, allowing you to specify
391
+ # simple SQL expressions.
392
+ #
393
+ # This could be a source of SQL injection, so only strings composed of plain
394
+ # column names and simple <code>function(column_name)</code> expressions
395
+ # with optional +ASC+/+DESC+ modifiers are allowed.
396
+ #
343
397
  # User.order('name')
344
398
  # # SELECT "users".* FROM "users" ORDER BY name
345
399
  #
@@ -348,8 +402,21 @@ module ActiveRecord
348
402
  #
349
403
  # User.order('name DESC, email')
350
404
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
405
+ #
406
+ # === Arel
407
+ #
408
+ # If you need to pass in complicated expressions that you have verified
409
+ # are safe for the database, you can use Arel.
410
+ #
411
+ # User.order(Arel.sql('end_date - start_date'))
412
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
413
+ #
414
+ # Custom query syntax, like JSON columns for Postgres, is supported in this way.
415
+ #
416
+ # User.order(Arel.sql("payload->>'kind'"))
417
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
351
418
  def order(*args)
352
- check_if_method_has_arguments!(:order, args) do
419
+ check_if_method_has_arguments!(__callee__, args) do
353
420
  sanitize_order_arguments(args)
354
421
  end
355
422
  spawn.order!(*args)
@@ -362,6 +429,29 @@ module ActiveRecord
362
429
  self
363
430
  end
364
431
 
432
+ # Allows to specify an order by a specific set of values. Depending on your
433
+ # adapter this will either use a CASE statement or a built-in function.
434
+ #
435
+ # User.in_order_of(:id, [1, 5, 3])
436
+ # # SELECT "users".* FROM "users"
437
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
438
+ # # WHERE "users"."id" IN (1, 5, 3)
439
+ #
440
+ def in_order_of(column, values)
441
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
442
+ return spawn.none! if values.empty?
443
+
444
+ references = column_references([column])
445
+ self.references_values |= references unless references.empty?
446
+
447
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
448
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
449
+
450
+ spawn
451
+ .order!(connection.field_ordered_value(arel_column, values))
452
+ .where!(arel_column.in(values))
453
+ end
454
+
365
455
  # Replaces any existing order defined on the relation with the specified order.
366
456
  #
367
457
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -372,15 +462,15 @@ module ActiveRecord
372
462
  #
373
463
  # generates a query with 'ORDER BY id ASC, name ASC'.
374
464
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
465
+ check_if_method_has_arguments!(__callee__, args) do
466
+ sanitize_order_arguments(args)
377
467
  end
378
468
  spawn.reorder!(*args)
379
469
  end
380
470
 
381
471
  # Same as #reorder but operates on relation in-place instead of copying.
382
472
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
473
+ preprocess_order_args(args)
384
474
  args.uniq!
385
475
  self.reordering_value = true
386
476
  self.order_values = args
@@ -425,7 +515,7 @@ module ActiveRecord
425
515
  # has_many :comments, -> { unscope(where: :trashed) }
426
516
  #
427
517
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
518
+ check_if_method_has_arguments!(__callee__, args)
429
519
  spawn.unscope!(*args)
430
520
  end
431
521
 
@@ -458,7 +548,7 @@ module ActiveRecord
458
548
  self
459
549
  end
460
550
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
551
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
552
  # the association(s).
463
553
  #
464
554
  # User.joins(:posts)
@@ -487,7 +577,7 @@ module ActiveRecord
487
577
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
578
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
579
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
580
+ check_if_method_has_arguments!(__callee__, args)
491
581
  spawn.joins!(*args)
492
582
  end
493
583
 
@@ -496,7 +586,7 @@ module ActiveRecord
496
586
  self
497
587
  end
498
588
 
499
- # Performs a left outer joins on +args+:
589
+ # Performs LEFT OUTER JOINs on +args+:
500
590
  #
501
591
  # User.left_outer_joins(:posts)
502
592
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@@ -578,13 +668,13 @@ module ActiveRecord
578
668
  #
579
669
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
670
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
671
+ # User.where(name: "Joe", email: "joe@example.com")
582
672
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
673
  #
584
- # User.where({ name: ["Alice", "Bob"]})
674
+ # User.where(name: ["Alice", "Bob"])
585
675
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
676
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
677
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
678
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
679
  #
590
680
  # In the case of a belongs_to relationship, an association key can be used
@@ -614,8 +704,8 @@ module ActiveRecord
614
704
  #
615
705
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
706
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
707
+ # User.joins(:posts).where("posts.published" => true)
708
+ # User.joins(:posts).where(posts: { published: true })
619
709
  #
620
710
  # === no argument
621
711
  #
@@ -668,6 +758,59 @@ module ActiveRecord
668
758
  scope
669
759
  end
670
760
 
761
+ # Allows you to invert an entire where clause instead of manually applying conditions.
762
+ #
763
+ # class User
764
+ # scope :active, -> { where(accepted: true, locked: false) }
765
+ # end
766
+ #
767
+ # User.where(accepted: true)
768
+ # # WHERE `accepted` = 1
769
+ #
770
+ # User.where(accepted: true).invert_where
771
+ # # WHERE `accepted` != 1
772
+ #
773
+ # User.active
774
+ # # WHERE `accepted` = 1 AND `locked` = 0
775
+ #
776
+ # User.active.invert_where
777
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
778
+ #
779
+ # Be careful because this inverts all conditions before +invert_where+ call.
780
+ #
781
+ # class User
782
+ # scope :active, -> { where(accepted: true, locked: false) }
783
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
784
+ # end
785
+ #
786
+ # # It also inverts `where(role: 'admin')` unexpectedly.
787
+ # User.where(role: 'admin').inactive
788
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
789
+ #
790
+ def invert_where
791
+ spawn.invert_where!
792
+ end
793
+
794
+ def invert_where! # :nodoc:
795
+ self.where_clause = where_clause.invert
796
+ self
797
+ end
798
+
799
+ # Checks whether the given relation is structurally compatible with this relation, to determine
800
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
801
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
802
+ # #where (if no #group has been defined) or #having (if a #group is present).
803
+ #
804
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
805
+ # # => true
806
+ #
807
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
808
+ # # => false
809
+ #
810
+ def structurally_compatible?(other)
811
+ structurally_incompatible_values_for(other).empty?
812
+ end
813
+
671
814
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
815
  # as an argument.
673
816
  #
@@ -886,7 +1029,7 @@ module ActiveRecord
886
1029
  self
887
1030
  end
888
1031
 
889
- # Specifies table from which the records will be fetched. For example:
1032
+ # Specifies the table from which the records will be fetched. For example:
890
1033
  #
891
1034
  # Topic.select('title').from('posts')
892
1035
  # # SELECT title FROM posts
@@ -896,9 +1039,26 @@ module ActiveRecord
896
1039
  # Topic.select('title').from(Topic.approved)
897
1040
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1041
  #
1042
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1043
+ #
899
1044
  # Topic.select('a.title').from(Topic.approved, :a)
900
1045
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1046
  #
1047
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1048
+ #
1049
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1050
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1051
+ #
1052
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1053
+ #
1054
+ # color = "red"
1055
+ # Color
1056
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1057
+ # .where("colorvalue->>'color' = ?", color)
1058
+ # .select("c.*").to_a
1059
+ # # SELECT c.*
1060
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1061
+ # # WHERE (colorvalue->>'color' = 'red')
902
1062
  def from(value, subquery_name = nil)
903
1063
  spawn.from!(value, subquery_name)
904
1064
  end
@@ -994,7 +1154,7 @@ module ActiveRecord
994
1154
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1155
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1156
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1157
+ check_if_method_has_arguments!(__callee__, args)
998
1158
  spawn.optimizer_hints!(*args)
999
1159
  end
1000
1160
 
@@ -1036,7 +1196,7 @@ module ActiveRecord
1036
1196
  #
1037
1197
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1038
1198
  def annotate(*args)
1039
- check_if_method_has_arguments!(:annotate, args)
1199
+ check_if_method_has_arguments!(__callee__, args)
1040
1200
  spawn.annotate!(*args)
1041
1201
  end
1042
1202
 
@@ -1054,6 +1214,47 @@ module ActiveRecord
1054
1214
  self
1055
1215
  end
1056
1216
 
1217
+ # Excludes the specified record (or collection of records) from the resulting
1218
+ # relation. For example:
1219
+ #
1220
+ # Post.excluding(post)
1221
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1222
+ #
1223
+ # Post.excluding(post_one, post_two)
1224
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1225
+ #
1226
+ # This can also be called on associations. As with the above example, either
1227
+ # a single record of collection thereof may be specified:
1228
+ #
1229
+ # post = Post.find(1)
1230
+ # comment = Comment.find(2)
1231
+ # post.comments.excluding(comment)
1232
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1233
+ #
1234
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1235
+ #
1236
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1237
+ # specified, or if any of the records in the collection (if a collection
1238
+ # is passed in) are not instances of the same model that the relation is
1239
+ # scoping.
1240
+ def excluding(*records)
1241
+ records.flatten!(1)
1242
+ records.compact!
1243
+
1244
+ unless records.all?(klass)
1245
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1246
+ end
1247
+
1248
+ spawn.excluding!(records)
1249
+ end
1250
+ alias :without :excluding
1251
+
1252
+ def excluding!(records) # :nodoc:
1253
+ predicates = [ predicate_builder[primary_key, records].invert ]
1254
+ self.where_clause += Relation::WhereClause.new(predicates)
1255
+ self
1256
+ end
1257
+
1057
1258
  # Returns the Arel object associated with the relation.
1058
1259
  def arel(aliases = nil) # :nodoc:
1059
1260
  @arel ||= build_arel(aliases)
@@ -1109,11 +1310,9 @@ module ActiveRecord
1109
1310
  nil
1110
1311
  end
1111
1312
 
1112
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1313
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1113
1314
  join_dependencies.each do |join_dependency|
1114
- join_dependency.each do |join|
1115
- yield join
1116
- end
1315
+ join_dependency.each(&block)
1117
1316
  end
1118
1317
  end
1119
1318
 
@@ -1155,14 +1354,6 @@ module ActiveRecord
1155
1354
  unless annotate_values.empty?
1156
1355
  annotates = annotate_values
1157
1356
  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
1357
  arel.comment(*annotates)
1167
1358
  end
1168
1359
 
@@ -1170,8 +1361,7 @@ module ActiveRecord
1170
1361
  end
1171
1362
 
1172
1363
  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)
1364
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1175
1365
  end
1176
1366
 
1177
1367
  def build_from
@@ -1282,7 +1472,7 @@ module ActiveRecord
1282
1472
  def build_select(arel)
1283
1473
  if select_values.any?
1284
1474
  arel.project(*arel_columns(select_values))
1285
- elsif klass.ignored_columns.any?
1475
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1286
1476
  arel.project(*klass.column_names.map { |field| table[field] })
1287
1477
  else
1288
1478
  arel.project(table[Arel.star])
@@ -1423,12 +1613,17 @@ module ActiveRecord
1423
1613
  order_args.map! do |arg|
1424
1614
  klass.sanitize_sql_for_order(arg)
1425
1615
  end
1426
- order_args.flatten!
1427
- order_args.compact_blank!
1428
1616
  end
1429
1617
 
1430
1618
  def column_references(order_args)
1431
- references = order_args.grep(String)
1619
+ references = order_args.flat_map do |arg|
1620
+ case arg
1621
+ when String, Symbol
1622
+ arg
1623
+ when Hash
1624
+ arg.keys
1625
+ end
1626
+ end
1432
1627
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
1628
  references
1434
1629
  end
@@ -1476,19 +1671,19 @@ module ActiveRecord
1476
1671
  # Post.references() # raises an error
1477
1672
  # Post.references([]) # does not raise an error
1478
1673
  #
1479
- # This particular method should be called with a method_name and the args
1674
+ # This particular method should be called with a method_name (__callee__) and the args
1480
1675
  # passed into that method as an input. For example:
1481
1676
  #
1482
1677
  # def references(*args)
1483
- # check_if_method_has_arguments!("references", args)
1678
+ # check_if_method_has_arguments!(__callee__, args)
1484
1679
  # ...
1485
1680
  # end
1486
1681
  def check_if_method_has_arguments!(method_name, args, message = nil)
1487
1682
  if args.blank?
1488
1683
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
- elsif block_given?
1490
- yield args
1491
1684
  else
1685
+ yield args if block_given?
1686
+
1492
1687
  args.flatten!
1493
1688
  args.compact_blank!
1494
1689
  end
@@ -1512,11 +1707,4 @@ module ActiveRecord
1512
1707
  end
1513
1708
  end
1514
1709
  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
1710
  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