activerecord 7.0.8.1 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +3 -3
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -3,14 +3,13 @@
3
3
  require "active_record/relation/from_clause"
4
4
  require "active_record/relation/query_attribute"
5
5
  require "active_record/relation/where_clause"
6
- require "active_model/forbidden_attributes_protection"
7
6
  require "active_support/core_ext/array/wrap"
8
7
 
9
8
  module ActiveRecord
10
9
  module QueryMethods
11
10
  include ActiveModel::ForbiddenAttributesProtection
12
11
 
13
- # WhereChain objects act as placeholder for queries in which +where+ does not have any parameter.
12
+ # +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
14
13
  # In this case, +where+ can be chained to return a new relation.
15
14
  class WhereChain
16
15
  def initialize(scope) # :nodoc:
@@ -39,7 +38,7 @@ module ActiveRecord
39
38
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
40
39
  #
41
40
  # User.where.not(name: "Jon", role: "admin")
42
- # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
41
+ # # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin')
43
42
  #
44
43
  # If there is a non-nil condition on a nullable column in the hash condition, the records that have
45
44
  # nil values on the nullable column won't be returned.
@@ -73,10 +72,26 @@ module ActiveRecord
73
72
  # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
74
73
  # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
75
74
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
75
+ #
76
+ # You can define join type in the scope and +associated+ will not use `JOIN` by default.
77
+ #
78
+ # Post.left_joins(:author).where.associated(:author)
79
+ # # SELECT "posts".* FROM "posts"
80
+ # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
81
+ # # WHERE "authors"."id" IS NOT NULL
82
+ #
83
+ # Post.left_joins(:comments).where.associated(:author)
84
+ # # SELECT "posts".* FROM "posts"
85
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
86
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
87
+ # # WHERE "author"."id" IS NOT NULL
76
88
  def associated(*associations)
77
89
  associations.each do |association|
78
90
  reflection = scope_association_reflection(association)
79
- @scope.joins!(association)
91
+ unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
92
+ @scope.joins!(association)
93
+ end
94
+
80
95
  if reflection.options[:class_name]
81
96
  self.not(association => { reflection.association_primary_key => nil })
82
97
  else
@@ -129,6 +144,15 @@ module ActiveRecord
129
144
  end
130
145
  end
131
146
 
147
+ # A wrapper to distinguish CTE joins from other nodes.
148
+ class CTEJoin # :nodoc:
149
+ attr_reader :name
150
+
151
+ def initialize(name)
152
+ @name = name
153
+ end
154
+ end
155
+
132
156
  FROZEN_EMPTY_ARRAY = [].freeze
133
157
  FROZEN_EMPTY_HASH = {}.freeze
134
158
 
@@ -149,7 +173,7 @@ module ActiveRecord
149
173
  end # end
150
174
 
151
175
  def #{method_name}=(value) # def includes_values=(value)
152
- assert_mutability! # assert_mutability!
176
+ assert_modifiable! # assert_modifiable!
153
177
  @values[:#{name}] = value # @values[:includes] = value
154
178
  end # end
155
179
  CODE
@@ -157,45 +181,69 @@ module ActiveRecord
157
181
 
158
182
  alias extensions extending_values
159
183
 
160
- # Specify relationships to be included in the result set. For
161
- # example:
184
+ # Specify associations +args+ to be eager loaded to prevent N + 1 queries.
185
+ # A separate query is performed for each association, unless a join is
186
+ # required by conditions.
162
187
  #
163
- # users = User.includes(:address)
188
+ # For example:
189
+ #
190
+ # users = User.includes(:address).limit(5)
164
191
  # users.each do |user|
165
192
  # user.address.city
166
193
  # end
167
194
  #
168
- # allows you to access the +address+ attribute of the +User+ model without
169
- # firing an additional query. This will often result in a
170
- # performance improvement over a simple join.
195
+ # # SELECT "users".* FROM "users" LIMIT 5
196
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
171
197
  #
172
- # You can also specify multiple relationships, like this:
198
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
199
+ # are loaded with a single query.
173
200
  #
174
- # users = User.includes(:address, :friends)
201
+ # Loading the associations in a separate query will often result in a
202
+ # performance improvement over a simple join, as a join can result in many
203
+ # rows that contain redundant data and it performs poorly at scale.
175
204
  #
176
- # Loading nested relationships is possible using a Hash:
205
+ # You can also specify multiple associations. Each association will result
206
+ # in an additional query:
207
+ #
208
+ # User.includes(:address, :friends).to_a
209
+ # # SELECT "users".* FROM "users"
210
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
211
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
177
212
  #
178
- # users = User.includes(:address, friends: [:address, :followers])
213
+ # Loading nested associations is possible using a Hash:
214
+ #
215
+ # User.includes(:address, friends: [:address, :followers])
179
216
  #
180
217
  # === Conditions
181
218
  #
182
219
  # If you want to add string conditions to your included models, you'll have
183
220
  # to explicitly reference them. For example:
184
221
  #
185
- # User.includes(:posts).where('posts.name = ?', 'example')
222
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
186
223
  #
187
224
  # Will throw an error, but this will work:
188
225
  #
189
- # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
226
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
227
+ # # SELECT "users"."id" AS t0_r0, ... FROM "users"
228
+ # # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
229
+ # # WHERE "posts"."name" = ? [["name", "example"]]
230
+ #
231
+ # As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
232
+ # the posts is no longer performed.
190
233
  #
191
234
  # Note that #includes works with association names while #references needs
192
235
  # the actual table name.
193
236
  #
194
- # If you pass the conditions via hash, you don't need to call #references
237
+ # If you pass the conditions via a Hash, you don't need to call #references
195
238
  # explicitly, as #where references the tables for you. For example, this
196
239
  # will work correctly:
197
240
  #
198
241
  # User.includes(:posts).where(posts: { name: 'example' })
242
+ #
243
+ # NOTE: Conditions affect both sides of an association. For example, the
244
+ # above code will return only users that have a post named "example",
245
+ # <em>and will only include posts named "example"</em>, even when a
246
+ # matching user has other additional posts.
199
247
  def includes(*args)
200
248
  check_if_method_has_arguments!(__callee__, args)
201
249
  spawn.includes!(*args)
@@ -206,12 +254,32 @@ module ActiveRecord
206
254
  self
207
255
  end
208
256
 
209
- # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
257
+ # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
258
+ # Performs a single query joining all specified associations. For example:
259
+ #
260
+ # users = User.eager_load(:address).limit(5)
261
+ # users.each do |user|
262
+ # user.address.city
263
+ # end
264
+ #
265
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
266
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
267
+ # # LIMIT 5
268
+ #
269
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
270
+ # are loaded with a single joined query.
210
271
  #
211
- # User.eager_load(:posts)
212
- # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
213
- # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
214
- # # "users"."id"
272
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
273
+ # similar to #includes:
274
+ #
275
+ # User.eager_load(:address, friends: [:address, :followers])
276
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
277
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
278
+ # # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
279
+ # # ...
280
+ #
281
+ # NOTE: Loading the associations in a join can result in many rows that
282
+ # contain redundant data and it performs poorly at scale.
215
283
  def eager_load(*args)
216
284
  check_if_method_has_arguments!(__callee__, args)
217
285
  spawn.eager_load!(*args)
@@ -222,10 +290,28 @@ module ActiveRecord
222
290
  self
223
291
  end
224
292
 
225
- # Allows preloading of +args+, in the same way that #includes does:
293
+ # Specify associations +args+ to be eager loaded using separate queries.
294
+ # A separate query is performed for each association.
295
+ #
296
+ # users = User.preload(:address).limit(5)
297
+ # users.each do |user|
298
+ # user.address.city
299
+ # end
300
+ #
301
+ # # SELECT "users".* FROM "users" LIMIT 5
302
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
226
303
  #
227
- # User.preload(:posts)
228
- # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
304
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
305
+ # are loaded with a separate query.
306
+ #
307
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
308
+ # similar to #includes:
309
+ #
310
+ # User.preload(:address, friends: [:address, :followers])
311
+ # # SELECT "users".* FROM "users"
312
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
313
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
314
+ # # SELECT ...
229
315
  def preload(*args)
230
316
  check_if_method_has_arguments!(__callee__, args)
231
317
  spawn.preload!(*args)
@@ -250,7 +336,7 @@ module ActiveRecord
250
336
  end
251
337
 
252
338
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
253
- # and should therefore be JOINed in any query rather than loaded separately.
339
+ # and should therefore be +JOIN+ed in any query rather than loaded separately.
254
340
  # This method only works in conjunction with #includes.
255
341
  # See #includes for more details.
256
342
  #
@@ -294,6 +380,14 @@ module ActiveRecord
294
380
  # Model.select(:field, :other_field, :and_one_more)
295
381
  # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
296
382
  #
383
+ # The argument also can be a hash of fields and aliases.
384
+ #
385
+ # Model.select(models: { field: :alias, other_field: :other_alias })
386
+ # # => [#<Model id: nil, alias: "value", other_alias: "value">]
387
+ #
388
+ # Model.select(models: [:field, :other_field])
389
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
390
+ #
297
391
  # You can also use one or more strings, which will be used unchanged as SELECT fields.
298
392
  #
299
393
  # Model.select('field AS field_one', 'other_field AS field_two')
@@ -308,7 +402,7 @@ module ActiveRecord
308
402
  # except +id+ will throw ActiveModel::MissingAttributeError:
309
403
  #
310
404
  # Model.select(:field).first.other_field
311
- # # => ActiveModel::MissingAttributeError: missing attribute: other_field
405
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
312
406
  def select(*fields)
313
407
  if block_given?
314
408
  if fields.any?
@@ -319,6 +413,8 @@ module ActiveRecord
319
413
  end
320
414
 
321
415
  check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
416
+
417
+ fields = process_select_args(fields)
322
418
  spawn._select!(*fields)
323
419
  end
324
420
 
@@ -327,6 +423,102 @@ module ActiveRecord
327
423
  self
328
424
  end
329
425
 
426
+ # Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
427
+ #
428
+ # Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
429
+ # use CTE's with MySQL 5.7.
430
+ #
431
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
432
+ # # => ActiveRecord::Relation
433
+ # # WITH posts_with_tags AS (
434
+ # # SELECT * FROM posts WHERE (tags_count > 0)
435
+ # # )
436
+ # # SELECT * FROM posts
437
+ #
438
+ # You can also pass an array of sub-queries to be joined in a +UNION ALL+.
439
+ #
440
+ # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
441
+ # # => ActiveRecord::Relation
442
+ # # WITH posts_with_tags_or_comments AS (
443
+ # # (SELECT * FROM posts WHERE (tags_count > 0))
444
+ # # UNION ALL
445
+ # # (SELECT * FROM posts WHERE (comments_count > 0))
446
+ # # )
447
+ # # SELECT * FROM posts
448
+ #
449
+ # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
450
+ #
451
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
452
+ # # => ActiveRecord::Relation
453
+ # # WITH posts_with_tags AS (
454
+ # # SELECT * FROM posts WHERE (tags_count > 0)
455
+ # # )
456
+ # # SELECT * FROM posts_with_tags AS posts
457
+ #
458
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
459
+ # # => ActiveRecord::Relation
460
+ # # WITH posts_with_tags AS (
461
+ # # SELECT * FROM posts WHERE (tags_count > 0)
462
+ # # )
463
+ # # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
464
+ #
465
+ # It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
466
+ # and you have verified it is safe for the database, you can pass it as SQL literal
467
+ # using +Arel+.
468
+ #
469
+ # Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
470
+ #
471
+ # Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
472
+ # be used with unsafe values that include unsanitized input.
473
+ #
474
+ # To add multiple CTEs just pass multiple key-value pairs
475
+ #
476
+ # Post.with(
477
+ # posts_with_comments: Post.where("comments_count > ?", 0),
478
+ # posts_with_tags: Post.where("tags_count > ?", 0)
479
+ # )
480
+ #
481
+ # or chain multiple +.with+ calls
482
+ #
483
+ # Post
484
+ # .with(posts_with_comments: Post.where("comments_count > ?", 0))
485
+ # .with(posts_with_tags: Post.where("tags_count > ?", 0))
486
+ def with(*args)
487
+ raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
488
+ check_if_method_has_arguments!(__callee__, args)
489
+ spawn.with!(*args)
490
+ end
491
+
492
+ # Like #with, but modifies relation in place.
493
+ def with!(*args) # :nodoc:
494
+ self.with_values += args
495
+ self
496
+ end
497
+
498
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
499
+ #
500
+ # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
501
+ # # => ActiveRecord::Relation
502
+ # # WITH post_and_replies AS (
503
+ # # (SELECT * FROM posts WHERE id = 42)
504
+ # # UNION ALL
505
+ # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
506
+ # # )
507
+ # # SELECT * FROM posts
508
+ #
509
+ # See `#with` for more information.
510
+ def with_recursive(*args)
511
+ check_if_method_has_arguments!(__callee__, args)
512
+ spawn.with_recursive!(*args)
513
+ end
514
+
515
+ # Like #with_recursive but modifies the relation in place.
516
+ def with_recursive!(*args) # :nodoc:
517
+ self.with_values += args
518
+ @with_is_recursive = true
519
+ self
520
+ end
521
+
330
522
  # Allows you to change a previously set select statement.
331
523
  #
332
524
  # Post.select(:title, :body)
@@ -339,6 +531,7 @@ module ActiveRecord
339
531
  # Note that we're unscoping the entire select statement.
340
532
  def reselect(*args)
341
533
  check_if_method_has_arguments!(__callee__, args)
534
+ args = process_select_args(args)
342
535
  spawn.reselect!(*args)
343
536
  end
344
537
 
@@ -378,6 +571,27 @@ module ActiveRecord
378
571
  self
379
572
  end
380
573
 
574
+ # Allows you to change a previously set group statement.
575
+ #
576
+ # Post.group(:title, :body)
577
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
578
+ #
579
+ # Post.group(:title, :body).regroup(:title)
580
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
581
+ #
582
+ # This is short-hand for <tt>unscope(:group).group(fields)</tt>.
583
+ # Note that we're unscoping the entire group statement.
584
+ def regroup(*args)
585
+ check_if_method_has_arguments!(__callee__, args)
586
+ spawn.regroup!(*args)
587
+ end
588
+
589
+ # Same as #regroup but operates on relation in-place instead of copying.
590
+ def regroup!(*args) # :nodoc:
591
+ self.group_values = args
592
+ self
593
+ end
594
+
381
595
  # Applies an <code>ORDER BY</code> clause to a query.
382
596
  #
383
597
  # #order accepts arguments in one of several formats.
@@ -426,7 +640,7 @@ module ActiveRecord
426
640
  # User.order(Arel.sql('end_date - start_date'))
427
641
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
428
642
  #
429
- # Custom query syntax, like JSON columns for Postgres, is supported in this way.
643
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
430
644
  #
431
645
  # User.order(Arel.sql("payload->>'kind'"))
432
646
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
@@ -444,7 +658,8 @@ module ActiveRecord
444
658
  self
445
659
  end
446
660
 
447
- # Allows to specify an order by a specific set of values.
661
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
662
+ # ordered and filtered by a specific set of +values+.
448
663
  #
449
664
  # User.in_order_of(:id, [1, 5, 3])
450
665
  # # SELECT "users".* FROM "users"
@@ -455,15 +670,41 @@ module ActiveRecord
455
670
  # # WHEN "users"."id" = 3 THEN 3
456
671
  # # END ASC
457
672
  #
673
+ # +column+ can point to an enum column; the actual query generated may be different depending
674
+ # on the database adapter and the column definition.
675
+ #
676
+ # class Conversation < ActiveRecord::Base
677
+ # enum :status, [ :active, :archived ]
678
+ # end
679
+ #
680
+ # Conversation.in_order_of(:status, [:archived, :active])
681
+ # # SELECT "conversations".* FROM "conversations"
682
+ # # WHERE "conversations"."status" IN (1, 0)
683
+ # # ORDER BY CASE
684
+ # # WHEN "conversations"."status" = 1 THEN 1
685
+ # # WHEN "conversations"."status" = 0 THEN 2
686
+ # # END ASC
687
+ #
688
+ # +values+ can also include +nil+.
689
+ #
690
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
691
+ # # SELECT "conversations".* FROM "conversations"
692
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
693
+ # # ORDER BY CASE
694
+ # # WHEN "conversations"."status" IS NULL THEN 1
695
+ # # WHEN "conversations"."status" = 1 THEN 2
696
+ # # WHEN "conversations"."status" = 0 THEN 3
697
+ # # END ASC
698
+ #
458
699
  def in_order_of(column, values)
459
- klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
700
+ klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
460
701
  return spawn.none! if values.empty?
461
702
 
462
703
  references = column_references([column])
463
704
  self.references_values |= references unless references.empty?
464
705
 
465
706
  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
707
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
467
708
 
468
709
  where_clause =
469
710
  if values.include?(nil)
@@ -485,7 +726,7 @@ module ActiveRecord
485
726
  #
486
727
  # User.order('email DESC').reorder('id ASC').order('name ASC')
487
728
  #
488
- # generates a query with 'ORDER BY id ASC, name ASC'.
729
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
489
730
  def reorder(*args)
490
731
  check_if_method_has_arguments!(__callee__, args) do
491
732
  sanitize_order_arguments(args)
@@ -504,7 +745,8 @@ module ActiveRecord
504
745
 
505
746
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
506
747
  :limit, :offset, :joins, :left_outer_joins, :annotate,
507
- :includes, :from, :readonly, :having, :optimizer_hints])
748
+ :includes, :eager_load, :preload, :from, :readonly,
749
+ :having, :optimizer_hints, :with])
508
750
 
509
751
  # Removes an unwanted relation that is already defined on a chain of relations.
510
752
  # This is useful when passing around chains of relations and would like to
@@ -554,7 +796,7 @@ module ActiveRecord
554
796
  if !VALID_UNSCOPING_VALUES.include?(scope)
555
797
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
556
798
  end
557
- assert_mutability!
799
+ assert_modifiable!
558
800
  @values.delete(scope)
559
801
  when Hash
560
802
  scope.each do |key, target_value|
@@ -614,7 +856,7 @@ module ActiveRecord
614
856
  # Performs LEFT OUTER JOINs on +args+:
615
857
  #
616
858
  # User.left_outer_joins(:posts)
617
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
859
+ # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
618
860
  #
619
861
  def left_outer_joins(*args)
620
862
  check_if_method_has_arguments!(__callee__, args)
@@ -634,7 +876,7 @@ module ActiveRecord
634
876
  # SQL is given as an illustration; the actual query generated may be different depending
635
877
  # on the database adapter.
636
878
  #
637
- # === string
879
+ # === \String
638
880
  #
639
881
  # A single string, without additional arguments, is passed to the query
640
882
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -646,7 +888,7 @@ module ActiveRecord
646
888
  # to injection attacks if not done properly. As an alternative, it is recommended
647
889
  # to use one of the following methods.
648
890
  #
649
- # === array
891
+ # === \Array
650
892
  #
651
893
  # If an array is passed, then the first element of the array is treated as a template, and
652
894
  # the remaining elements are inserted into the template to generate the condition.
@@ -686,7 +928,7 @@ module ActiveRecord
686
928
  # dependencies on the underlying database. If your code is intended for general consumption,
687
929
  # test with multiple database backends.
688
930
  #
689
- # === hash
931
+ # === \Hash
690
932
  #
691
933
  # #where will also accept a hash condition, in which the keys are fields and the values
692
934
  # are values to be searched for.
@@ -720,6 +962,12 @@ module ActiveRecord
720
962
  # PriceEstimate.where(estimate_of: treasure)
721
963
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
722
964
  #
965
+ # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
966
+ # an array of columns with an array of tuples as values.
967
+ #
968
+ # Article.where([:author_id, :id] => [[15, 1], [15, 2]])
969
+ # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
970
+ #
723
971
  # === Joins
724
972
  #
725
973
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -732,7 +980,7 @@ module ActiveRecord
732
980
  # User.joins(:posts).where("posts.published" => true)
733
981
  # User.joins(:posts).where(posts: { published: true })
734
982
  #
735
- # === no argument
983
+ # === No Argument
736
984
  #
737
985
  # If no argument is passed, #where returns a new instance of WhereChain, that
738
986
  # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
@@ -756,7 +1004,7 @@ module ActiveRecord
756
1004
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
757
1005
  # # WHERE "authors"."id" IS NULL
758
1006
  #
759
- # === blank condition
1007
+ # === Blank Condition
760
1008
  #
761
1009
  # If the condition is any blank-ish object, then #where is a no-op and returns
762
1010
  # the current relation.
@@ -789,6 +1037,8 @@ module ActiveRecord
789
1037
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
790
1038
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
791
1039
  def rewhere(conditions)
1040
+ return unscope(:where) if conditions.nil?
1041
+
792
1042
  scope = spawn
793
1043
  where_clause = scope.build_where_clause(conditions)
794
1044
 
@@ -894,7 +1144,11 @@ module ActiveRecord
894
1144
  #
895
1145
  def or(other)
896
1146
  if other.is_a?(Relation)
897
- spawn.or!(other)
1147
+ if @none
1148
+ other.spawn
1149
+ else
1150
+ spawn.or!(other)
1151
+ end
898
1152
  else
899
1153
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
900
1154
  end
@@ -907,7 +1161,7 @@ module ActiveRecord
907
1161
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
908
1162
  end
909
1163
 
910
- self.where_clause = self.where_clause.or(other.where_clause)
1164
+ self.where_clause = where_clause.or(other.where_clause)
911
1165
  self.having_clause = having_clause.or(other.having_clause)
912
1166
  self.references_values |= other.references_values
913
1167
 
@@ -1007,15 +1261,29 @@ module ActiveRecord
1007
1261
  end
1008
1262
 
1009
1263
  def none! # :nodoc:
1010
- where!("1=0").extending!(NullRelation)
1264
+ unless @none
1265
+ where!("1=0")
1266
+ @none = true
1267
+ end
1268
+ self
1011
1269
  end
1012
1270
 
1013
- # Sets readonly attributes for the returned relation. If value is
1014
- # true (default), attempting to update a record will result in an error.
1271
+ def null_relation? # :nodoc:
1272
+ @none
1273
+ end
1274
+
1275
+ # Mark a relation as readonly. Attempting to update a record will result in
1276
+ # an error.
1015
1277
  #
1016
1278
  # users = User.readonly
1017
1279
  # users.first.save
1018
1280
  # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1281
+ #
1282
+ # To make a readonly relation writable, pass +false+.
1283
+ #
1284
+ # users.readonly(false)
1285
+ # users.first.save
1286
+ # => true
1019
1287
  def readonly(value = true)
1020
1288
  spawn.readonly!(value)
1021
1289
  end
@@ -1132,7 +1400,7 @@ module ActiveRecord
1132
1400
  #
1133
1401
  # The object returned is a relation, which can be further extended.
1134
1402
  #
1135
- # === Using a module
1403
+ # === Using a \Module
1136
1404
  #
1137
1405
  # module Pagination
1138
1406
  # def page(number)
@@ -1147,7 +1415,7 @@ module ActiveRecord
1147
1415
  #
1148
1416
  # scope = Model.all.extending(Pagination, SomethingElse)
1149
1417
  #
1150
- # === Using a block
1418
+ # === Using a Block
1151
1419
  #
1152
1420
  # scope = Model.all.extending do
1153
1421
  # def page(number)
@@ -1264,6 +1532,9 @@ module ActiveRecord
1264
1532
  # Post.excluding(post_one, post_two)
1265
1533
  # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1266
1534
  #
1535
+ # Post.excluding(Post.drafts)
1536
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1537
+ #
1267
1538
  # This can also be called on associations. As with the above example, either
1268
1539
  # a single record of collection thereof may be specified:
1269
1540
  #
@@ -1279,14 +1550,15 @@ module ActiveRecord
1279
1550
  # is passed in) are not instances of the same model that the relation is
1280
1551
  # scoping.
1281
1552
  def excluding(*records)
1553
+ relations = records.extract! { |element| element.is_a?(Relation) }
1282
1554
  records.flatten!(1)
1283
1555
  records.compact!
1284
1556
 
1285
- unless records.all?(klass)
1557
+ unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1286
1558
  raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1287
1559
  end
1288
1560
 
1289
- spawn.excluding!(records)
1561
+ spawn.excluding!(records + relations.flat_map(&:ids))
1290
1562
  end
1291
1563
  alias :without :excluding
1292
1564
 
@@ -1298,7 +1570,7 @@ module ActiveRecord
1298
1570
 
1299
1571
  # Returns the Arel object associated with the relation.
1300
1572
  def arel(aliases = nil) # :nodoc:
1301
- @arel ||= build_arel(aliases)
1573
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1302
1574
  end
1303
1575
 
1304
1576
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1319,13 +1591,29 @@ module ActiveRecord
1319
1591
  def build_where_clause(opts, rest = []) # :nodoc:
1320
1592
  opts = sanitize_forbidden_attributes(opts)
1321
1593
 
1594
+ if opts.is_a?(Array)
1595
+ opts, *rest = opts
1596
+ end
1597
+
1322
1598
  case opts
1323
- when String, Array
1324
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1599
+ when String
1600
+ if rest.empty?
1601
+ parts = [Arel.sql(opts)]
1602
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1603
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1604
+ elsif opts.include?("?")
1605
+ parts = [build_bound_sql_literal(opts, rest)]
1606
+ else
1607
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1608
+ end
1325
1609
  when Hash
1326
1610
  opts = opts.transform_keys do |key|
1327
- key = key.to_s
1328
- klass.attribute_aliases[key] || key
1611
+ if key.is_a?(Array)
1612
+ key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1613
+ else
1614
+ key = key.to_s
1615
+ klass.attribute_aliases[key] || key
1616
+ end
1329
1617
  end
1330
1618
  references = PredicateBuilder.references(opts)
1331
1619
  self.references_values |= references unless references.empty?
@@ -1343,7 +1631,76 @@ module ActiveRecord
1343
1631
  end
1344
1632
  alias :build_having_clause :build_where_clause
1345
1633
 
1634
+ def async!
1635
+ @async = true
1636
+ self
1637
+ end
1638
+
1639
+ protected
1640
+ def arel_columns(columns)
1641
+ columns.flat_map do |field|
1642
+ case field
1643
+ when Symbol
1644
+ arel_column(field.to_s) do |attr_name|
1645
+ adapter_class.quote_table_name(attr_name)
1646
+ end
1647
+ when String
1648
+ arel_column(field, &:itself)
1649
+ when Proc
1650
+ field.call
1651
+ when Hash
1652
+ arel_columns_from_hash(field)
1653
+ else
1654
+ field
1655
+ end
1656
+ end
1657
+ end
1658
+
1346
1659
  private
1660
+ def async
1661
+ spawn.async!
1662
+ end
1663
+
1664
+ def build_named_bound_sql_literal(statement, values)
1665
+ bound_values = values.transform_values do |value|
1666
+ if ActiveRecord::Relation === value
1667
+ Arel.sql(value.to_sql)
1668
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1669
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1670
+ values.empty? ? nil : values
1671
+ else
1672
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1673
+ value
1674
+ end
1675
+ end
1676
+
1677
+ begin
1678
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1679
+ rescue Arel::BindError => error
1680
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1681
+ end
1682
+ end
1683
+
1684
+ def build_bound_sql_literal(statement, values)
1685
+ bound_values = values.map do |value|
1686
+ if ActiveRecord::Relation === value
1687
+ Arel.sql(value.to_sql)
1688
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1689
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1690
+ values.empty? ? nil : values
1691
+ else
1692
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1693
+ value
1694
+ end
1695
+ end
1696
+
1697
+ begin
1698
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1699
+ rescue Arel::BindError => error
1700
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1701
+ end
1702
+ end
1703
+
1347
1704
  def lookup_table_klass_from_join_dependencies(table_name)
1348
1705
  each_join_dependencies do |join|
1349
1706
  return join.base_klass if table_name == join.table_name
@@ -1358,22 +1715,21 @@ module ActiveRecord
1358
1715
  end
1359
1716
 
1360
1717
  def build_join_dependencies
1361
- associations = joins_values | left_outer_joins_values
1362
- associations |= eager_load_values unless eager_load_values.empty?
1363
- associations |= includes_values unless includes_values.empty?
1718
+ joins = joins_values | left_outer_joins_values
1719
+ joins |= eager_load_values unless eager_load_values.empty?
1720
+ joins |= includes_values unless includes_values.empty?
1364
1721
 
1365
1722
  join_dependencies = []
1366
1723
  join_dependencies.unshift construct_join_dependency(
1367
- select_association_list(associations, join_dependencies), nil
1724
+ select_named_joins(joins, join_dependencies), nil
1368
1725
  )
1369
1726
  end
1370
1727
 
1371
- def assert_mutability!
1372
- raise ImmutableRelation if @loaded
1373
- raise ImmutableRelation if defined?(@arel) && @arel
1728
+ def assert_modifiable!
1729
+ raise UnmodifiableRelation if @loaded || @arel
1374
1730
  end
1375
1731
 
1376
- def build_arel(aliases = nil)
1732
+ def build_arel(connection, aliases = nil)
1377
1733
  arel = Arel::SelectManager.new(table)
1378
1734
 
1379
1735
  build_joins(arel.join_sources, aliases)
@@ -1385,6 +1741,7 @@ module ActiveRecord
1385
1741
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1386
1742
 
1387
1743
  build_order(arel)
1744
+ build_with(arel)
1388
1745
  build_select(arel)
1389
1746
 
1390
1747
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1420,6 +1777,18 @@ module ActiveRecord
1420
1777
  end
1421
1778
  end
1422
1779
 
1780
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1781
+ cte_joins, associations = join_names.partition do |join_name|
1782
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1783
+ end
1784
+
1785
+ cte_joins.each do |cte_name|
1786
+ block&.call(CTEJoin.new(cte_name))
1787
+ end
1788
+
1789
+ select_association_list(associations, stashed_joins, &block)
1790
+ end
1791
+
1423
1792
  def select_association_list(associations, stashed_joins = nil)
1424
1793
  result = []
1425
1794
  associations.each do |association|
@@ -1435,20 +1804,21 @@ module ActiveRecord
1435
1804
  result
1436
1805
  end
1437
1806
 
1438
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1439
- end
1440
-
1441
1807
  def build_join_buckets
1442
1808
  buckets = Hash.new { |h, k| h[k] = [] }
1443
1809
 
1444
1810
  unless left_outer_joins_values.empty?
1445
1811
  stashed_left_joins = []
1446
- left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1447
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1812
+ left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
1813
+ if left_join.is_a?(CTEJoin)
1814
+ buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
1815
+ else
1816
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1817
+ end
1448
1818
  end
1449
1819
 
1450
1820
  if joins_values.empty?
1451
- buckets[:association_join] = left_joins
1821
+ buckets[:named_join] = left_joins
1452
1822
  buckets[:stashed_join] = stashed_left_joins
1453
1823
  return buckets, Arel::Nodes::OuterJoin
1454
1824
  else
@@ -1474,9 +1844,11 @@ module ActiveRecord
1474
1844
  end
1475
1845
  end
1476
1846
 
1477
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1847
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1478
1848
  if join.is_a?(Arel::Nodes::Join)
1479
1849
  buckets[:join_node] << join
1850
+ elsif join.is_a?(CTEJoin)
1851
+ buckets[:join_node] << build_with_join_node(join.name)
1480
1852
  else
1481
1853
  raise "unknown class: %s" % join.class.name
1482
1854
  end
@@ -1493,16 +1865,16 @@ module ActiveRecord
1493
1865
 
1494
1866
  buckets, join_type = build_join_buckets
1495
1867
 
1496
- association_joins = buckets[:association_join]
1497
- stashed_joins = buckets[:stashed_join]
1498
- leading_joins = buckets[:leading_join]
1499
- join_nodes = buckets[:join_node]
1868
+ named_joins = buckets[:named_join]
1869
+ stashed_joins = buckets[:stashed_join]
1870
+ leading_joins = buckets[:leading_join]
1871
+ join_nodes = buckets[:join_node]
1500
1872
 
1501
1873
  join_sources.concat(leading_joins) unless leading_joins.empty?
1502
1874
 
1503
- unless association_joins.empty? && stashed_joins.empty?
1875
+ unless named_joins.empty? && stashed_joins.empty?
1504
1876
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1505
- join_dependency = construct_join_dependency(association_joins, join_type)
1877
+ join_dependency = construct_join_dependency(named_joins, join_type)
1506
1878
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1507
1879
  end
1508
1880
 
@@ -1520,31 +1892,65 @@ module ActiveRecord
1520
1892
  end
1521
1893
  end
1522
1894
 
1523
- def arel_columns(columns)
1524
- columns.flat_map do |field|
1525
- case field
1526
- when Symbol
1527
- arel_column(field.to_s) do |attr_name|
1528
- connection.quote_table_name(attr_name)
1529
- end
1530
- when String
1531
- arel_column(field, &:itself)
1532
- when Proc
1533
- field.call
1895
+ def build_with(arel)
1896
+ return if with_values.empty?
1897
+
1898
+ with_statements = with_values.map do |with_value|
1899
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1900
+
1901
+ build_with_value_from_hash(with_value)
1902
+ end
1903
+
1904
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1905
+ end
1906
+
1907
+ def build_with_value_from_hash(hash)
1908
+ hash.map do |name, value|
1909
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1910
+ end
1911
+ end
1912
+
1913
+ def build_with_expression_from_value(value, nested = false)
1914
+ case value
1915
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1916
+ when ActiveRecord::Relation
1917
+ if nested
1918
+ value.arel.ast
1534
1919
  else
1535
- field
1920
+ value.arel
1536
1921
  end
1922
+ when Arel::SelectManager then value
1923
+ when Array
1924
+ return build_with_expression_from_value(value.first, false) if value.size == 1
1925
+
1926
+ parts = value.map do |query|
1927
+ build_with_expression_from_value(query, true)
1928
+ end
1929
+
1930
+ parts.reduce do |result, value|
1931
+ Arel::Nodes::UnionAll.new(result, value)
1932
+ end
1933
+ else
1934
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1537
1935
  end
1538
1936
  end
1539
1937
 
1938
+ def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
1939
+ with_table = Arel::Table.new(name)
1940
+
1941
+ table.join(with_table, kind).on(
1942
+ with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1943
+ ).join_sources.first
1944
+ end
1945
+
1540
1946
  def arel_column(field)
1541
1947
  field = klass.attribute_aliases[field] || field
1542
1948
  from = from_clause.name || from_clause.value
1543
1949
 
1544
1950
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1545
1951
  table[field]
1546
- elsif field.match?(/\A\w+\.\w+\z/)
1547
- table, column = field.split(".")
1952
+ elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1953
+ self.references_values |= [Arel.sql(table, retryable: true)]
1548
1954
  predicate_builder.resolve_arel_attribute(table, column) do
1549
1955
  lookup_table_klass_from_join_dependencies(table)
1550
1956
  end
@@ -1555,7 +1961,7 @@ module ActiveRecord
1555
1961
 
1556
1962
  def table_name_matches?(from)
1557
1963
  table_name = Regexp.escape(table.name)
1558
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1964
+ quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
1559
1965
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1560
1966
  end
1561
1967
 
@@ -1611,7 +2017,9 @@ module ActiveRecord
1611
2017
  args.each do |arg|
1612
2018
  next unless arg.is_a?(Hash)
1613
2019
  arg.each do |_key, value|
1614
- unless VALID_DIRECTIONS.include?(value)
2020
+ if value.is_a?(Hash)
2021
+ validate_order_args([value])
2022
+ elsif VALID_DIRECTIONS.exclude?(value)
1615
2023
  raise ArgumentError,
1616
2024
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1617
2025
  end
@@ -1619,10 +2027,14 @@ module ActiveRecord
1619
2027
  end
1620
2028
  end
1621
2029
 
2030
+ def flattened_args(args)
2031
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2032
+ end
2033
+
1622
2034
  def preprocess_order_args(order_args)
1623
2035
  @klass.disallow_raw_sql!(
1624
- order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1625
- permit: connection.column_name_with_order_matcher
2036
+ flattened_args(order_args),
2037
+ permit: model.adapter_class.column_name_with_order_matcher
1626
2038
  )
1627
2039
 
1628
2040
  validate_order_args(order_args)
@@ -1636,14 +2048,20 @@ module ActiveRecord
1636
2048
  when Symbol
1637
2049
  order_column(arg.to_s).asc
1638
2050
  when Hash
1639
- arg.map { |field, dir|
1640
- case field
1641
- when Arel::Nodes::SqlLiteral
1642
- field.public_send(dir.downcase)
2051
+ arg.map do |key, value|
2052
+ if value.is_a?(Hash)
2053
+ value.map do |field, dir|
2054
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2055
+ end
1643
2056
  else
1644
- order_column(field.to_s).public_send(dir.downcase)
2057
+ case key
2058
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2059
+ key.public_send(value.downcase)
2060
+ else
2061
+ order_column(key.to_s).public_send(value.downcase)
2062
+ end
1645
2063
  end
1646
- }
2064
+ end
1647
2065
  else
1648
2066
  arg
1649
2067
  end
@@ -1657,16 +2075,32 @@ module ActiveRecord
1657
2075
  end
1658
2076
 
1659
2077
  def column_references(order_args)
1660
- references = order_args.flat_map do |arg|
2078
+ order_args.flat_map do |arg|
1661
2079
  case arg
1662
2080
  when String, Symbol
1663
- arg
2081
+ extract_table_name_from(arg)
1664
2082
  when Hash
1665
- arg.keys
2083
+ arg
2084
+ .map do |key, value|
2085
+ case value
2086
+ when Hash
2087
+ key.to_s
2088
+ else
2089
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
2090
+ end
2091
+ end
2092
+ when Arel::Attribute
2093
+ arg.relation.name
2094
+ when Arel::Nodes::Ordering
2095
+ if arg.expr.is_a?(Arel::Attribute)
2096
+ arg.expr.relation.name
2097
+ end
1666
2098
  end
1667
- end
1668
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1669
- references
2099
+ end.compact
2100
+ end
2101
+
2102
+ def extract_table_name_from(string)
2103
+ string.match(/^\W?(\w+)\W?\./) && $1
1670
2104
  end
1671
2105
 
1672
2106
  def order_column(field)
@@ -1674,7 +2108,7 @@ module ActiveRecord
1674
2108
  if attr_name == "count" && !group_values.empty?
1675
2109
  table[attr_name]
1676
2110
  else
1677
- Arel.sql(connection.quote_table_name(attr_name))
2111
+ Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
1678
2112
  end
1679
2113
  end
1680
2114
  end
@@ -1739,6 +2173,41 @@ module ActiveRecord
1739
2173
  end
1740
2174
  end
1741
2175
 
2176
+ def process_select_args(fields)
2177
+ fields.flat_map do |field|
2178
+ if field.is_a?(Hash)
2179
+ arel_columns_from_hash(field)
2180
+ else
2181
+ field
2182
+ end
2183
+ end
2184
+ end
2185
+
2186
+ def arel_columns_from_hash(fields)
2187
+ fields.flat_map do |key, columns_aliases|
2188
+ case columns_aliases
2189
+ when Hash
2190
+ columns_aliases.map do |column, column_alias|
2191
+ if values[:joins]&.include?(key)
2192
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2193
+ self.references_values |= references unless references.empty?
2194
+ end
2195
+ arel_column("#{key}.#{column}") do
2196
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2197
+ end.as(column_alias.to_s)
2198
+ end
2199
+ when Array
2200
+ columns_aliases.map do |column|
2201
+ arel_column("#{key}.#{column}", &:itself)
2202
+ end
2203
+ when String, Symbol
2204
+ arel_column(key.to_s) do
2205
+ predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2206
+ end.as(columns_aliases.to_s)
2207
+ end
2208
+ end
2209
+ end
2210
+
1742
2211
  STRUCTURAL_VALUE_METHODS = (
1743
2212
  Relation::VALUE_METHODS -
1744
2213
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]