activerecord 6.1.7.8 → 7.0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1582 -1018
  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 +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,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