activerecord 6.1.7.2 → 7.0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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.