activerecord 6.1.7.6 → 7.0.8

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1570 -1016
  3. data/README.rdoc +3 -3
  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 +20 -22
  17. data/lib/active_record/associations/collection_proxy.rb +15 -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 +50 -14
  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 +138 -100
  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 +8 -6
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +19 -22
  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 +14 -16
  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 +52 -23
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +82 -25
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +144 -82
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +115 -85
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +37 -25
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -23
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +4 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  70. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -1
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -17
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/quoting.rb +76 -73
  83. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  86. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +40 -21
  88. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  89. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -106
  90. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +33 -18
  93. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -0
  94. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +19 -17
  95. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +98 -36
  96. data/lib/active_record/connection_adapters.rb +6 -5
  97. data/lib/active_record/connection_handling.rb +49 -55
  98. data/lib/active_record/core.rb +123 -148
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  100. data/lib/active_record/database_configurations/database_config.rb +12 -9
  101. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  102. data/lib/active_record/database_configurations/url_config.rb +2 -2
  103. data/lib/active_record/database_configurations.rb +15 -32
  104. data/lib/active_record/delegated_type.rb +53 -12
  105. data/lib/active_record/destroy_association_async_job.rb +1 -1
  106. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  107. data/lib/active_record/dynamic_matchers.rb +1 -1
  108. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  109. data/lib/active_record/encryption/cipher.rb +53 -0
  110. data/lib/active_record/encryption/config.rb +44 -0
  111. data/lib/active_record/encryption/configurable.rb +67 -0
  112. data/lib/active_record/encryption/context.rb +35 -0
  113. data/lib/active_record/encryption/contexts.rb +72 -0
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  116. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  118. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  119. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  120. data/lib/active_record/encryption/encryptor.rb +155 -0
  121. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  122. data/lib/active_record/encryption/errors.rb +15 -0
  123. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  124. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  125. data/lib/active_record/encryption/key.rb +28 -0
  126. data/lib/active_record/encryption/key_generator.rb +42 -0
  127. data/lib/active_record/encryption/key_provider.rb +46 -0
  128. data/lib/active_record/encryption/message.rb +33 -0
  129. data/lib/active_record/encryption/message_serializer.rb +90 -0
  130. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  131. data/lib/active_record/encryption/properties.rb +76 -0
  132. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  133. data/lib/active_record/encryption/scheme.rb +99 -0
  134. data/lib/active_record/encryption.rb +55 -0
  135. data/lib/active_record/enum.rb +50 -43
  136. data/lib/active_record/errors.rb +67 -4
  137. data/lib/active_record/explain_registry.rb +11 -6
  138. data/lib/active_record/explain_subscriber.rb +1 -1
  139. data/lib/active_record/fixture_set/file.rb +15 -1
  140. data/lib/active_record/fixture_set/table_row.rb +41 -6
  141. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  142. data/lib/active_record/fixtures.rb +20 -23
  143. data/lib/active_record/future_result.rb +139 -0
  144. data/lib/active_record/gem_version.rb +5 -5
  145. data/lib/active_record/inheritance.rb +55 -17
  146. data/lib/active_record/insert_all.rb +80 -14
  147. data/lib/active_record/integration.rb +4 -3
  148. data/lib/active_record/internal_metadata.rb +1 -5
  149. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  150. data/lib/active_record/locking/optimistic.rb +36 -21
  151. data/lib/active_record/locking/pessimistic.rb +10 -4
  152. data/lib/active_record/log_subscriber.rb +23 -7
  153. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  154. data/lib/active_record/middleware/database_selector.rb +18 -6
  155. data/lib/active_record/middleware/shard_selector.rb +60 -0
  156. data/lib/active_record/migration/command_recorder.rb +8 -9
  157. data/lib/active_record/migration/compatibility.rb +93 -46
  158. data/lib/active_record/migration/join_table.rb +1 -1
  159. data/lib/active_record/migration.rb +167 -87
  160. data/lib/active_record/model_schema.rb +58 -59
  161. data/lib/active_record/nested_attributes.rb +13 -12
  162. data/lib/active_record/no_touching.rb +3 -3
  163. data/lib/active_record/null_relation.rb +2 -6
  164. data/lib/active_record/persistence.rb +231 -61
  165. data/lib/active_record/query_cache.rb +2 -2
  166. data/lib/active_record/query_logs.rb +149 -0
  167. data/lib/active_record/querying.rb +16 -6
  168. data/lib/active_record/railtie.rb +136 -22
  169. data/lib/active_record/railties/controller_runtime.rb +4 -5
  170. data/lib/active_record/railties/databases.rake +78 -136
  171. data/lib/active_record/readonly_attributes.rb +11 -0
  172. data/lib/active_record/reflection.rb +80 -49
  173. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  174. data/lib/active_record/relation/batches.rb +6 -6
  175. data/lib/active_record/relation/calculations.rb +92 -60
  176. data/lib/active_record/relation/delegation.rb +7 -7
  177. data/lib/active_record/relation/finder_methods.rb +31 -35
  178. data/lib/active_record/relation/merger.rb +20 -13
  179. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  180. data/lib/active_record/relation/predicate_builder.rb +1 -6
  181. data/lib/active_record/relation/query_attribute.rb +28 -11
  182. data/lib/active_record/relation/query_methods.rb +304 -68
  183. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  184. data/lib/active_record/relation/spawn_methods.rb +2 -2
  185. data/lib/active_record/relation/where_clause.rb +10 -19
  186. data/lib/active_record/relation.rb +189 -88
  187. data/lib/active_record/result.rb +23 -11
  188. data/lib/active_record/runtime_registry.rb +9 -13
  189. data/lib/active_record/sanitization.rb +17 -12
  190. data/lib/active_record/schema.rb +38 -23
  191. data/lib/active_record/schema_dumper.rb +29 -19
  192. data/lib/active_record/schema_migration.rb +4 -4
  193. data/lib/active_record/scoping/default.rb +60 -13
  194. data/lib/active_record/scoping/named.rb +3 -11
  195. data/lib/active_record/scoping.rb +64 -34
  196. data/lib/active_record/serialization.rb +6 -1
  197. data/lib/active_record/signed_id.rb +3 -3
  198. data/lib/active_record/store.rb +2 -2
  199. data/lib/active_record/suppressor.rb +11 -15
  200. data/lib/active_record/table_metadata.rb +6 -2
  201. data/lib/active_record/tasks/database_tasks.rb +127 -60
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  204. data/lib/active_record/test_databases.rb +1 -1
  205. data/lib/active_record/test_fixtures.rb +9 -6
  206. data/lib/active_record/timestamp.rb +3 -4
  207. data/lib/active_record/transactions.rb +12 -17
  208. data/lib/active_record/translation.rb +3 -3
  209. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  210. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  211. data/lib/active_record/type/internal/timezone.rb +2 -2
  212. data/lib/active_record/type/serialized.rb +9 -5
  213. data/lib/active_record/type/type_map.rb +17 -20
  214. data/lib/active_record/type.rb +1 -2
  215. data/lib/active_record/validations/associated.rb +4 -4
  216. data/lib/active_record/validations/presence.rb +2 -2
  217. data/lib/active_record/validations/uniqueness.rb +4 -4
  218. data/lib/active_record/version.rb +1 -1
  219. data/lib/active_record.rb +225 -27
  220. data/lib/arel/attributes/attribute.rb +0 -8
  221. data/lib/arel/crud.rb +28 -22
  222. data/lib/arel/delete_manager.rb +18 -4
  223. data/lib/arel/filter_predications.rb +9 -0
  224. data/lib/arel/insert_manager.rb +2 -3
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/casted.rb +1 -1
  227. data/lib/arel/nodes/delete_statement.rb +12 -13
  228. data/lib/arel/nodes/filter.rb +10 -0
  229. data/lib/arel/nodes/function.rb +1 -0
  230. data/lib/arel/nodes/insert_statement.rb +2 -2
  231. data/lib/arel/nodes/select_core.rb +2 -2
  232. data/lib/arel/nodes/select_statement.rb +2 -2
  233. data/lib/arel/nodes/update_statement.rb +8 -3
  234. data/lib/arel/nodes.rb +1 -0
  235. data/lib/arel/predications.rb +11 -3
  236. data/lib/arel/select_manager.rb +10 -4
  237. data/lib/arel/table.rb +0 -1
  238. data/lib/arel/tree_manager.rb +0 -12
  239. data/lib/arel/update_manager.rb +18 -4
  240. data/lib/arel/visitors/dot.rb +80 -90
  241. data/lib/arel/visitors/mysql.rb +8 -2
  242. data/lib/arel/visitors/postgresql.rb +0 -10
  243. data/lib/arel/visitors/to_sql.rb +58 -2
  244. data/lib/arel.rb +2 -1
  245. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  247. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  248. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  249. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  250. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  251. 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,38 @@ 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
+ if reflection.options[:class_name]
81
+ self.not(association => { reflection.association_primary_key => nil })
82
+ else
83
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
84
+ end
85
+ end
86
+
87
+ @scope
88
+ end
89
+
53
90
  # Returns a new relation with left outer joins and where clause to identify
54
91
  # missing relations.
55
92
  #
@@ -68,16 +105,28 @@ module ActiveRecord
68
105
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
106
  # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
107
  # # 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)
108
+ def missing(*associations)
109
+ associations.each do |association|
110
+ reflection = scope_association_reflection(association)
111
+ @scope.left_outer_joins!(association)
112
+ if reflection.options[:class_name]
113
+ @scope.where!(association => { reflection.association_primary_key => nil })
114
+ else
115
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
116
+ end
77
117
  end
78
118
 
79
119
  @scope
80
120
  end
121
+
122
+ private
123
+ def scope_association_reflection(association)
124
+ reflection = @scope.klass._reflect_on_association(association)
125
+ unless reflection
126
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
127
+ end
128
+ reflection
129
+ end
81
130
  end
82
131
 
83
132
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -128,7 +177,7 @@ module ActiveRecord
128
177
  #
129
178
  # users = User.includes(:address, friends: [:address, :followers])
130
179
  #
131
- # === conditions
180
+ # === Conditions
132
181
  #
133
182
  # If you want to add string conditions to your included models, you'll have
134
183
  # to explicitly reference them. For example:
@@ -148,7 +197,7 @@ module ActiveRecord
148
197
  #
149
198
  # User.includes(:posts).where(posts: { name: 'example' })
150
199
  def includes(*args)
151
- check_if_method_has_arguments!(:includes, args)
200
+ check_if_method_has_arguments!(__callee__, args)
152
201
  spawn.includes!(*args)
153
202
  end
154
203
 
@@ -164,7 +213,7 @@ module ActiveRecord
164
213
  # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
165
214
  # # "users"."id"
166
215
  def eager_load(*args)
167
- check_if_method_has_arguments!(:eager_load, args)
216
+ check_if_method_has_arguments!(__callee__, args)
168
217
  spawn.eager_load!(*args)
169
218
  end
170
219
 
@@ -178,7 +227,7 @@ module ActiveRecord
178
227
  # User.preload(:posts)
179
228
  # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
180
229
  def preload(*args)
181
- check_if_method_has_arguments!(:preload, args)
230
+ check_if_method_has_arguments!(__callee__, args)
182
231
  spawn.preload!(*args)
183
232
  end
184
233
 
@@ -211,7 +260,7 @@ module ActiveRecord
211
260
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
212
261
  # # Query now knows the string references posts, so adds a JOIN
213
262
  def references(*table_names)
214
- check_if_method_has_arguments!(:references, table_names)
263
+ check_if_method_has_arguments!(__callee__, table_names)
215
264
  spawn.references!(*table_names)
216
265
  end
217
266
 
@@ -248,7 +297,7 @@ module ActiveRecord
248
297
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
249
298
  #
250
299
  # Model.select('field AS field_one', 'other_field AS field_two')
251
- # # => [#<Model id: nil, field: "value", other_field: "value">]
300
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
252
301
  #
253
302
  # If an alias was specified, it will be accessible from the resulting objects:
254
303
  #
@@ -269,7 +318,7 @@ module ActiveRecord
269
318
  return super()
270
319
  end
271
320
 
272
- check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
321
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
273
322
  spawn._select!(*fields)
274
323
  end
275
324
 
@@ -289,7 +338,7 @@ module ActiveRecord
289
338
  # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
339
  # Note that we're unscoping the entire select statement.
291
340
  def reselect(*args)
292
- check_if_method_has_arguments!(:reselect, args)
341
+ check_if_method_has_arguments!(__callee__, args)
293
342
  spawn.reselect!(*args)
294
343
  end
295
344
 
@@ -320,7 +369,7 @@ module ActiveRecord
320
369
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
321
370
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
322
371
  def group(*args)
323
- check_if_method_has_arguments!(:group, args)
372
+ check_if_method_has_arguments!(__callee__, args)
324
373
  spawn.group!(*args)
325
374
  end
326
375
 
@@ -329,17 +378,37 @@ module ActiveRecord
329
378
  self
330
379
  end
331
380
 
332
- # Allows to specify an order attribute:
381
+ # Applies an <code>ORDER BY</code> clause to a query.
382
+ #
383
+ # #order accepts arguments in one of several formats.
384
+ #
385
+ # === symbols
386
+ #
387
+ # The symbol represents the name of the column you want to order the results by.
333
388
  #
334
389
  # User.order(:name)
335
390
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
336
391
  #
392
+ # By default, the order is ascending. If you want descending order, you can
393
+ # map the column name symbol to +:desc+.
394
+ #
337
395
  # User.order(email: :desc)
338
396
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
339
397
  #
398
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
399
+ #
340
400
  # User.order(:name, email: :desc)
341
401
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
342
402
  #
403
+ # === strings
404
+ #
405
+ # Strings are passed directly to the database, allowing you to specify
406
+ # simple SQL expressions.
407
+ #
408
+ # This could be a source of SQL injection, so only strings composed of plain
409
+ # column names and simple <code>function(column_name)</code> expressions
410
+ # with optional +ASC+/+DESC+ modifiers are allowed.
411
+ #
343
412
  # User.order('name')
344
413
  # # SELECT "users".* FROM "users" ORDER BY name
345
414
  #
@@ -348,8 +417,21 @@ module ActiveRecord
348
417
  #
349
418
  # User.order('name DESC, email')
350
419
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
420
+ #
421
+ # === Arel
422
+ #
423
+ # If you need to pass in complicated expressions that you have verified
424
+ # are safe for the database, you can use Arel.
425
+ #
426
+ # User.order(Arel.sql('end_date - start_date'))
427
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
428
+ #
429
+ # Custom query syntax, like JSON columns for Postgres, is supported in this way.
430
+ #
431
+ # User.order(Arel.sql("payload->>'kind'"))
432
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
351
433
  def order(*args)
352
- check_if_method_has_arguments!(:order, args) do
434
+ check_if_method_has_arguments!(__callee__, args) do
353
435
  sanitize_order_arguments(args)
354
436
  end
355
437
  spawn.order!(*args)
@@ -362,6 +444,39 @@ module ActiveRecord
362
444
  self
363
445
  end
364
446
 
447
+ # Allows to specify an order by a specific set of values.
448
+ #
449
+ # User.in_order_of(:id, [1, 5, 3])
450
+ # # SELECT "users".* FROM "users"
451
+ # # WHERE "users"."id" IN (1, 5, 3)
452
+ # # ORDER BY CASE
453
+ # # WHEN "users"."id" = 1 THEN 1
454
+ # # WHEN "users"."id" = 5 THEN 2
455
+ # # WHEN "users"."id" = 3 THEN 3
456
+ # # END ASC
457
+ #
458
+ def in_order_of(column, values)
459
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
460
+ return spawn.none! if values.empty?
461
+
462
+ references = column_references([column])
463
+ self.references_values |= references unless references.empty?
464
+
465
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
466
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
467
+
468
+ where_clause =
469
+ if values.include?(nil)
470
+ arel_column.in(values.compact).or(arel_column.eq(nil))
471
+ else
472
+ arel_column.in(values)
473
+ end
474
+
475
+ spawn
476
+ .order!(build_case_for_value_position(arel_column, values))
477
+ .where!(where_clause)
478
+ end
479
+
365
480
  # Replaces any existing order defined on the relation with the specified order.
366
481
  #
367
482
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -372,15 +487,15 @@ module ActiveRecord
372
487
  #
373
488
  # generates a query with 'ORDER BY id ASC, name ASC'.
374
489
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
490
+ check_if_method_has_arguments!(__callee__, args) do
491
+ sanitize_order_arguments(args)
377
492
  end
378
493
  spawn.reorder!(*args)
379
494
  end
380
495
 
381
496
  # Same as #reorder but operates on relation in-place instead of copying.
382
497
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
498
+ preprocess_order_args(args)
384
499
  args.uniq!
385
500
  self.reordering_value = true
386
501
  self.order_values = args
@@ -425,7 +540,7 @@ module ActiveRecord
425
540
  # has_many :comments, -> { unscope(where: :trashed) }
426
541
  #
427
542
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
543
+ check_if_method_has_arguments!(__callee__, args)
429
544
  spawn.unscope!(*args)
430
545
  end
431
546
 
@@ -458,7 +573,7 @@ module ActiveRecord
458
573
  self
459
574
  end
460
575
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
576
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
577
  # the association(s).
463
578
  #
464
579
  # User.joins(:posts)
@@ -487,7 +602,7 @@ module ActiveRecord
487
602
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
603
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
604
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
605
+ check_if_method_has_arguments!(__callee__, args)
491
606
  spawn.joins!(*args)
492
607
  end
493
608
 
@@ -496,7 +611,7 @@ module ActiveRecord
496
611
  self
497
612
  end
498
613
 
499
- # Performs a left outer joins on +args+:
614
+ # Performs LEFT OUTER JOINs on +args+:
500
615
  #
501
616
  # User.left_outer_joins(:posts)
502
617
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@@ -578,13 +693,13 @@ module ActiveRecord
578
693
  #
579
694
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
695
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
696
+ # User.where(name: "Joe", email: "joe@example.com")
582
697
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
698
  #
584
- # User.where({ name: ["Alice", "Bob"]})
699
+ # User.where(name: ["Alice", "Bob"])
585
700
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
701
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
702
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
703
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
704
  #
590
705
  # In the case of a belongs_to relationship, an association key can be used
@@ -614,18 +729,32 @@ module ActiveRecord
614
729
  #
615
730
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
731
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
732
+ # User.joins(:posts).where("posts.published" => true)
733
+ # User.joins(:posts).where(posts: { published: true })
619
734
  #
620
735
  # === no argument
621
736
  #
622
737
  # 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.
738
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
739
+ #
740
+ # Chaining with WhereChain#not:
624
741
  #
625
742
  # User.where.not(name: "Jon")
626
743
  # # SELECT * FROM users WHERE name != 'Jon'
627
744
  #
628
- # See WhereChain for more details on #not.
745
+ # Chaining with WhereChain#associated:
746
+ #
747
+ # Post.where.associated(:author)
748
+ # # SELECT "posts".* FROM "posts"
749
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
750
+ # # WHERE "authors"."id" IS NOT NULL
751
+ #
752
+ # Chaining with WhereChain#missing:
753
+ #
754
+ # Post.where.missing(:author)
755
+ # # SELECT "posts".* FROM "posts"
756
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
757
+ # # WHERE "authors"."id" IS NULL
629
758
  #
630
759
  # === blank condition
631
760
  #
@@ -668,6 +797,59 @@ module ActiveRecord
668
797
  scope
669
798
  end
670
799
 
800
+ # Allows you to invert an entire where clause instead of manually applying conditions.
801
+ #
802
+ # class User
803
+ # scope :active, -> { where(accepted: true, locked: false) }
804
+ # end
805
+ #
806
+ # User.where(accepted: true)
807
+ # # WHERE `accepted` = 1
808
+ #
809
+ # User.where(accepted: true).invert_where
810
+ # # WHERE `accepted` != 1
811
+ #
812
+ # User.active
813
+ # # WHERE `accepted` = 1 AND `locked` = 0
814
+ #
815
+ # User.active.invert_where
816
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
817
+ #
818
+ # Be careful because this inverts all conditions before +invert_where+ call.
819
+ #
820
+ # class User
821
+ # scope :active, -> { where(accepted: true, locked: false) }
822
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
823
+ # end
824
+ #
825
+ # # It also inverts `where(role: 'admin')` unexpectedly.
826
+ # User.where(role: 'admin').inactive
827
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
828
+ #
829
+ def invert_where
830
+ spawn.invert_where!
831
+ end
832
+
833
+ def invert_where! # :nodoc:
834
+ self.where_clause = where_clause.invert
835
+ self
836
+ end
837
+
838
+ # Checks whether the given relation is structurally compatible with this relation, to determine
839
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
840
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
841
+ # #where (if no #group has been defined) or #having (if a #group is present).
842
+ #
843
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
844
+ # # => true
845
+ #
846
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
847
+ # # => false
848
+ #
849
+ def structurally_compatible?(other)
850
+ structurally_incompatible_values_for(other).empty?
851
+ end
852
+
671
853
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
854
  # as an argument.
673
855
  #
@@ -886,7 +1068,7 @@ module ActiveRecord
886
1068
  self
887
1069
  end
888
1070
 
889
- # Specifies table from which the records will be fetched. For example:
1071
+ # Specifies the table from which the records will be fetched. For example:
890
1072
  #
891
1073
  # Topic.select('title').from('posts')
892
1074
  # # SELECT title FROM posts
@@ -896,9 +1078,26 @@ module ActiveRecord
896
1078
  # Topic.select('title').from(Topic.approved)
897
1079
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1080
  #
1081
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1082
+ #
899
1083
  # Topic.select('a.title').from(Topic.approved, :a)
900
1084
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1085
  #
1086
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1087
+ #
1088
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1089
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1090
+ #
1091
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1092
+ #
1093
+ # color = "red"
1094
+ # Color
1095
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1096
+ # .where("colorvalue->>'color' = ?", color)
1097
+ # .select("c.*").to_a
1098
+ # # SELECT c.*
1099
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1100
+ # # WHERE (colorvalue->>'color' = 'red')
902
1101
  def from(value, subquery_name = nil)
903
1102
  spawn.from!(value, subquery_name)
904
1103
  end
@@ -994,7 +1193,7 @@ module ActiveRecord
994
1193
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1194
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1195
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1196
+ check_if_method_has_arguments!(__callee__, args)
998
1197
  spawn.optimizer_hints!(*args)
999
1198
  end
1000
1199
 
@@ -1038,7 +1237,7 @@ module ActiveRecord
1038
1237
  #
1039
1238
  # Some escaping is performed, however untrusted user input should not be used.
1040
1239
  def annotate(*args)
1041
- check_if_method_has_arguments!(:annotate, args)
1240
+ check_if_method_has_arguments!(__callee__, args)
1042
1241
  spawn.annotate!(*args)
1043
1242
  end
1044
1243
 
@@ -1056,6 +1255,47 @@ module ActiveRecord
1056
1255
  self
1057
1256
  end
1058
1257
 
1258
+ # Excludes the specified record (or collection of records) from the resulting
1259
+ # relation. For example:
1260
+ #
1261
+ # Post.excluding(post)
1262
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1263
+ #
1264
+ # Post.excluding(post_one, post_two)
1265
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1266
+ #
1267
+ # This can also be called on associations. As with the above example, either
1268
+ # a single record of collection thereof may be specified:
1269
+ #
1270
+ # post = Post.find(1)
1271
+ # comment = Comment.find(2)
1272
+ # post.comments.excluding(comment)
1273
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1274
+ #
1275
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1276
+ #
1277
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1278
+ # specified, or if any of the records in the collection (if a collection
1279
+ # is passed in) are not instances of the same model that the relation is
1280
+ # scoping.
1281
+ def excluding(*records)
1282
+ records.flatten!(1)
1283
+ records.compact!
1284
+
1285
+ unless records.all?(klass)
1286
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1287
+ end
1288
+
1289
+ spawn.excluding!(records)
1290
+ end
1291
+ alias :without :excluding
1292
+
1293
+ def excluding!(records) # :nodoc:
1294
+ predicates = [ predicate_builder[primary_key, records].invert ]
1295
+ self.where_clause += Relation::WhereClause.new(predicates)
1296
+ self
1297
+ end
1298
+
1059
1299
  # Returns the Arel object associated with the relation.
1060
1300
  def arel(aliases = nil) # :nodoc:
1061
1301
  @arel ||= build_arel(aliases)
@@ -1111,11 +1351,9 @@ module ActiveRecord
1111
1351
  nil
1112
1352
  end
1113
1353
 
1114
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1354
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1115
1355
  join_dependencies.each do |join_dependency|
1116
- join_dependency.each do |join|
1117
- yield join
1118
- end
1356
+ join_dependency.each(&block)
1119
1357
  end
1120
1358
  end
1121
1359
 
@@ -1157,14 +1395,6 @@ module ActiveRecord
1157
1395
  unless annotate_values.empty?
1158
1396
  annotates = annotate_values
1159
1397
  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
1398
  arel.comment(*annotates)
1169
1399
  end
1170
1400
 
@@ -1172,8 +1402,7 @@ module ActiveRecord
1172
1402
  end
1173
1403
 
1174
1404
  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)
1405
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1177
1406
  end
1178
1407
 
1179
1408
  def build_from
@@ -1284,7 +1513,7 @@ module ActiveRecord
1284
1513
  def build_select(arel)
1285
1514
  if select_values.any?
1286
1515
  arel.project(*arel_columns(select_values))
1287
- elsif klass.ignored_columns.any?
1516
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1288
1517
  arel.project(*klass.column_names.map { |field| table[field] })
1289
1518
  else
1290
1519
  arel.project(table[Arel.star])
@@ -1425,12 +1654,17 @@ module ActiveRecord
1425
1654
  order_args.map! do |arg|
1426
1655
  klass.sanitize_sql_for_order(arg)
1427
1656
  end
1428
- order_args.flatten!
1429
- order_args.compact_blank!
1430
1657
  end
1431
1658
 
1432
1659
  def column_references(order_args)
1433
- references = order_args.grep(String)
1660
+ references = order_args.flat_map do |arg|
1661
+ case arg
1662
+ when String, Symbol
1663
+ arg
1664
+ when Hash
1665
+ arg.keys
1666
+ end
1667
+ end
1434
1668
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1435
1669
  references
1436
1670
  end
@@ -1445,6 +1679,15 @@ module ActiveRecord
1445
1679
  end
1446
1680
  end
1447
1681
 
1682
+ def build_case_for_value_position(column, values)
1683
+ node = Arel::Nodes::Case.new
1684
+ values.each.with_index(1) do |value, order|
1685
+ node.when(column.eq(value)).then(order)
1686
+ end
1687
+
1688
+ Arel::Nodes::Ascending.new(node)
1689
+ end
1690
+
1448
1691
  def resolve_arel_attributes(attrs)
1449
1692
  attrs.flat_map do |attr|
1450
1693
  case attr
@@ -1478,19 +1721,19 @@ module ActiveRecord
1478
1721
  # Post.references() # raises an error
1479
1722
  # Post.references([]) # does not raise an error
1480
1723
  #
1481
- # This particular method should be called with a method_name and the args
1724
+ # This particular method should be called with a method_name (__callee__) and the args
1482
1725
  # passed into that method as an input. For example:
1483
1726
  #
1484
1727
  # def references(*args)
1485
- # check_if_method_has_arguments!("references", args)
1728
+ # check_if_method_has_arguments!(__callee__, args)
1486
1729
  # ...
1487
1730
  # end
1488
1731
  def check_if_method_has_arguments!(method_name, args, message = nil)
1489
1732
  if args.blank?
1490
1733
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1491
- elsif block_given?
1492
- yield args
1493
1734
  else
1735
+ yield args if block_given?
1736
+
1494
1737
  args.flatten!
1495
1738
  args.compact_blank!
1496
1739
  end
@@ -1514,11 +1757,4 @@ module ActiveRecord
1514
1757
  end
1515
1758
  end
1516
1759
  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
1760
  end