activerecord 7.0.8 → 7.2.0

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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  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 +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +5 -5
  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 +328 -471
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +7 -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 +58 -45
  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 +10 -24
  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 +317 -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 +188 -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 +306 -128
  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 +274 -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 +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  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 +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  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 +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  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/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. 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.
187
+ #
188
+ # For example:
162
189
  #
163
- # users = User.includes(:address)
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)
197
+ #
198
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
199
+ # are loaded with a single query.
171
200
  #
172
- # You can also specify multiple relationships, like this:
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.
173
204
  #
174
- # users = User.includes(:address, :friends)
205
+ # You can also specify multiple associations. Each association will result
206
+ # in an additional query:
175
207
  #
176
- # Loading nested relationships is possible using a Hash:
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:
210
259
  #
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"
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.
271
+ #
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
1269
+ end
1270
+
1271
+ def null_relation? # :nodoc:
1272
+ @none
1011
1273
  end
1012
1274
 
1013
- # Sets readonly attributes for the returned relation. If value is
1014
- # true (default), attempting to update a record will result in an error.
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,56 @@ 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
+
1346
1639
  private
1640
+ def async
1641
+ spawn.async!
1642
+ end
1643
+
1644
+ def build_named_bound_sql_literal(statement, values)
1645
+ bound_values = values.transform_values do |value|
1646
+ if ActiveRecord::Relation === value
1647
+ Arel.sql(value.to_sql)
1648
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1649
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1650
+ values.empty? ? nil : values
1651
+ else
1652
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1653
+ value
1654
+ end
1655
+ end
1656
+
1657
+ begin
1658
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1659
+ rescue Arel::BindError => error
1660
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1661
+ end
1662
+ end
1663
+
1664
+ def build_bound_sql_literal(statement, values)
1665
+ bound_values = values.map 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})", bound_values, nil)
1679
+ rescue Arel::BindError => error
1680
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1681
+ end
1682
+ end
1683
+
1347
1684
  def lookup_table_klass_from_join_dependencies(table_name)
1348
1685
  each_join_dependencies do |join|
1349
1686
  return join.base_klass if table_name == join.table_name
@@ -1358,22 +1695,21 @@ module ActiveRecord
1358
1695
  end
1359
1696
 
1360
1697
  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?
1698
+ joins = joins_values | left_outer_joins_values
1699
+ joins |= eager_load_values unless eager_load_values.empty?
1700
+ joins |= includes_values unless includes_values.empty?
1364
1701
 
1365
1702
  join_dependencies = []
1366
1703
  join_dependencies.unshift construct_join_dependency(
1367
- select_association_list(associations, join_dependencies), nil
1704
+ select_named_joins(joins, join_dependencies), nil
1368
1705
  )
1369
1706
  end
1370
1707
 
1371
- def assert_mutability!
1372
- raise ImmutableRelation if @loaded
1373
- raise ImmutableRelation if defined?(@arel) && @arel
1708
+ def assert_modifiable!
1709
+ raise UnmodifiableRelation if @loaded || @arel
1374
1710
  end
1375
1711
 
1376
- def build_arel(aliases = nil)
1712
+ def build_arel(connection, aliases = nil)
1377
1713
  arel = Arel::SelectManager.new(table)
1378
1714
 
1379
1715
  build_joins(arel.join_sources, aliases)
@@ -1385,6 +1721,7 @@ module ActiveRecord
1385
1721
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1386
1722
 
1387
1723
  build_order(arel)
1724
+ build_with(arel)
1388
1725
  build_select(arel)
1389
1726
 
1390
1727
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1420,6 +1757,18 @@ module ActiveRecord
1420
1757
  end
1421
1758
  end
1422
1759
 
1760
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1761
+ cte_joins, associations = join_names.partition do |join_name|
1762
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1763
+ end
1764
+
1765
+ cte_joins.each do |cte_name|
1766
+ block&.call(CTEJoin.new(cte_name))
1767
+ end
1768
+
1769
+ select_association_list(associations, stashed_joins, &block)
1770
+ end
1771
+
1423
1772
  def select_association_list(associations, stashed_joins = nil)
1424
1773
  result = []
1425
1774
  associations.each do |association|
@@ -1435,20 +1784,21 @@ module ActiveRecord
1435
1784
  result
1436
1785
  end
1437
1786
 
1438
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1439
- end
1440
-
1441
1787
  def build_join_buckets
1442
1788
  buckets = Hash.new { |h, k| h[k] = [] }
1443
1789
 
1444
1790
  unless left_outer_joins_values.empty?
1445
1791
  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"
1792
+ left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
1793
+ if left_join.is_a?(CTEJoin)
1794
+ buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
1795
+ else
1796
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1797
+ end
1448
1798
  end
1449
1799
 
1450
1800
  if joins_values.empty?
1451
- buckets[:association_join] = left_joins
1801
+ buckets[:named_join] = left_joins
1452
1802
  buckets[:stashed_join] = stashed_left_joins
1453
1803
  return buckets, Arel::Nodes::OuterJoin
1454
1804
  else
@@ -1474,9 +1824,11 @@ module ActiveRecord
1474
1824
  end
1475
1825
  end
1476
1826
 
1477
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1827
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1478
1828
  if join.is_a?(Arel::Nodes::Join)
1479
1829
  buckets[:join_node] << join
1830
+ elsif join.is_a?(CTEJoin)
1831
+ buckets[:join_node] << build_with_join_node(join.name)
1480
1832
  else
1481
1833
  raise "unknown class: %s" % join.class.name
1482
1834
  end
@@ -1493,16 +1845,16 @@ module ActiveRecord
1493
1845
 
1494
1846
  buckets, join_type = build_join_buckets
1495
1847
 
1496
- association_joins = buckets[:association_join]
1497
- stashed_joins = buckets[:stashed_join]
1498
- leading_joins = buckets[:leading_join]
1499
- join_nodes = buckets[:join_node]
1848
+ named_joins = buckets[:named_join]
1849
+ stashed_joins = buckets[:stashed_join]
1850
+ leading_joins = buckets[:leading_join]
1851
+ join_nodes = buckets[:join_node]
1500
1852
 
1501
1853
  join_sources.concat(leading_joins) unless leading_joins.empty?
1502
1854
 
1503
- unless association_joins.empty? && stashed_joins.empty?
1855
+ unless named_joins.empty? && stashed_joins.empty?
1504
1856
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1505
- join_dependency = construct_join_dependency(association_joins, join_type)
1857
+ join_dependency = construct_join_dependency(named_joins, join_type)
1506
1858
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1507
1859
  end
1508
1860
 
@@ -1520,17 +1872,56 @@ module ActiveRecord
1520
1872
  end
1521
1873
  end
1522
1874
 
1875
+ def build_with(arel)
1876
+ return if with_values.empty?
1877
+
1878
+ with_statements = with_values.map do |with_value|
1879
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1880
+
1881
+ build_with_value_from_hash(with_value)
1882
+ end
1883
+
1884
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1885
+ end
1886
+
1887
+ def build_with_value_from_hash(hash)
1888
+ hash.map do |name, value|
1889
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1890
+ end
1891
+ end
1892
+
1893
+ def build_with_expression_from_value(value)
1894
+ case value
1895
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1896
+ when ActiveRecord::Relation then value.arel
1897
+ when Arel::SelectManager then value
1898
+ when Array then value.map { |q| build_with_expression_from_value(q) }.reduce { |result, value| result.union(:all, value) }
1899
+ else
1900
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1901
+ end
1902
+ end
1903
+
1904
+ def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
1905
+ with_table = Arel::Table.new(name)
1906
+
1907
+ table.join(with_table, kind).on(
1908
+ with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1909
+ ).join_sources.first
1910
+ end
1911
+
1523
1912
  def arel_columns(columns)
1524
1913
  columns.flat_map do |field|
1525
1914
  case field
1526
1915
  when Symbol
1527
1916
  arel_column(field.to_s) do |attr_name|
1528
- connection.quote_table_name(attr_name)
1917
+ adapter_class.quote_table_name(attr_name)
1529
1918
  end
1530
1919
  when String
1531
1920
  arel_column(field, &:itself)
1532
1921
  when Proc
1533
1922
  field.call
1923
+ when Hash
1924
+ arel_columns_from_hash(field)
1534
1925
  else
1535
1926
  field
1536
1927
  end
@@ -1555,7 +1946,7 @@ module ActiveRecord
1555
1946
 
1556
1947
  def table_name_matches?(from)
1557
1948
  table_name = Regexp.escape(table.name)
1558
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1949
+ quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
1559
1950
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1560
1951
  end
1561
1952
 
@@ -1611,7 +2002,9 @@ module ActiveRecord
1611
2002
  args.each do |arg|
1612
2003
  next unless arg.is_a?(Hash)
1613
2004
  arg.each do |_key, value|
1614
- unless VALID_DIRECTIONS.include?(value)
2005
+ if value.is_a?(Hash)
2006
+ validate_order_args([value])
2007
+ elsif VALID_DIRECTIONS.exclude?(value)
1615
2008
  raise ArgumentError,
1616
2009
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1617
2010
  end
@@ -1619,10 +2012,14 @@ module ActiveRecord
1619
2012
  end
1620
2013
  end
1621
2014
 
2015
+ def flattened_args(args)
2016
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2017
+ end
2018
+
1622
2019
  def preprocess_order_args(order_args)
1623
2020
  @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
2021
+ flattened_args(order_args),
2022
+ permit: model.adapter_class.column_name_with_order_matcher
1626
2023
  )
1627
2024
 
1628
2025
  validate_order_args(order_args)
@@ -1636,14 +2033,20 @@ module ActiveRecord
1636
2033
  when Symbol
1637
2034
  order_column(arg.to_s).asc
1638
2035
  when Hash
1639
- arg.map { |field, dir|
1640
- case field
1641
- when Arel::Nodes::SqlLiteral
1642
- field.public_send(dir.downcase)
2036
+ arg.map do |key, value|
2037
+ if value.is_a?(Hash)
2038
+ value.map do |field, dir|
2039
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2040
+ end
1643
2041
  else
1644
- order_column(field.to_s).public_send(dir.downcase)
2042
+ case key
2043
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2044
+ key.public_send(value.downcase)
2045
+ else
2046
+ order_column(key.to_s).public_send(value.downcase)
2047
+ end
1645
2048
  end
1646
- }
2049
+ end
1647
2050
  else
1648
2051
  arg
1649
2052
  end
@@ -1657,16 +2060,32 @@ module ActiveRecord
1657
2060
  end
1658
2061
 
1659
2062
  def column_references(order_args)
1660
- references = order_args.flat_map do |arg|
2063
+ order_args.flat_map do |arg|
1661
2064
  case arg
1662
2065
  when String, Symbol
1663
- arg
2066
+ extract_table_name_from(arg)
1664
2067
  when Hash
1665
- arg.keys
2068
+ arg
2069
+ .map do |key, value|
2070
+ case value
2071
+ when Hash
2072
+ key.to_s
2073
+ else
2074
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
2075
+ end
2076
+ end
2077
+ when Arel::Attribute
2078
+ arg.relation.name
2079
+ when Arel::Nodes::Ordering
2080
+ if arg.expr.is_a?(Arel::Attribute)
2081
+ arg.expr.relation.name
2082
+ end
1666
2083
  end
1667
- end
1668
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1669
- references
2084
+ end.compact
2085
+ end
2086
+
2087
+ def extract_table_name_from(string)
2088
+ string.match(/^\W?(\w+)\W?\./) && $1
1670
2089
  end
1671
2090
 
1672
2091
  def order_column(field)
@@ -1674,7 +2093,7 @@ module ActiveRecord
1674
2093
  if attr_name == "count" && !group_values.empty?
1675
2094
  table[attr_name]
1676
2095
  else
1677
- Arel.sql(connection.quote_table_name(attr_name))
2096
+ Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
1678
2097
  end
1679
2098
  end
1680
2099
  end
@@ -1739,6 +2158,41 @@ module ActiveRecord
1739
2158
  end
1740
2159
  end
1741
2160
 
2161
+ def process_select_args(fields)
2162
+ fields.flat_map do |field|
2163
+ if field.is_a?(Hash)
2164
+ arel_columns_from_hash(field)
2165
+ else
2166
+ field
2167
+ end
2168
+ end
2169
+ end
2170
+
2171
+ def arel_columns_from_hash(fields)
2172
+ fields.flat_map do |key, columns_aliases|
2173
+ case columns_aliases
2174
+ when Hash
2175
+ columns_aliases.map do |column, column_alias|
2176
+ if values[:joins]&.include?(key)
2177
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2178
+ self.references_values |= references unless references.empty?
2179
+ end
2180
+ arel_column("#{key}.#{column}") do
2181
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2182
+ end.as(column_alias.to_s)
2183
+ end
2184
+ when Array
2185
+ columns_aliases.map do |column|
2186
+ arel_column("#{key}.#{column}", &:itself)
2187
+ end
2188
+ when String, Symbol
2189
+ arel_column(key.to_s) do
2190
+ predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2191
+ end.as(columns_aliases.to_s)
2192
+ end
2193
+ end
2194
+ end
2195
+
1742
2196
  STRUCTURAL_VALUE_METHODS = (
1743
2197
  Relation::VALUE_METHODS -
1744
2198
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]