activerecord 7.0.8.7 → 7.2.3

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 (283) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +781 -1777
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +30 -30
  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 +31 -23
  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 +40 -9
  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 +35 -21
  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 +4 -3
  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 +153 -33
  47. data/lib/active_record/attributes.rb +96 -71
  48. data/lib/active_record/autosave_association.rb +81 -39
  49. data/lib/active_record/base.rb +11 -7
  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 +343 -91
  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 +229 -64
  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 +142 -12
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
  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 +60 -55
  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 +108 -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 +153 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
  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 +57 -45
  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 +51 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
  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 +101 -105
  110. data/lib/active_record/core.rb +273 -178
  111. data/lib/active_record/counter_cache.rb +69 -35
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -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 +56 -27
  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 +46 -22
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
  130. data/lib/active_record/encryption/encryptor.rb +35 -19
  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 +130 -28
  143. data/lib/active_record/errors.rb +154 -34
  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 +48 -10
  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 +4 -4
  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 +236 -118
  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 +96 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +35 -10
  178. data/lib/active_record/railtie.rb +131 -87
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +147 -155
  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 +270 -108
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +97 -21
  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 +20 -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 +3 -2
  196. data/lib/active_record/relation/query_methods.rb +585 -109
  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 +15 -21
  200. data/lib/active_record/relation.rb +592 -92
  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 +90 -23
  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 +33 -11
  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 +23 -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 +108 -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 +3 -1
  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/crud.rb +2 -0
  248. data/lib/arel/delete_manager.rb +5 -0
  249. data/lib/arel/errors.rb +10 -0
  250. data/lib/arel/factory_methods.rb +4 -0
  251. data/lib/arel/nodes/binary.rb +6 -7
  252. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  253. data/lib/arel/nodes/cte.rb +36 -0
  254. data/lib/arel/nodes/delete_statement.rb +4 -2
  255. data/lib/arel/nodes/fragments.rb +35 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  257. data/lib/arel/nodes/leading_join.rb +8 -0
  258. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  259. data/lib/arel/nodes/node.rb +115 -5
  260. data/lib/arel/nodes/sql_literal.rb +13 -0
  261. data/lib/arel/nodes/table_alias.rb +4 -0
  262. data/lib/arel/nodes/update_statement.rb +4 -2
  263. data/lib/arel/nodes.rb +6 -2
  264. data/lib/arel/predications.rb +3 -1
  265. data/lib/arel/select_manager.rb +7 -3
  266. data/lib/arel/table.rb +9 -5
  267. data/lib/arel/tree_manager.rb +8 -3
  268. data/lib/arel/update_manager.rb +7 -1
  269. data/lib/arel/visitors/dot.rb +3 -0
  270. data/lib/arel/visitors/mysql.rb +17 -5
  271. data/lib/arel/visitors/postgresql.rb +1 -12
  272. data/lib/arel/visitors/sqlite.rb +25 -0
  273. data/lib/arel/visitors/to_sql.rb +114 -34
  274. data/lib/arel/visitors/visitor.rb +2 -2
  275. data/lib/arel.rb +21 -3
  276. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  277. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  278. data/lib/rails/generators/active_record/migration.rb +3 -1
  279. data/lib/rails/generators/active_record/model/USAGE +113 -0
  280. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  281. metadata +56 -17
  282. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  283. 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,104 @@ 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
+ args = process_with_args(args)
495
+ self.with_values |= args
496
+ self
497
+ end
498
+
499
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
500
+ #
501
+ # 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')])
502
+ # # => ActiveRecord::Relation
503
+ # # WITH post_and_replies AS (
504
+ # # (SELECT * FROM posts WHERE id = 42)
505
+ # # UNION ALL
506
+ # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
507
+ # # )
508
+ # # SELECT * FROM posts
509
+ #
510
+ # See `#with` for more information.
511
+ def with_recursive(*args)
512
+ check_if_method_has_arguments!(__callee__, args)
513
+ spawn.with_recursive!(*args)
514
+ end
515
+
516
+ # Like #with_recursive but modifies the relation in place.
517
+ def with_recursive!(*args) # :nodoc:
518
+ args = process_with_args(args)
519
+ self.with_values |= args
520
+ @with_is_recursive = true
521
+ self
522
+ end
523
+
330
524
  # Allows you to change a previously set select statement.
331
525
  #
332
526
  # Post.select(:title, :body)
@@ -339,6 +533,7 @@ module ActiveRecord
339
533
  # Note that we're unscoping the entire select statement.
340
534
  def reselect(*args)
341
535
  check_if_method_has_arguments!(__callee__, args)
536
+ args = process_select_args(args)
342
537
  spawn.reselect!(*args)
343
538
  end
344
539
 
@@ -378,6 +573,27 @@ module ActiveRecord
378
573
  self
379
574
  end
380
575
 
576
+ # Allows you to change a previously set group statement.
577
+ #
578
+ # Post.group(:title, :body)
579
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
580
+ #
581
+ # Post.group(:title, :body).regroup(:title)
582
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
583
+ #
584
+ # This is short-hand for <tt>unscope(:group).group(fields)</tt>.
585
+ # Note that we're unscoping the entire group statement.
586
+ def regroup(*args)
587
+ check_if_method_has_arguments!(__callee__, args)
588
+ spawn.regroup!(*args)
589
+ end
590
+
591
+ # Same as #regroup but operates on relation in-place instead of copying.
592
+ def regroup!(*args) # :nodoc:
593
+ self.group_values = args
594
+ self
595
+ end
596
+
381
597
  # Applies an <code>ORDER BY</code> clause to a query.
382
598
  #
383
599
  # #order accepts arguments in one of several formats.
@@ -426,7 +642,7 @@ module ActiveRecord
426
642
  # User.order(Arel.sql('end_date - start_date'))
427
643
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
428
644
  #
429
- # Custom query syntax, like JSON columns for Postgres, is supported in this way.
645
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
430
646
  #
431
647
  # User.order(Arel.sql("payload->>'kind'"))
432
648
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
@@ -444,7 +660,8 @@ module ActiveRecord
444
660
  self
445
661
  end
446
662
 
447
- # Allows to specify an order by a specific set of values.
663
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
664
+ # ordered and filtered by a specific set of +values+.
448
665
  #
449
666
  # User.in_order_of(:id, [1, 5, 3])
450
667
  # # SELECT "users".* FROM "users"
@@ -455,15 +672,41 @@ module ActiveRecord
455
672
  # # WHEN "users"."id" = 3 THEN 3
456
673
  # # END ASC
457
674
  #
675
+ # +column+ can point to an enum column; the actual query generated may be different depending
676
+ # on the database adapter and the column definition.
677
+ #
678
+ # class Conversation < ActiveRecord::Base
679
+ # enum :status, [ :active, :archived ]
680
+ # end
681
+ #
682
+ # Conversation.in_order_of(:status, [:archived, :active])
683
+ # # SELECT "conversations".* FROM "conversations"
684
+ # # WHERE "conversations"."status" IN (1, 0)
685
+ # # ORDER BY CASE
686
+ # # WHEN "conversations"."status" = 1 THEN 1
687
+ # # WHEN "conversations"."status" = 0 THEN 2
688
+ # # END ASC
689
+ #
690
+ # +values+ can also include +nil+.
691
+ #
692
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
693
+ # # SELECT "conversations".* FROM "conversations"
694
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
695
+ # # ORDER BY CASE
696
+ # # WHEN "conversations"."status" IS NULL THEN 1
697
+ # # WHEN "conversations"."status" = 1 THEN 2
698
+ # # WHEN "conversations"."status" = 0 THEN 3
699
+ # # END ASC
700
+ #
458
701
  def in_order_of(column, values)
459
- klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
702
+ klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
460
703
  return spawn.none! if values.empty?
461
704
 
462
705
  references = column_references([column])
463
706
  self.references_values |= references unless references.empty?
464
707
 
465
708
  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
709
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
467
710
 
468
711
  where_clause =
469
712
  if values.include?(nil)
@@ -485,7 +728,7 @@ module ActiveRecord
485
728
  #
486
729
  # User.order('email DESC').reorder('id ASC').order('name ASC')
487
730
  #
488
- # generates a query with 'ORDER BY id ASC, name ASC'.
731
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
489
732
  def reorder(*args)
490
733
  check_if_method_has_arguments!(__callee__, args) do
491
734
  sanitize_order_arguments(args)
@@ -504,7 +747,8 @@ module ActiveRecord
504
747
 
505
748
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
506
749
  :limit, :offset, :joins, :left_outer_joins, :annotate,
507
- :includes, :from, :readonly, :having, :optimizer_hints])
750
+ :includes, :eager_load, :preload, :from, :readonly,
751
+ :having, :optimizer_hints, :with])
508
752
 
509
753
  # Removes an unwanted relation that is already defined on a chain of relations.
510
754
  # This is useful when passing around chains of relations and would like to
@@ -554,7 +798,7 @@ module ActiveRecord
554
798
  if !VALID_UNSCOPING_VALUES.include?(scope)
555
799
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
556
800
  end
557
- assert_mutability!
801
+ assert_modifiable!
558
802
  @values.delete(scope)
559
803
  when Hash
560
804
  scope.each do |key, target_value|
@@ -614,7 +858,7 @@ module ActiveRecord
614
858
  # Performs LEFT OUTER JOINs on +args+:
615
859
  #
616
860
  # User.left_outer_joins(:posts)
617
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
861
+ # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
618
862
  #
619
863
  def left_outer_joins(*args)
620
864
  check_if_method_has_arguments!(__callee__, args)
@@ -634,7 +878,7 @@ module ActiveRecord
634
878
  # SQL is given as an illustration; the actual query generated may be different depending
635
879
  # on the database adapter.
636
880
  #
637
- # === string
881
+ # === \String
638
882
  #
639
883
  # A single string, without additional arguments, is passed to the query
640
884
  # constructor as an SQL fragment, and used in the where clause of the query.
@@ -646,7 +890,7 @@ module ActiveRecord
646
890
  # to injection attacks if not done properly. As an alternative, it is recommended
647
891
  # to use one of the following methods.
648
892
  #
649
- # === array
893
+ # === \Array
650
894
  #
651
895
  # If an array is passed, then the first element of the array is treated as a template, and
652
896
  # the remaining elements are inserted into the template to generate the condition.
@@ -686,7 +930,7 @@ module ActiveRecord
686
930
  # dependencies on the underlying database. If your code is intended for general consumption,
687
931
  # test with multiple database backends.
688
932
  #
689
- # === hash
933
+ # === \Hash
690
934
  #
691
935
  # #where will also accept a hash condition, in which the keys are fields and the values
692
936
  # are values to be searched for.
@@ -720,6 +964,12 @@ module ActiveRecord
720
964
  # PriceEstimate.where(estimate_of: treasure)
721
965
  # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
722
966
  #
967
+ # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
968
+ # an array of columns with an array of tuples as values.
969
+ #
970
+ # Article.where([:author_id, :id] => [[15, 1], [15, 2]])
971
+ # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
972
+ #
723
973
  # === Joins
724
974
  #
725
975
  # If the relation is the result of a join, you may create a condition which uses any of the
@@ -732,7 +982,7 @@ module ActiveRecord
732
982
  # User.joins(:posts).where("posts.published" => true)
733
983
  # User.joins(:posts).where(posts: { published: true })
734
984
  #
735
- # === no argument
985
+ # === No Argument
736
986
  #
737
987
  # If no argument is passed, #where returns a new instance of WhereChain, that
738
988
  # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
@@ -756,7 +1006,7 @@ module ActiveRecord
756
1006
  # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
757
1007
  # # WHERE "authors"."id" IS NULL
758
1008
  #
759
- # === blank condition
1009
+ # === Blank Condition
760
1010
  #
761
1011
  # If the condition is any blank-ish object, then #where is a no-op and returns
762
1012
  # the current relation.
@@ -789,6 +1039,8 @@ module ActiveRecord
789
1039
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
790
1040
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
791
1041
  def rewhere(conditions)
1042
+ return unscope(:where) if conditions.nil?
1043
+
792
1044
  scope = spawn
793
1045
  where_clause = scope.build_where_clause(conditions)
794
1046
 
@@ -894,7 +1146,11 @@ module ActiveRecord
894
1146
  #
895
1147
  def or(other)
896
1148
  if other.is_a?(Relation)
897
- spawn.or!(other)
1149
+ if @none
1150
+ other.spawn
1151
+ else
1152
+ spawn.or!(other)
1153
+ end
898
1154
  else
899
1155
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
900
1156
  end
@@ -907,7 +1163,7 @@ module ActiveRecord
907
1163
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
908
1164
  end
909
1165
 
910
- self.where_clause = self.where_clause.or(other.where_clause)
1166
+ self.where_clause = where_clause.or(other.where_clause)
911
1167
  self.having_clause = having_clause.or(other.having_clause)
912
1168
  self.references_values |= other.references_values
913
1169
 
@@ -1007,15 +1263,29 @@ module ActiveRecord
1007
1263
  end
1008
1264
 
1009
1265
  def none! # :nodoc:
1010
- where!("1=0").extending!(NullRelation)
1266
+ unless @none
1267
+ where!("1=0")
1268
+ @none = true
1269
+ end
1270
+ self
1271
+ end
1272
+
1273
+ def null_relation? # :nodoc:
1274
+ @none
1011
1275
  end
1012
1276
 
1013
- # Sets readonly attributes for the returned relation. If value is
1014
- # true (default), attempting to update a record will result in an error.
1277
+ # Mark a relation as readonly. Attempting to update a record will result in
1278
+ # an error.
1015
1279
  #
1016
1280
  # users = User.readonly
1017
1281
  # users.first.save
1018
- # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1282
+ # # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1283
+ #
1284
+ # To make a readonly relation writable, pass +false+.
1285
+ #
1286
+ # users.readonly(false)
1287
+ # users.first.save
1288
+ # # => true
1019
1289
  def readonly(value = true)
1020
1290
  spawn.readonly!(value)
1021
1291
  end
@@ -1030,7 +1300,7 @@ module ActiveRecord
1030
1300
  #
1031
1301
  # user = User.strict_loading.first
1032
1302
  # user.comments.to_a
1033
- # => ActiveRecord::StrictLoadingViolationError
1303
+ # # => ActiveRecord::StrictLoadingViolationError
1034
1304
  def strict_loading(value = true)
1035
1305
  spawn.strict_loading!(value)
1036
1306
  end
@@ -1132,7 +1402,7 @@ module ActiveRecord
1132
1402
  #
1133
1403
  # The object returned is a relation, which can be further extended.
1134
1404
  #
1135
- # === Using a module
1405
+ # === Using a \Module
1136
1406
  #
1137
1407
  # module Pagination
1138
1408
  # def page(number)
@@ -1147,7 +1417,7 @@ module ActiveRecord
1147
1417
  #
1148
1418
  # scope = Model.all.extending(Pagination, SomethingElse)
1149
1419
  #
1150
- # === Using a block
1420
+ # === Using a Block
1151
1421
  #
1152
1422
  # scope = Model.all.extending do
1153
1423
  # def page(number)
@@ -1264,6 +1534,9 @@ module ActiveRecord
1264
1534
  # Post.excluding(post_one, post_two)
1265
1535
  # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1266
1536
  #
1537
+ # Post.excluding(Post.drafts)
1538
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1539
+ #
1267
1540
  # This can also be called on associations. As with the above example, either
1268
1541
  # a single record of collection thereof may be specified:
1269
1542
  #
@@ -1279,14 +1552,15 @@ module ActiveRecord
1279
1552
  # is passed in) are not instances of the same model that the relation is
1280
1553
  # scoping.
1281
1554
  def excluding(*records)
1555
+ relations = records.extract! { |element| element.is_a?(Relation) }
1282
1556
  records.flatten!(1)
1283
1557
  records.compact!
1284
1558
 
1285
- unless records.all?(klass)
1559
+ unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1286
1560
  raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1287
1561
  end
1288
1562
 
1289
- spawn.excluding!(records)
1563
+ spawn.excluding!(records + relations.flat_map(&:ids))
1290
1564
  end
1291
1565
  alias :without :excluding
1292
1566
 
@@ -1298,7 +1572,7 @@ module ActiveRecord
1298
1572
 
1299
1573
  # Returns the Arel object associated with the relation.
1300
1574
  def arel(aliases = nil) # :nodoc:
1301
- @arel ||= build_arel(aliases)
1575
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1302
1576
  end
1303
1577
 
1304
1578
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1319,13 +1593,29 @@ module ActiveRecord
1319
1593
  def build_where_clause(opts, rest = []) # :nodoc:
1320
1594
  opts = sanitize_forbidden_attributes(opts)
1321
1595
 
1596
+ if opts.is_a?(Array)
1597
+ opts, *rest = opts
1598
+ end
1599
+
1322
1600
  case opts
1323
- when String, Array
1324
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1601
+ when String
1602
+ if rest.empty?
1603
+ parts = [Arel.sql(opts)]
1604
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1605
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1606
+ elsif opts.include?("?")
1607
+ parts = [build_bound_sql_literal(opts, rest)]
1608
+ else
1609
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1610
+ end
1325
1611
  when Hash
1326
1612
  opts = opts.transform_keys do |key|
1327
- key = key.to_s
1328
- klass.attribute_aliases[key] || key
1613
+ if key.is_a?(Array)
1614
+ key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1615
+ else
1616
+ key = key.to_s
1617
+ klass.attribute_aliases[key] || key
1618
+ end
1329
1619
  end
1330
1620
  references = PredicateBuilder.references(opts)
1331
1621
  self.references_values |= references unless references.empty?
@@ -1343,7 +1633,76 @@ module ActiveRecord
1343
1633
  end
1344
1634
  alias :build_having_clause :build_where_clause
1345
1635
 
1636
+ def async!
1637
+ @async = true
1638
+ self
1639
+ end
1640
+
1641
+ protected
1642
+ def arel_columns(columns)
1643
+ columns.flat_map do |field|
1644
+ case field
1645
+ when Symbol
1646
+ arel_column(field.to_s) do |attr_name|
1647
+ adapter_class.quote_table_name(attr_name)
1648
+ end
1649
+ when String
1650
+ arel_column(field, &:itself)
1651
+ when Proc
1652
+ field.call
1653
+ when Hash
1654
+ arel_columns_from_hash(field)
1655
+ else
1656
+ field
1657
+ end
1658
+ end
1659
+ end
1660
+
1346
1661
  private
1662
+ def async
1663
+ spawn.async!
1664
+ end
1665
+
1666
+ def build_named_bound_sql_literal(statement, values)
1667
+ bound_values = values.transform_values do |value|
1668
+ if ActiveRecord::Relation === value
1669
+ Arel.sql(value.to_sql)
1670
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1671
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1672
+ values.empty? ? nil : values
1673
+ else
1674
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1675
+ value
1676
+ end
1677
+ end
1678
+
1679
+ begin
1680
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1681
+ rescue Arel::BindError => error
1682
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1683
+ end
1684
+ end
1685
+
1686
+ def build_bound_sql_literal(statement, values)
1687
+ bound_values = values.map do |value|
1688
+ if ActiveRecord::Relation === value
1689
+ Arel.sql(value.to_sql)
1690
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1691
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1692
+ values.empty? ? nil : values
1693
+ else
1694
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1695
+ value
1696
+ end
1697
+ end
1698
+
1699
+ begin
1700
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1701
+ rescue Arel::BindError => error
1702
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1703
+ end
1704
+ end
1705
+
1347
1706
  def lookup_table_klass_from_join_dependencies(table_name)
1348
1707
  each_join_dependencies do |join|
1349
1708
  return join.base_klass if table_name == join.table_name
@@ -1358,22 +1717,21 @@ module ActiveRecord
1358
1717
  end
1359
1718
 
1360
1719
  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?
1720
+ joins = joins_values | left_outer_joins_values
1721
+ joins |= eager_load_values unless eager_load_values.empty?
1722
+ joins |= includes_values unless includes_values.empty?
1364
1723
 
1365
1724
  join_dependencies = []
1366
1725
  join_dependencies.unshift construct_join_dependency(
1367
- select_association_list(associations, join_dependencies), nil
1726
+ select_named_joins(joins, join_dependencies), nil
1368
1727
  )
1369
1728
  end
1370
1729
 
1371
- def assert_mutability!
1372
- raise ImmutableRelation if @loaded
1373
- raise ImmutableRelation if defined?(@arel) && @arel
1730
+ def assert_modifiable!
1731
+ raise UnmodifiableRelation if @loaded || @arel
1374
1732
  end
1375
1733
 
1376
- def build_arel(aliases = nil)
1734
+ def build_arel(connection, aliases = nil)
1377
1735
  arel = Arel::SelectManager.new(table)
1378
1736
 
1379
1737
  build_joins(arel.join_sources, aliases)
@@ -1385,6 +1743,7 @@ module ActiveRecord
1385
1743
  arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1386
1744
 
1387
1745
  build_order(arel)
1746
+ build_with(arel)
1388
1747
  build_select(arel)
1389
1748
 
1390
1749
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
@@ -1420,6 +1779,18 @@ module ActiveRecord
1420
1779
  end
1421
1780
  end
1422
1781
 
1782
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1783
+ cte_joins, associations = join_names.partition do |join_name|
1784
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1785
+ end
1786
+
1787
+ cte_joins.each do |cte_name|
1788
+ block&.call(CTEJoin.new(cte_name))
1789
+ end
1790
+
1791
+ select_association_list(associations, stashed_joins, &block)
1792
+ end
1793
+
1423
1794
  def select_association_list(associations, stashed_joins = nil)
1424
1795
  result = []
1425
1796
  associations.each do |association|
@@ -1435,20 +1806,21 @@ module ActiveRecord
1435
1806
  result
1436
1807
  end
1437
1808
 
1438
- class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1439
- end
1440
-
1441
1809
  def build_join_buckets
1442
1810
  buckets = Hash.new { |h, k| h[k] = [] }
1443
1811
 
1444
1812
  unless left_outer_joins_values.empty?
1445
1813
  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"
1814
+ left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
1815
+ if left_join.is_a?(CTEJoin)
1816
+ buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
1817
+ else
1818
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1819
+ end
1448
1820
  end
1449
1821
 
1450
1822
  if joins_values.empty?
1451
- buckets[:association_join] = left_joins
1823
+ buckets[:named_join] = left_joins
1452
1824
  buckets[:stashed_join] = stashed_left_joins
1453
1825
  return buckets, Arel::Nodes::OuterJoin
1454
1826
  else
@@ -1474,9 +1846,11 @@ module ActiveRecord
1474
1846
  end
1475
1847
  end
1476
1848
 
1477
- buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1849
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1478
1850
  if join.is_a?(Arel::Nodes::Join)
1479
1851
  buckets[:join_node] << join
1852
+ elsif join.is_a?(CTEJoin)
1853
+ buckets[:join_node] << build_with_join_node(join.name)
1480
1854
  else
1481
1855
  raise "unknown class: %s" % join.class.name
1482
1856
  end
@@ -1493,16 +1867,16 @@ module ActiveRecord
1493
1867
 
1494
1868
  buckets, join_type = build_join_buckets
1495
1869
 
1496
- association_joins = buckets[:association_join]
1497
- stashed_joins = buckets[:stashed_join]
1498
- leading_joins = buckets[:leading_join]
1499
- join_nodes = buckets[:join_node]
1870
+ named_joins = buckets[:named_join]
1871
+ stashed_joins = buckets[:stashed_join]
1872
+ leading_joins = buckets[:leading_join]
1873
+ join_nodes = buckets[:join_node]
1500
1874
 
1501
1875
  join_sources.concat(leading_joins) unless leading_joins.empty?
1502
1876
 
1503
- unless association_joins.empty? && stashed_joins.empty?
1877
+ unless named_joins.empty? && stashed_joins.empty?
1504
1878
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1505
- join_dependency = construct_join_dependency(association_joins, join_type)
1879
+ join_dependency = construct_join_dependency(named_joins, join_type)
1506
1880
  join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1507
1881
  end
1508
1882
 
@@ -1520,31 +1894,63 @@ module ActiveRecord
1520
1894
  end
1521
1895
  end
1522
1896
 
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
1897
+ def build_with(arel)
1898
+ return if with_values.empty?
1899
+
1900
+ with_statements = with_values.map do |with_value|
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
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)
1536
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.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
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,48 @@ 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
+
2211
+ def process_with_args(args)
2212
+ args.flat_map do |arg|
2213
+ raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
2214
+ arg.map { |k, v| { k => v } }
2215
+ end
2216
+ end
2217
+
1742
2218
  STRUCTURAL_VALUE_METHODS = (
1743
2219
  Relation::VALUE_METHODS -
1744
2220
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]