activerecord 6.1.7.4 → 7.0.6

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1449 -1014
  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 +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 +14 -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 +15 -7
  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 +17 -28
  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 +153 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -84
  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 +45 -21
  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 +71 -71
  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 +28 -16
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +97 -32
  95. data/lib/active_record/connection_adapters.rb +6 -5
  96. data/lib/active_record/connection_handling.rb +49 -55
  97. data/lib/active_record/core.rb +123 -148
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  99. data/lib/active_record/database_configurations/database_config.rb +12 -9
  100. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  101. data/lib/active_record/database_configurations/url_config.rb +2 -2
  102. data/lib/active_record/database_configurations.rb +15 -32
  103. data/lib/active_record/delegated_type.rb +53 -12
  104. data/lib/active_record/destroy_association_async_job.rb +1 -1
  105. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  106. data/lib/active_record/dynamic_matchers.rb +1 -1
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  108. data/lib/active_record/encryption/cipher.rb +53 -0
  109. data/lib/active_record/encryption/config.rb +44 -0
  110. data/lib/active_record/encryption/configurable.rb +67 -0
  111. data/lib/active_record/encryption/context.rb +35 -0
  112. data/lib/active_record/encryption/contexts.rb +72 -0
  113. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  114. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  115. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  116. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  117. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  118. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  119. data/lib/active_record/encryption/encryptor.rb +155 -0
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  121. data/lib/active_record/encryption/errors.rb +15 -0
  122. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  123. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  124. data/lib/active_record/encryption/key.rb +28 -0
  125. data/lib/active_record/encryption/key_generator.rb +42 -0
  126. data/lib/active_record/encryption/key_provider.rb +46 -0
  127. data/lib/active_record/encryption/message.rb +33 -0
  128. data/lib/active_record/encryption/message_serializer.rb +90 -0
  129. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  130. data/lib/active_record/encryption/properties.rb +76 -0
  131. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  132. data/lib/active_record/encryption/scheme.rb +99 -0
  133. data/lib/active_record/encryption.rb +55 -0
  134. data/lib/active_record/enum.rb +50 -43
  135. data/lib/active_record/errors.rb +67 -4
  136. data/lib/active_record/explain_registry.rb +11 -6
  137. data/lib/active_record/explain_subscriber.rb +1 -1
  138. data/lib/active_record/fixture_set/file.rb +15 -1
  139. data/lib/active_record/fixture_set/table_row.rb +41 -6
  140. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  141. data/lib/active_record/fixtures.rb +20 -23
  142. data/lib/active_record/future_result.rb +139 -0
  143. data/lib/active_record/gem_version.rb +5 -5
  144. data/lib/active_record/inheritance.rb +55 -17
  145. data/lib/active_record/insert_all.rb +80 -14
  146. data/lib/active_record/integration.rb +4 -3
  147. data/lib/active_record/internal_metadata.rb +1 -5
  148. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  149. data/lib/active_record/locking/optimistic.rb +36 -21
  150. data/lib/active_record/locking/pessimistic.rb +10 -4
  151. data/lib/active_record/log_subscriber.rb +23 -7
  152. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  153. data/lib/active_record/middleware/database_selector.rb +18 -6
  154. data/lib/active_record/middleware/shard_selector.rb +60 -0
  155. data/lib/active_record/migration/command_recorder.rb +8 -9
  156. data/lib/active_record/migration/compatibility.rb +91 -2
  157. data/lib/active_record/migration/join_table.rb +1 -1
  158. data/lib/active_record/migration.rb +115 -84
  159. data/lib/active_record/model_schema.rb +58 -59
  160. data/lib/active_record/nested_attributes.rb +13 -12
  161. data/lib/active_record/no_touching.rb +3 -3
  162. data/lib/active_record/null_relation.rb +2 -6
  163. data/lib/active_record/persistence.rb +228 -60
  164. data/lib/active_record/query_cache.rb +2 -2
  165. data/lib/active_record/query_logs.rb +149 -0
  166. data/lib/active_record/querying.rb +16 -6
  167. data/lib/active_record/railtie.rb +136 -22
  168. data/lib/active_record/railties/controller_runtime.rb +1 -1
  169. data/lib/active_record/railties/databases.rake +78 -136
  170. data/lib/active_record/readonly_attributes.rb +11 -0
  171. data/lib/active_record/reflection.rb +80 -49
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  173. data/lib/active_record/relation/batches.rb +6 -6
  174. data/lib/active_record/relation/calculations.rb +92 -60
  175. data/lib/active_record/relation/delegation.rb +7 -7
  176. data/lib/active_record/relation/finder_methods.rb +31 -35
  177. data/lib/active_record/relation/merger.rb +20 -13
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  179. data/lib/active_record/relation/predicate_builder.rb +2 -6
  180. data/lib/active_record/relation/query_attribute.rb +5 -11
  181. data/lib/active_record/relation/query_methods.rb +285 -68
  182. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  183. data/lib/active_record/relation/spawn_methods.rb +2 -2
  184. data/lib/active_record/relation/where_clause.rb +10 -19
  185. data/lib/active_record/relation.rb +189 -88
  186. data/lib/active_record/result.rb +23 -11
  187. data/lib/active_record/runtime_registry.rb +9 -13
  188. data/lib/active_record/sanitization.rb +17 -12
  189. data/lib/active_record/schema.rb +38 -23
  190. data/lib/active_record/schema_dumper.rb +29 -19
  191. data/lib/active_record/schema_migration.rb +4 -4
  192. data/lib/active_record/scoping/default.rb +60 -13
  193. data/lib/active_record/scoping/named.rb +3 -11
  194. data/lib/active_record/scoping.rb +64 -34
  195. data/lib/active_record/serialization.rb +6 -1
  196. data/lib/active_record/signed_id.rb +3 -3
  197. data/lib/active_record/store.rb +2 -2
  198. data/lib/active_record/suppressor.rb +11 -15
  199. data/lib/active_record/table_metadata.rb +5 -1
  200. data/lib/active_record/tasks/database_tasks.rb +127 -60
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  203. data/lib/active_record/test_databases.rb +1 -1
  204. data/lib/active_record/test_fixtures.rb +9 -6
  205. data/lib/active_record/timestamp.rb +3 -4
  206. data/lib/active_record/transactions.rb +9 -14
  207. data/lib/active_record/translation.rb +3 -3
  208. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  209. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  210. data/lib/active_record/type/internal/timezone.rb +2 -2
  211. data/lib/active_record/type/serialized.rb +5 -5
  212. data/lib/active_record/type/type_map.rb +17 -20
  213. data/lib/active_record/type.rb +1 -2
  214. data/lib/active_record/validations/associated.rb +4 -4
  215. data/lib/active_record/validations/presence.rb +2 -2
  216. data/lib/active_record/validations/uniqueness.rb +4 -4
  217. data/lib/active_record/version.rb +1 -1
  218. data/lib/active_record.rb +225 -27
  219. data/lib/arel/attributes/attribute.rb +0 -8
  220. data/lib/arel/crud.rb +28 -22
  221. data/lib/arel/delete_manager.rb +18 -4
  222. data/lib/arel/filter_predications.rb +9 -0
  223. data/lib/arel/insert_manager.rb +2 -3
  224. data/lib/arel/nodes/casted.rb +1 -1
  225. data/lib/arel/nodes/delete_statement.rb +12 -13
  226. data/lib/arel/nodes/filter.rb +10 -0
  227. data/lib/arel/nodes/function.rb +1 -0
  228. data/lib/arel/nodes/insert_statement.rb +2 -2
  229. data/lib/arel/nodes/select_core.rb +2 -2
  230. data/lib/arel/nodes/select_statement.rb +2 -2
  231. data/lib/arel/nodes/update_statement.rb +8 -3
  232. data/lib/arel/nodes.rb +1 -0
  233. data/lib/arel/predications.rb +11 -3
  234. data/lib/arel/select_manager.rb +10 -4
  235. data/lib/arel/table.rb +0 -1
  236. data/lib/arel/tree_manager.rb +0 -12
  237. data/lib/arel/update_manager.rb +18 -4
  238. data/lib/arel/visitors/dot.rb +80 -90
  239. data/lib/arel/visitors/mysql.rb +8 -2
  240. data/lib/arel/visitors/postgresql.rb +0 -10
  241. data/lib/arel/visitors/to_sql.rb +58 -2
  242. data/lib/arel.rb +2 -1
  243. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  244. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  245. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  247. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  248. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  249. metadata +58 -14
@@ -20,25 +20,19 @@ module ActiveRecord
20
20
  def nil?
21
21
  unless value_before_type_cast.is_a?(StatementCache::Substitute)
22
22
  value_before_type_cast.nil? ||
23
- type.respond_to?(:subtype, true) && value_for_database.nil?
23
+ type.respond_to?(:subtype) && serializable? && value_for_database.nil?
24
24
  end
25
- rescue ::RangeError
26
25
  end
27
26
 
28
27
  def infinite?
29
- infinity?(value_before_type_cast) || infinity?(value_for_database)
30
- rescue ::RangeError
28
+ infinity?(value_before_type_cast) || serializable? && infinity?(value_for_database)
31
29
  end
32
30
 
33
31
  def unboundable?
34
- if defined?(@_unboundable)
35
- @_unboundable
36
- else
37
- value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
38
- @_unboundable = nil
32
+ unless defined?(@_unboundable)
33
+ serializable? { |value| @_unboundable = value <=> 0 } && @_unboundable = nil
39
34
  end
40
- rescue ::RangeError
41
- @_unboundable = type.cast(value_before_type_cast) <=> 0
35
+ @_unboundable
42
36
  end
43
37
 
44
38
  private
@@ -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 @scope.table_name == reflection.table_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 @scope.table_name == reflection.table_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,29 @@ module ActiveRecord
362
444
  self
363
445
  end
364
446
 
447
+ # Allows to specify an order by a specific set of values. Depending on your
448
+ # adapter this will either use a CASE statement or a built-in function.
449
+ #
450
+ # User.in_order_of(:id, [1, 5, 3])
451
+ # # SELECT "users".* FROM "users"
452
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
453
+ # # WHERE "users"."id" IN (1, 5, 3)
454
+ #
455
+ def in_order_of(column, values)
456
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
457
+ return spawn.none! if values.empty?
458
+
459
+ references = column_references([column])
460
+ self.references_values |= references unless references.empty?
461
+
462
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
463
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
464
+
465
+ spawn
466
+ .order!(connection.field_ordered_value(arel_column, values))
467
+ .where!(arel_column.in(values))
468
+ end
469
+
365
470
  # Replaces any existing order defined on the relation with the specified order.
366
471
  #
367
472
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -372,15 +477,15 @@ module ActiveRecord
372
477
  #
373
478
  # generates a query with 'ORDER BY id ASC, name ASC'.
374
479
  def reorder(*args)
375
- check_if_method_has_arguments!(:reorder, args) do
376
- sanitize_order_arguments(args) unless args.all?(&:blank?)
480
+ check_if_method_has_arguments!(__callee__, args) do
481
+ sanitize_order_arguments(args)
377
482
  end
378
483
  spawn.reorder!(*args)
379
484
  end
380
485
 
381
486
  # Same as #reorder but operates on relation in-place instead of copying.
382
487
  def reorder!(*args) # :nodoc:
383
- preprocess_order_args(args) unless args.all?(&:blank?)
488
+ preprocess_order_args(args)
384
489
  args.uniq!
385
490
  self.reordering_value = true
386
491
  self.order_values = args
@@ -425,7 +530,7 @@ module ActiveRecord
425
530
  # has_many :comments, -> { unscope(where: :trashed) }
426
531
  #
427
532
  def unscope(*args)
428
- check_if_method_has_arguments!(:unscope, args)
533
+ check_if_method_has_arguments!(__callee__, args)
429
534
  spawn.unscope!(*args)
430
535
  end
431
536
 
@@ -458,7 +563,7 @@ module ActiveRecord
458
563
  self
459
564
  end
460
565
 
461
- # Performs a joins on +args+. The given symbol(s) should match the name of
566
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
462
567
  # the association(s).
463
568
  #
464
569
  # User.joins(:posts)
@@ -487,7 +592,7 @@ module ActiveRecord
487
592
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
488
593
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
489
594
  def joins(*args)
490
- check_if_method_has_arguments!(:joins, args)
595
+ check_if_method_has_arguments!(__callee__, args)
491
596
  spawn.joins!(*args)
492
597
  end
493
598
 
@@ -496,7 +601,7 @@ module ActiveRecord
496
601
  self
497
602
  end
498
603
 
499
- # Performs a left outer joins on +args+:
604
+ # Performs LEFT OUTER JOINs on +args+:
500
605
  #
501
606
  # User.left_outer_joins(:posts)
502
607
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@@ -578,13 +683,13 @@ module ActiveRecord
578
683
  #
579
684
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
580
685
  #
581
- # User.where({ name: "Joe", email: "joe@example.com" })
686
+ # User.where(name: "Joe", email: "joe@example.com")
582
687
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
583
688
  #
584
- # User.where({ name: ["Alice", "Bob"]})
689
+ # User.where(name: ["Alice", "Bob"])
585
690
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
586
691
  #
587
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
692
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
588
693
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
589
694
  #
590
695
  # In the case of a belongs_to relationship, an association key can be used
@@ -614,18 +719,32 @@ module ActiveRecord
614
719
  #
615
720
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
616
721
  #
617
- # User.joins(:posts).where({ "posts.published" => true })
618
- # User.joins(:posts).where({ posts: { published: true } })
722
+ # User.joins(:posts).where("posts.published" => true)
723
+ # User.joins(:posts).where(posts: { published: true })
619
724
  #
620
725
  # === no argument
621
726
  #
622
727
  # 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.
728
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
729
+ #
730
+ # Chaining with WhereChain#not:
624
731
  #
625
732
  # User.where.not(name: "Jon")
626
733
  # # SELECT * FROM users WHERE name != 'Jon'
627
734
  #
628
- # See WhereChain for more details on #not.
735
+ # Chaining with WhereChain#associated:
736
+ #
737
+ # Post.where.associated(:author)
738
+ # # SELECT "posts".* FROM "posts"
739
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
740
+ # # WHERE "authors"."id" IS NOT NULL
741
+ #
742
+ # Chaining with WhereChain#missing:
743
+ #
744
+ # Post.where.missing(:author)
745
+ # # SELECT "posts".* FROM "posts"
746
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
747
+ # # WHERE "authors"."id" IS NULL
629
748
  #
630
749
  # === blank condition
631
750
  #
@@ -668,6 +787,59 @@ module ActiveRecord
668
787
  scope
669
788
  end
670
789
 
790
+ # Allows you to invert an entire where clause instead of manually applying conditions.
791
+ #
792
+ # class User
793
+ # scope :active, -> { where(accepted: true, locked: false) }
794
+ # end
795
+ #
796
+ # User.where(accepted: true)
797
+ # # WHERE `accepted` = 1
798
+ #
799
+ # User.where(accepted: true).invert_where
800
+ # # WHERE `accepted` != 1
801
+ #
802
+ # User.active
803
+ # # WHERE `accepted` = 1 AND `locked` = 0
804
+ #
805
+ # User.active.invert_where
806
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
807
+ #
808
+ # Be careful because this inverts all conditions before +invert_where+ call.
809
+ #
810
+ # class User
811
+ # scope :active, -> { where(accepted: true, locked: false) }
812
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
813
+ # end
814
+ #
815
+ # # It also inverts `where(role: 'admin')` unexpectedly.
816
+ # User.where(role: 'admin').inactive
817
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
818
+ #
819
+ def invert_where
820
+ spawn.invert_where!
821
+ end
822
+
823
+ def invert_where! # :nodoc:
824
+ self.where_clause = where_clause.invert
825
+ self
826
+ end
827
+
828
+ # Checks whether the given relation is structurally compatible with this relation, to determine
829
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
830
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
831
+ # #where (if no #group has been defined) or #having (if a #group is present).
832
+ #
833
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
834
+ # # => true
835
+ #
836
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
837
+ # # => false
838
+ #
839
+ def structurally_compatible?(other)
840
+ structurally_incompatible_values_for(other).empty?
841
+ end
842
+
671
843
  # Returns a new relation, which is the logical intersection of this relation and the one passed
672
844
  # as an argument.
673
845
  #
@@ -886,7 +1058,7 @@ module ActiveRecord
886
1058
  self
887
1059
  end
888
1060
 
889
- # Specifies table from which the records will be fetched. For example:
1061
+ # Specifies the table from which the records will be fetched. For example:
890
1062
  #
891
1063
  # Topic.select('title').from('posts')
892
1064
  # # SELECT title FROM posts
@@ -896,9 +1068,26 @@ module ActiveRecord
896
1068
  # Topic.select('title').from(Topic.approved)
897
1069
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
898
1070
  #
1071
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1072
+ #
899
1073
  # Topic.select('a.title').from(Topic.approved, :a)
900
1074
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
901
1075
  #
1076
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1077
+ #
1078
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1079
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1080
+ #
1081
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1082
+ #
1083
+ # color = "red"
1084
+ # Color
1085
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1086
+ # .where("colorvalue->>'color' = ?", color)
1087
+ # .select("c.*").to_a
1088
+ # # SELECT c.*
1089
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1090
+ # # WHERE (colorvalue->>'color' = 'red')
902
1091
  def from(value, subquery_name = nil)
903
1092
  spawn.from!(value, subquery_name)
904
1093
  end
@@ -994,7 +1183,7 @@ module ActiveRecord
994
1183
  # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
1184
  # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
1185
  def optimizer_hints(*args)
997
- check_if_method_has_arguments!(:optimizer_hints, args)
1186
+ check_if_method_has_arguments!(__callee__, args)
998
1187
  spawn.optimizer_hints!(*args)
999
1188
  end
1000
1189
 
@@ -1038,7 +1227,7 @@ module ActiveRecord
1038
1227
  #
1039
1228
  # Some escaping is performed, however untrusted user input should not be used.
1040
1229
  def annotate(*args)
1041
- check_if_method_has_arguments!(:annotate, args)
1230
+ check_if_method_has_arguments!(__callee__, args)
1042
1231
  spawn.annotate!(*args)
1043
1232
  end
1044
1233
 
@@ -1056,6 +1245,47 @@ module ActiveRecord
1056
1245
  self
1057
1246
  end
1058
1247
 
1248
+ # Excludes the specified record (or collection of records) from the resulting
1249
+ # relation. For example:
1250
+ #
1251
+ # Post.excluding(post)
1252
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1253
+ #
1254
+ # Post.excluding(post_one, post_two)
1255
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1256
+ #
1257
+ # This can also be called on associations. As with the above example, either
1258
+ # a single record of collection thereof may be specified:
1259
+ #
1260
+ # post = Post.find(1)
1261
+ # comment = Comment.find(2)
1262
+ # post.comments.excluding(comment)
1263
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1264
+ #
1265
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1266
+ #
1267
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1268
+ # specified, or if any of the records in the collection (if a collection
1269
+ # is passed in) are not instances of the same model that the relation is
1270
+ # scoping.
1271
+ def excluding(*records)
1272
+ records.flatten!(1)
1273
+ records.compact!
1274
+
1275
+ unless records.all?(klass)
1276
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1277
+ end
1278
+
1279
+ spawn.excluding!(records)
1280
+ end
1281
+ alias :without :excluding
1282
+
1283
+ def excluding!(records) # :nodoc:
1284
+ predicates = [ predicate_builder[primary_key, records].invert ]
1285
+ self.where_clause += Relation::WhereClause.new(predicates)
1286
+ self
1287
+ end
1288
+
1059
1289
  # Returns the Arel object associated with the relation.
1060
1290
  def arel(aliases = nil) # :nodoc:
1061
1291
  @arel ||= build_arel(aliases)
@@ -1111,11 +1341,9 @@ module ActiveRecord
1111
1341
  nil
1112
1342
  end
1113
1343
 
1114
- def each_join_dependencies(join_dependencies = build_join_dependencies)
1344
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1115
1345
  join_dependencies.each do |join_dependency|
1116
- join_dependency.each do |join|
1117
- yield join
1118
- end
1346
+ join_dependency.each(&block)
1119
1347
  end
1120
1348
  end
1121
1349
 
@@ -1157,14 +1385,6 @@ module ActiveRecord
1157
1385
  unless annotate_values.empty?
1158
1386
  annotates = annotate_values
1159
1387
  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
1388
  arel.comment(*annotates)
1169
1389
  end
1170
1390
 
@@ -1172,8 +1392,7 @@ module ActiveRecord
1172
1392
  end
1173
1393
 
1174
1394
  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)
1395
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1177
1396
  end
1178
1397
 
1179
1398
  def build_from
@@ -1284,7 +1503,7 @@ module ActiveRecord
1284
1503
  def build_select(arel)
1285
1504
  if select_values.any?
1286
1505
  arel.project(*arel_columns(select_values))
1287
- elsif klass.ignored_columns.any?
1506
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1288
1507
  arel.project(*klass.column_names.map { |field| table[field] })
1289
1508
  else
1290
1509
  arel.project(table[Arel.star])
@@ -1425,12 +1644,17 @@ module ActiveRecord
1425
1644
  order_args.map! do |arg|
1426
1645
  klass.sanitize_sql_for_order(arg)
1427
1646
  end
1428
- order_args.flatten!
1429
- order_args.compact_blank!
1430
1647
  end
1431
1648
 
1432
1649
  def column_references(order_args)
1433
- references = order_args.grep(String)
1650
+ references = order_args.flat_map do |arg|
1651
+ case arg
1652
+ when String, Symbol
1653
+ arg
1654
+ when Hash
1655
+ arg.keys
1656
+ end
1657
+ end
1434
1658
  references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1435
1659
  references
1436
1660
  end
@@ -1478,19 +1702,19 @@ module ActiveRecord
1478
1702
  # Post.references() # raises an error
1479
1703
  # Post.references([]) # does not raise an error
1480
1704
  #
1481
- # This particular method should be called with a method_name and the args
1705
+ # This particular method should be called with a method_name (__callee__) and the args
1482
1706
  # passed into that method as an input. For example:
1483
1707
  #
1484
1708
  # def references(*args)
1485
- # check_if_method_has_arguments!("references", args)
1709
+ # check_if_method_has_arguments!(__callee__, args)
1486
1710
  # ...
1487
1711
  # end
1488
1712
  def check_if_method_has_arguments!(method_name, args, message = nil)
1489
1713
  if args.blank?
1490
1714
  raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1491
- elsif block_given?
1492
- yield args
1493
1715
  else
1716
+ yield args if block_given?
1717
+
1494
1718
  args.flatten!
1495
1719
  args.compact_blank!
1496
1720
  end
@@ -1514,11 +1738,4 @@ module ActiveRecord
1514
1738
  end
1515
1739
  end
1516
1740
  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
1741
  end