activerecord 6.1.7.2 → 7.0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1295 -1007
  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 +4 -8
  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 +71 -71
  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 +206 -105
  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 +123 -148
  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 +149 -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 +1 -1
  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 +9 -6
  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 +225 -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 +53 -9
@@ -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
 
@@ -1038,7 +1219,7 @@ module ActiveRecord
1038
1219
  #
1039
1220
  # Some escaping is performed, however untrusted user input should not be used.
1040
1221
  def annotate(*args)
1041
- check_if_method_has_arguments!(:annotate, args)
1222
+ check_if_method_has_arguments!(__callee__, args)
1042
1223
  spawn.annotate!(*args)
1043
1224
  end
1044
1225
 
@@ -1056,6 +1237,47 @@ module ActiveRecord
1056
1237
  self
1057
1238
  end
1058
1239
 
1240
+ # Excludes the specified record (or collection of records) from the resulting
1241
+ # relation. For example:
1242
+ #
1243
+ # Post.excluding(post)
1244
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1245
+ #
1246
+ # Post.excluding(post_one, post_two)
1247
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1248
+ #
1249
+ # This can also be called on associations. As with the above example, either
1250
+ # a single record of collection thereof may be specified:
1251
+ #
1252
+ # post = Post.find(1)
1253
+ # comment = Comment.find(2)
1254
+ # post.comments.excluding(comment)
1255
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1256
+ #
1257
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1258
+ #
1259
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1260
+ # specified, or if any of the records in the collection (if a collection
1261
+ # is passed in) are not instances of the same model that the relation is
1262
+ # scoping.
1263
+ def excluding(*records)
1264
+ records.flatten!(1)
1265
+ records.compact!
1266
+
1267
+ unless records.all?(klass)
1268
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1269
+ end
1270
+
1271
+ spawn.excluding!(records)
1272
+ end
1273
+ alias :without :excluding
1274
+
1275
+ def excluding!(records) # :nodoc:
1276
+ predicates = [ predicate_builder[primary_key, records].invert ]
1277
+ self.where_clause += Relation::WhereClause.new(predicates)
1278
+ self
1279
+ end
1280
+
1059
1281
  # Returns the Arel object associated with the relation.
1060
1282
  def arel(aliases = nil) # :nodoc:
1061
1283
  @arel ||= build_arel(aliases)
@@ -1111,11 +1333,9 @@ module ActiveRecord
1111
1333
  nil
1112
1334
  end
1113
1335
 
1114
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1336
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1115
1337
  join_dependencies.each do |join_dependency|
1116
- join_dependency.each do |join|
1117
- yield join
1118
- end
1338
+ join_dependency.each(&block)
1119
1339
  end
1120
1340
  end
1121
1341
 
@@ -1157,14 +1377,6 @@ module ActiveRecord
1157
1377
  unless annotate_values.empty?
1158
1378
  annotates = annotate_values
1159
1379
  annotates = annotates.uniq if annotates.size > 1
1160
- unless annotates == annotate_values
1161
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
1162
- Duplicated query annotations are no longer shown in queries in Rails 7.0.
1163
- To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1164
- (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1165
- MSG
1166
- annotates = annotate_values
1167
- end
1168
1380
  arel.comment(*annotates)
1169
1381
  end
1170
1382
 
@@ -1172,8 +1384,7 @@ module ActiveRecord
1172
1384
  end
1173
1385
 
1174
1386
  def build_cast_value(name, value)
1175
- cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1176
- Arel::Nodes::BindParam.new(cast_value)
1387
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1177
1388
  end
1178
1389
 
1179
1390
  def build_from
@@ -1284,7 +1495,7 @@ module ActiveRecord
1284
1495
  def build_select(arel)
1285
1496
  if select_values.any?
1286
1497
  arel.project(*arel_columns(select_values))
1287
- elsif klass.ignored_columns.any?
1498
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1288
1499
  arel.project(*klass.column_names.map { |field| table[field] })
1289
1500
  else
1290
1501
  arel.project(table[Arel.star])
@@ -1425,12 +1636,17 @@ module ActiveRecord
1425
1636
  order_args.map! do |arg|
1426
1637
  klass.sanitize_sql_for_order(arg)
1427
1638
  end
1428
- order_args.flatten!
1429
- order_args.compact_blank!
1430
1639
  end
1431
1640
 
1432
1641
  def column_references(order_args)
1433
- references = order_args.grep(String)
1642
+ references = order_args.flat_map do |arg|
1643
+ case arg
1644
+ when String, Symbol
1645
+ arg
1646
+ when Hash
1647
+ arg.keys
1648
+ end
1649
+ end
1434
1650
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1435
1651
  references
1436
1652
  end
@@ -1478,19 +1694,19 @@ module ActiveRecord
1478
1694
  # Post.references() # raises an error
1479
1695
  # Post.references([]) # does not raise an error
1480
1696
  #
1481
- # This particular method should be called with a method_name and the args
1697
+ # This particular method should be called with a method_name (__callee__) and the args
1482
1698
  # passed into that method as an input. For example:
1483
1699
  #
1484
1700
  # def references(*args)
1485
- # check_if_method_has_arguments!("references", args)
1701
+ # check_if_method_has_arguments!(__callee__, args)
1486
1702
  # ...
1487
1703
  # end
1488
1704
  def check_if_method_has_arguments!(method_name, args, message = nil)
1489
1705
  if args.blank?
1490
1706
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1491
- elsif block_given?
1492
- yield args
1493
1707
  else
1708
+ yield args if block_given?
1709
+
1494
1710
  args.flatten!
1495
1711
  args.compact_blank!
1496
1712
  end
@@ -1514,11 +1730,4 @@ module ActiveRecord
1514
1730
  end
1515
1731
  end
1516
1732
  end
1517
-
1518
- class Relation # :nodoc:
1519
- # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1520
- # TODO: Remove the class once Rails 6.1 has released.
1521
- class WhereClauseFactory # :nodoc:
1522
- end
1523
- end
1524
1733
  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.