activerecord 6.1.7.4 → 7.0.6

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