activerecord 6.1.6 → 7.0.4

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 (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1314 -975
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +19 -21
  17. data/lib/active_record/associations/collection_proxy.rb +10 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +124 -95
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +14 -15
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +10 -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 +80 -24
  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 +105 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -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 +20 -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 +19 -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 +51 -51
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -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 +37 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +49 -55
  94. data/lib/active_record/core.rb +124 -134
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -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 +15 -32
  100. data/lib/active_record/delegated_type.rb +53 -12
  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 +67 -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 +206 -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 +50 -43
  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 +20 -23
  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 +1 -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 +10 -4
  147. data/lib/active_record/log_subscriber.rb +23 -7
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +18 -6
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +7 -7
  152. data/lib/active_record/migration/compatibility.rb +84 -2
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +114 -83
  155. data/lib/active_record/model_schema.rb +58 -59
  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 +228 -60
  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 +16 -6
  163. data/lib/active_record/railtie.rb +136 -22
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +78 -136
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +73 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +6 -6
  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 +276 -67
  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 +189 -88
  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 +17 -12
  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 +60 -13
  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 +6 -1
  191. data/lib/active_record/signed_id.rb +3 -3
  192. data/lib/active_record/store.rb +7 -2
  193. data/lib/active_record/suppressor.rb +11 -15
  194. data/lib/active_record/tasks/database_tasks.rb +127 -60
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  197. data/lib/active_record/test_databases.rb +1 -1
  198. data/lib/active_record/test_fixtures.rb +16 -9
  199. data/lib/active_record/timestamp.rb +3 -4
  200. data/lib/active_record/transactions.rb +9 -14
  201. data/lib/active_record/translation.rb +3 -3
  202. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  203. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  204. data/lib/active_record/type/internal/timezone.rb +2 -2
  205. data/lib/active_record/type/serialized.rb +1 -1
  206. data/lib/active_record/type/type_map.rb +17 -20
  207. data/lib/active_record/type.rb +1 -2
  208. data/lib/active_record/validations/associated.rb +4 -4
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +4 -4
  211. data/lib/active_record/version.rb +1 -1
  212. data/lib/active_record.rb +217 -27
  213. data/lib/arel/attributes/attribute.rb +0 -8
  214. data/lib/arel/crud.rb +28 -22
  215. data/lib/arel/delete_manager.rb +18 -4
  216. data/lib/arel/filter_predications.rb +9 -0
  217. data/lib/arel/insert_manager.rb +2 -3
  218. data/lib/arel/nodes/casted.rb +1 -1
  219. data/lib/arel/nodes/delete_statement.rb +12 -13
  220. data/lib/arel/nodes/filter.rb +10 -0
  221. data/lib/arel/nodes/function.rb +1 -0
  222. data/lib/arel/nodes/insert_statement.rb +2 -2
  223. data/lib/arel/nodes/select_core.rb +2 -2
  224. data/lib/arel/nodes/select_statement.rb +2 -2
  225. data/lib/arel/nodes/update_statement.rb +8 -3
  226. data/lib/arel/nodes.rb +1 -0
  227. data/lib/arel/predications.rb +11 -3
  228. data/lib/arel/select_manager.rb +10 -4
  229. data/lib/arel/table.rb +0 -1
  230. data/lib/arel/tree_manager.rb +0 -12
  231. data/lib/arel/update_manager.rb +18 -4
  232. data/lib/arel/visitors/dot.rb +80 -90
  233. data/lib/arel/visitors/mysql.rb +8 -2
  234. data/lib/arel/visitors/postgresql.rb +0 -10
  235. data/lib/arel/visitors/to_sql.rb +58 -2
  236. data/lib/arel.rb +2 -1
  237. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  238. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  239. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  240. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  241. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  242. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  243. metadata +55 -11
@@ -8,14 +8,12 @@ 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
- # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
- # In this case, #where must be chained with #not to return a new relation.
13
+ # WhereChain objects act as placeholder for queries in which +where+ does not have any parameter.
14
+ # In this case, +where+ can be chained to return a new relation.
17
15
  class WhereChain
18
- def initialize(scope)
16
+ def initialize(scope) # :nodoc:
19
17
  @scope = scope
20
18
  end
21
19
 
@@ -42,6 +40,13 @@ module ActiveRecord
42
40
  #
43
41
  # User.where.not(name: "Jon", role: "admin")
44
42
  # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
43
+ #
44
+ # If there is a non-nil condition on a nullable column in the hash condition, the records that have
45
+ # nil values on the nullable column won't be returned.
46
+ # User.create!(nullable_country: nil)
47
+ # User.where.not(nullable_country: "UK")
48
+ # # SELECT * FROM users WHERE NOT (nullable_country = 'UK')
49
+ # # => []
45
50
  def not(opts, *rest)
46
51
  where_clause = @scope.send(:build_where_clause, opts, rest)
47
52
 
@@ -50,6 +55,34 @@ module ActiveRecord
50
55
  @scope
51
56
  end
52
57
 
58
+ # Returns a new relation with joins and where clause to identify
59
+ # associated relations.
60
+ #
61
+ # For example, posts that are associated to a related author:
62
+ #
63
+ # Post.where.associated(:author)
64
+ # # SELECT "posts".* FROM "posts"
65
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
66
+ # # WHERE "authors"."id" IS NOT NULL
67
+ #
68
+ # Additionally, multiple relations can be combined. This will return posts
69
+ # associated to both an author and any comments:
70
+ #
71
+ # Post.where.associated(:author, :comments)
72
+ # # SELECT "posts".* FROM "posts"
73
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
74
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
75
+ # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
76
+ def associated(*associations)
77
+ associations.each do |association|
78
+ reflection = scope_association_reflection(association)
79
+ @scope.joins!(association)
80
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
81
+ end
82
+
83
+ @scope
84
+ end
85
+
53
86
  # Returns a new relation with left outer joins and where clause to identify
54
87
  # missing relations.
55
88
  #
@@ -68,16 +101,24 @@ module ActiveRecord
68
101
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
102
  # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
103
  # # 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)
104
+ def missing(*associations)
105
+ associations.each do |association|
106
+ reflection = scope_association_reflection(association)
107
+ @scope.left_outer_joins!(association)
108
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
77
109
  end
78
110
 
79
111
  @scope
80
112
  end
113
+
114
+ private
115
+ def scope_association_reflection(association)
116
+ reflection = @scope.klass._reflect_on_association(association)
117
+ unless reflection
118
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
119
+ end
120
+ reflection
121
+ end
81
122
  end
82
123
 
83
124
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -128,7 +169,7 @@ module ActiveRecord
128
169
  #
129
170
  # users = User.includes(:address, friends: [:address, :followers])
130
171
  #
131
- # === conditions
172
+ # === Conditions
132
173
  #
133
174
  # If you want to add string conditions to your included models, you'll have
134
175
  # to explicitly reference them. For example:
@@ -148,7 +189,7 @@ module ActiveRecord
148
189
  #
149
190
  # User.includes(:posts).where(posts: { name: 'example' })
150
191
  def includes(*args)
151
- check_if_method_has_arguments!(:includes, args)
192
+ check_if_method_has_arguments!(__callee__, args)
152
193
  spawn.includes!(*args)
153
194
  end
154
195
 
@@ -164,7 +205,7 @@ module ActiveRecord
164
205
  # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
165
206
  # # "users"."id"
166
207
  def eager_load(*args)
167
- check_if_method_has_arguments!(:eager_load, args)
208
+ check_if_method_has_arguments!(__callee__, args)
168
209
  spawn.eager_load!(*args)
169
210
  end
170
211
 
@@ -178,7 +219,7 @@ module ActiveRecord
178
219
  # User.preload(:posts)
179
220
  # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
180
221
  def preload(*args)
181
- check_if_method_has_arguments!(:preload, args)
222
+ check_if_method_has_arguments!(__callee__, args)
182
223
  spawn.preload!(*args)
183
224
  end
184
225
 
@@ -211,7 +252,7 @@ module ActiveRecord
211
252
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
212
253
  # # Query now knows the string references posts, so adds a JOIN
213
254
  def references(*table_names)
214
- check_if_method_has_arguments!(:references, table_names)
255
+ check_if_method_has_arguments!(__callee__, table_names)
215
256
  spawn.references!(*table_names)
216
257
  end
217
258
 
@@ -269,7 +310,7 @@ module ActiveRecord
269
310
  return super()
270
311
  end
271
312
 
272
- check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
313
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
273
314
  spawn._select!(*fields)
274
315
  end
275
316
 
@@ -289,7 +330,7 @@ module ActiveRecord
289
330
  # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
331
  # Note that we're unscoping the entire select statement.
291
332
  def reselect(*args)
292
- check_if_method_has_arguments!(:reselect, args)
333
+ check_if_method_has_arguments!(__callee__, args)
293
334
  spawn.reselect!(*args)
294
335
  end
295
336
 
@@ -320,7 +361,7 @@ module ActiveRecord
320
361
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
321
362
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
322
363
  def group(*args)
323
- check_if_method_has_arguments!(:group, args)
364
+ check_if_method_has_arguments!(__callee__, args)
324
365
  spawn.group!(*args)
325
366
  end
326
367
 
@@ -329,17 +370,37 @@ module ActiveRecord
329
370
  self
330
371
  end
331
372
 
332
- # Allows to specify an order attribute:
373
+ # Applies an <code>ORDER BY</code> clause to a query.
374
+ #
375
+ # #order accepts arguments in one of several formats.
376
+ #
377
+ # === symbols
378
+ #
379
+ # The symbol represents the name of the column you want to order the results by.
333
380
  #
334
381
  # User.order(:name)
335
382
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
336
383
  #
384
+ # By default, the order is ascending. If you want descending order, you can
385
+ # map the column name symbol to +:desc+.
386
+ #
337
387
  # User.order(email: :desc)
338
388
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
339
389
  #
390
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
391
+ #
340
392
  # User.order(:name, email: :desc)
341
393
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
342
394
  #
395
+ # === strings
396
+ #
397
+ # Strings are passed directly to the database, allowing you to specify
398
+ # simple SQL expressions.
399
+ #
400
+ # This could be a source of SQL injection, so only strings composed of plain
401
+ # column names and simple <code>function(column_name)</code> expressions
402
+ # with optional +ASC+/+DESC+ modifiers are allowed.
403
+ #
343
404
  # User.order('name')
344
405
  # # SELECT "users".* FROM "users" ORDER BY name
345
406
  #
@@ -348,8 +409,21 @@ module ActiveRecord
348
409
  #
349
410
  # User.order('name DESC, email')
350
411
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
412
+ #
413
+ # === Arel
414
+ #
415
+ # If you need to pass in complicated expressions that you have verified
416
+ # are safe for the database, you can use Arel.
417
+ #
418
+ # User.order(Arel.sql('end_date - start_date'))
419
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
420
+ #
421
+ # Custom query syntax, like JSON columns for Postgres, is supported in this way.
422
+ #
423
+ # User.order(Arel.sql("payload->>'kind'"))
424
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
351
425
  def order(*args)
352
- check_if_method_has_arguments!(:order, args) do
426
+ check_if_method_has_arguments!(__callee__, args) do
353
427
  sanitize_order_arguments(args)
354
428
  end
355
429
  spawn.order!(*args)
@@ -362,6 +436,29 @@ module ActiveRecord
362
436
  self
363
437
  end
364
438
 
439
+ # Allows to specify an order by a specific set of values. Depending on your
440
+ # adapter this will either use a CASE statement or a built-in function.
441
+ #
442
+ # User.in_order_of(:id, [1, 5, 3])
443
+ # # SELECT "users".* FROM "users"
444
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
445
+ # # WHERE "users"."id" IN (1, 5, 3)
446
+ #
447
+ def in_order_of(column, values)
448
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
449
+ return spawn.none! if values.empty?
450
+
451
+ references = column_references([column])
452
+ self.references_values |= references unless references.empty?
453
+
454
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
455
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
456
+
457
+ spawn
458
+ .order!(connection.field_ordered_value(arel_column, values))
459
+ .where!(arel_column.in(values))
460
+ end
461
+
365
462
  # Replaces any existing order defined on the relation with the specified order.
366
463
  #
367
464
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -372,15 +469,15 @@ module ActiveRecord
372
469
  #
373
470
  # generates a query with 'ORDER BY id ASC, name ASC'.
374
471
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
472
+ check_if_method_has_arguments!(__callee__, args) do
473
+ sanitize_order_arguments(args)
377
474
  end
378
475
  spawn.reorder!(*args)
379
476
  end
380
477
 
381
478
  # Same as #reorder but operates on relation in-place instead of copying.
382
479
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
480
+ preprocess_order_args(args)
384
481
  args.uniq!
385
482
  self.reordering_value = true
386
483
  self.order_values = args
@@ -425,7 +522,7 @@ module ActiveRecord
425
522
  # has_many :comments, -> { unscope(where: :trashed) }
426
523
  #
427
524
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
525
+ check_if_method_has_arguments!(__callee__, args)
429
526
  spawn.unscope!(*args)
430
527
  end
431
528
 
@@ -458,7 +555,7 @@ module ActiveRecord
458
555
  self
459
556
  end
460
557
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
558
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
559
  # the association(s).
463
560
  #
464
561
  # User.joins(:posts)
@@ -487,7 +584,7 @@ module ActiveRecord
487
584
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
585
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
586
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
587
+ check_if_method_has_arguments!(__callee__, args)
491
588
  spawn.joins!(*args)
492
589
  end
493
590
 
@@ -496,7 +593,7 @@ module ActiveRecord
496
593
  self
497
594
  end
498
595
 
499
- # Performs a left outer joins on +args+:
596
+ # Performs LEFT OUTER JOINs on +args+:
500
597
  #
501
598
  # User.left_outer_joins(:posts)
502
599
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@@ -578,13 +675,13 @@ module ActiveRecord
578
675
  #
579
676
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
677
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
678
+ # User.where(name: "Joe", email: "joe@example.com")
582
679
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
680
  #
584
- # User.where({ name: ["Alice", "Bob"]})
681
+ # User.where(name: ["Alice", "Bob"])
585
682
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
683
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
684
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
685
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
686
  #
590
687
  # In the case of a belongs_to relationship, an association key can be used
@@ -614,18 +711,32 @@ module ActiveRecord
614
711
  #
615
712
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
713
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
714
+ # User.joins(:posts).where("posts.published" => true)
715
+ # User.joins(:posts).where(posts: { published: true })
619
716
  #
620
717
  # === no argument
621
718
  #
622
719
  # If no argument is passed, #where returns a new instance of WhereChain, that
623
- # can be chained with #not to return a new relation that negates the where clause.
720
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
721
+ #
722
+ # Chaining with WhereChain#not:
624
723
  #
625
724
  # User.where.not(name: "Jon")
626
725
  # # SELECT * FROM users WHERE name != 'Jon'
627
726
  #
628
- # See WhereChain for more details on #not.
727
+ # Chaining with WhereChain#associated:
728
+ #
729
+ # Post.where.associated(:author)
730
+ # # SELECT "posts".* FROM "posts"
731
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
732
+ # # WHERE "authors"."id" IS NOT NULL
733
+ #
734
+ # Chaining with WhereChain#missing:
735
+ #
736
+ # Post.where.missing(:author)
737
+ # # SELECT "posts".* FROM "posts"
738
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
739
+ # # WHERE "authors"."id" IS NULL
629
740
  #
630
741
  # === blank condition
631
742
  #
@@ -668,6 +779,59 @@ module ActiveRecord
668
779
  scope
669
780
  end
670
781
 
782
+ # Allows you to invert an entire where clause instead of manually applying conditions.
783
+ #
784
+ # class User
785
+ # scope :active, -> { where(accepted: true, locked: false) }
786
+ # end
787
+ #
788
+ # User.where(accepted: true)
789
+ # # WHERE `accepted` = 1
790
+ #
791
+ # User.where(accepted: true).invert_where
792
+ # # WHERE `accepted` != 1
793
+ #
794
+ # User.active
795
+ # # WHERE `accepted` = 1 AND `locked` = 0
796
+ #
797
+ # User.active.invert_where
798
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
799
+ #
800
+ # Be careful because this inverts all conditions before +invert_where+ call.
801
+ #
802
+ # class User
803
+ # scope :active, -> { where(accepted: true, locked: false) }
804
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
805
+ # end
806
+ #
807
+ # # It also inverts `where(role: 'admin')` unexpectedly.
808
+ # User.where(role: 'admin').inactive
809
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
810
+ #
811
+ def invert_where
812
+ spawn.invert_where!
813
+ end
814
+
815
+ def invert_where! # :nodoc:
816
+ self.where_clause = where_clause.invert
817
+ self
818
+ end
819
+
820
+ # Checks whether the given relation is structurally compatible with this relation, to determine
821
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
822
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
823
+ # #where (if no #group has been defined) or #having (if a #group is present).
824
+ #
825
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
826
+ # # => true
827
+ #
828
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
829
+ # # => false
830
+ #
831
+ def structurally_compatible?(other)
832
+ structurally_incompatible_values_for(other).empty?
833
+ end
834
+
671
835
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
836
  # as an argument.
673
837
  #
@@ -886,7 +1050,7 @@ module ActiveRecord
886
1050
  self
887
1051
  end
888
1052
 
889
- # Specifies table from which the records will be fetched. For example:
1053
+ # Specifies the table from which the records will be fetched. For example:
890
1054
  #
891
1055
  # Topic.select('title').from('posts')
892
1056
  # # SELECT title FROM posts
@@ -896,9 +1060,26 @@ module ActiveRecord
896
1060
  # Topic.select('title').from(Topic.approved)
897
1061
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1062
  #
1063
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1064
+ #
899
1065
  # Topic.select('a.title').from(Topic.approved, :a)
900
1066
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1067
  #
1068
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1069
+ #
1070
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1071
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1072
+ #
1073
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1074
+ #
1075
+ # color = "red"
1076
+ # Color
1077
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1078
+ # .where("colorvalue->>'color' = ?", color)
1079
+ # .select("c.*").to_a
1080
+ # # SELECT c.*
1081
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1082
+ # # WHERE (colorvalue->>'color' = 'red')
902
1083
  def from(value, subquery_name = nil)
903
1084
  spawn.from!(value, subquery_name)
904
1085
  end
@@ -994,7 +1175,7 @@ module ActiveRecord
994
1175
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1176
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1177
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1178
+ check_if_method_has_arguments!(__callee__, args)
998
1179
  spawn.optimizer_hints!(*args)
999
1180
  end
1000
1181
 
@@ -1036,7 +1217,7 @@ module ActiveRecord
1036
1217
  #
1037
1218
  # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1038
1219
  def annotate(*args)
1039
- check_if_method_has_arguments!(:annotate, args)
1220
+ check_if_method_has_arguments!(__callee__, args)
1040
1221
  spawn.annotate!(*args)
1041
1222
  end
1042
1223
 
@@ -1054,6 +1235,47 @@ module ActiveRecord
1054
1235
  self
1055
1236
  end
1056
1237
 
1238
+ # Excludes the specified record (or collection of records) from the resulting
1239
+ # relation. For example:
1240
+ #
1241
+ # Post.excluding(post)
1242
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1243
+ #
1244
+ # Post.excluding(post_one, post_two)
1245
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1246
+ #
1247
+ # This can also be called on associations. As with the above example, either
1248
+ # a single record of collection thereof may be specified:
1249
+ #
1250
+ # post = Post.find(1)
1251
+ # comment = Comment.find(2)
1252
+ # post.comments.excluding(comment)
1253
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1254
+ #
1255
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1256
+ #
1257
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1258
+ # specified, or if any of the records in the collection (if a collection
1259
+ # is passed in) are not instances of the same model that the relation is
1260
+ # scoping.
1261
+ def excluding(*records)
1262
+ records.flatten!(1)
1263
+ records.compact!
1264
+
1265
+ unless records.all?(klass)
1266
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1267
+ end
1268
+
1269
+ spawn.excluding!(records)
1270
+ end
1271
+ alias :without :excluding
1272
+
1273
+ def excluding!(records) # :nodoc:
1274
+ predicates = [ predicate_builder[primary_key, records].invert ]
1275
+ self.where_clause += Relation::WhereClause.new(predicates)
1276
+ self
1277
+ end
1278
+
1057
1279
  # Returns the Arel object associated with the relation.
1058
1280
  def arel(aliases = nil) # :nodoc:
1059
1281
  @arel ||= build_arel(aliases)
@@ -1109,11 +1331,9 @@ module ActiveRecord
1109
1331
  nil
1110
1332
  end
1111
1333
 
1112
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1334
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1113
1335
  join_dependencies.each do |join_dependency|
1114
- join_dependency.each do |join|
1115
- yield join
1116
- end
1336
+ join_dependency.each(&block)
1117
1337
  end
1118
1338
  end
1119
1339
 
@@ -1155,14 +1375,6 @@ module ActiveRecord
1155
1375
  unless annotate_values.empty?
1156
1376
  annotates = annotate_values
1157
1377
  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 7.0.
1161
- To migrate to Rails 7.0'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
1378
  arel.comment(*annotates)
1167
1379
  end
1168
1380
 
@@ -1170,8 +1382,7 @@ module ActiveRecord
1170
1382
  end
1171
1383
 
1172
1384
  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)
1385
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1175
1386
  end
1176
1387
 
1177
1388
  def build_from
@@ -1282,7 +1493,7 @@ module ActiveRecord
1282
1493
  def build_select(arel)
1283
1494
  if select_values.any?
1284
1495
  arel.project(*arel_columns(select_values))
1285
- elsif klass.ignored_columns.any?
1496
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1286
1497
  arel.project(*klass.column_names.map { |field| table[field] })
1287
1498
  else
1288
1499
  arel.project(table[Arel.star])
@@ -1423,12 +1634,17 @@ module ActiveRecord
1423
1634
  order_args.map! do |arg|
1424
1635
  klass.sanitize_sql_for_order(arg)
1425
1636
  end
1426
- order_args.flatten!
1427
- order_args.compact_blank!
1428
1637
  end
1429
1638
 
1430
1639
  def column_references(order_args)
1431
- references = order_args.grep(String)
1640
+ references = order_args.flat_map do |arg|
1641
+ case arg
1642
+ when String, Symbol
1643
+ arg
1644
+ when Hash
1645
+ arg.keys
1646
+ end
1647
+ end
1432
1648
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
1649
  references
1434
1650
  end
@@ -1476,19 +1692,19 @@ module ActiveRecord
1476
1692
  # Post.references() # raises an error
1477
1693
  # Post.references([]) # does not raise an error
1478
1694
  #
1479
- # This particular method should be called with a method_name and the args
1695
+ # This particular method should be called with a method_name (__callee__) and the args
1480
1696
  # passed into that method as an input. For example:
1481
1697
  #
1482
1698
  # def references(*args)
1483
- # check_if_method_has_arguments!("references", args)
1699
+ # check_if_method_has_arguments!(__callee__, args)
1484
1700
  # ...
1485
1701
  # end
1486
1702
  def check_if_method_has_arguments!(method_name, args, message = nil)
1487
1703
  if args.blank?
1488
1704
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
- elsif block_given?
1490
- yield args
1491
1705
  else
1706
+ yield args if block_given?
1707
+
1492
1708
  args.flatten!
1493
1709
  args.compact_blank!
1494
1710
  end
@@ -1512,11 +1728,4 @@ module ActiveRecord
1512
1728
  end
1513
1729
  end
1514
1730
  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
1731
  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.