activerecord 5.2.6 → 6.1.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1038 -571
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +13 -12
  7. data/lib/active_record/aggregations.rb +9 -8
  8. data/lib/active_record/association_relation.rb +30 -10
  9. data/lib/active_record/associations.rb +137 -25
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +95 -42
  12. data/lib/active_record/associations/association_scope.rb +23 -21
  13. data/lib/active_record/associations/belongs_to_association.rb +54 -46
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
  15. data/lib/active_record/associations/builder/association.rb +45 -22
  16. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  17. data/lib/active_record/associations/builder/collection_association.rb +8 -17
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  19. data/lib/active_record/associations/builder/has_many.rb +8 -2
  20. data/lib/active_record/associations/builder/has_one.rb +33 -2
  21. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  22. data/lib/active_record/associations/collection_association.rb +31 -29
  23. data/lib/active_record/associations/collection_proxy.rb +25 -21
  24. data/lib/active_record/associations/foreign_association.rb +20 -0
  25. data/lib/active_record/associations/has_many_association.rb +26 -13
  26. data/lib/active_record/associations/has_many_through_association.rb +24 -18
  27. data/lib/active_record/associations/has_one_association.rb +43 -31
  28. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  31. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  32. data/lib/active_record/associations/preloader.rb +47 -34
  33. data/lib/active_record/associations/preloader/association.rb +71 -43
  34. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  35. data/lib/active_record/associations/singular_association.rb +3 -17
  36. data/lib/active_record/associations/through_association.rb +1 -1
  37. data/lib/active_record/attribute_assignment.rb +17 -19
  38. data/lib/active_record/attribute_methods.rb +81 -143
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  40. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  41. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  42. data/lib/active_record/attribute_methods/query.rb +4 -8
  43. data/lib/active_record/attribute_methods/read.rb +14 -56
  44. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  46. data/lib/active_record/attribute_methods/write.rb +18 -34
  47. data/lib/active_record/attributes.rb +46 -9
  48. data/lib/active_record/autosave_association.rb +57 -42
  49. data/lib/active_record/base.rb +4 -17
  50. data/lib/active_record/callbacks.rb +158 -43
  51. data/lib/active_record/coders/yaml_column.rb +1 -2
  52. data/lib/active_record/connection_adapters.rb +50 -0
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  58. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  60. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +207 -90
  61. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
  63. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  64. data/lib/active_record/connection_adapters/abstract_adapter.rb +228 -98
  65. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  66. data/lib/active_record/connection_adapters/column.rb +30 -12
  67. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  68. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  69. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  71. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  77. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  79. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  80. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  83. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  99. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  101. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  103. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  104. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  105. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  106. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  107. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  108. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
  109. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  110. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  111. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  112. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  113. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  114. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  116. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  117. data/lib/active_record/connection_handling.rb +293 -33
  118. data/lib/active_record/core.rb +323 -97
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations.rb +272 -0
  121. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  122. data/lib/active_record/database_configurations/database_config.rb +80 -0
  123. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  124. data/lib/active_record/database_configurations/url_config.rb +53 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +111 -37
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +200 -481
  138. data/lib/active_record/gem_version.rb +4 -4
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +37 -23
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +35 -35
  147. data/lib/active_record/middleware/database_selector.rb +77 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  150. data/lib/active_record/migration.rb +206 -157
  151. data/lib/active_record/migration/command_recorder.rb +96 -44
  152. data/lib/active_record/migration/compatibility.rb +142 -64
  153. data/lib/active_record/migration/join_table.rb +0 -1
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +115 -58
  162. data/lib/active_record/railties/console_sandbox.rb +2 -4
  163. data/lib/active_record/railties/controller_runtime.rb +30 -35
  164. data/lib/active_record/railties/databases.rake +408 -78
  165. data/lib/active_record/readonly_attributes.rb +4 -0
  166. data/lib/active_record/reflection.rb +109 -93
  167. data/lib/active_record/relation.rb +374 -104
  168. data/lib/active_record/relation/batches.rb +44 -35
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  170. data/lib/active_record/relation/calculations.rb +153 -90
  171. data/lib/active_record/relation/delegation.rb +35 -50
  172. data/lib/active_record/relation/finder_methods.rb +64 -39
  173. data/lib/active_record/relation/from_clause.rb +5 -1
  174. data/lib/active_record/relation/merger.rb +32 -40
  175. data/lib/active_record/relation/predicate_builder.rb +62 -45
  176. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  177. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  182. data/lib/active_record/relation/query_attribute.rb +13 -8
  183. data/lib/active_record/relation/query_methods.rb +475 -186
  184. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  185. data/lib/active_record/relation/spawn_methods.rb +9 -9
  186. data/lib/active_record/relation/where_clause.rb +111 -61
  187. data/lib/active_record/result.rb +64 -38
  188. data/lib/active_record/runtime_registry.rb +2 -2
  189. data/lib/active_record/sanitization.rb +22 -41
  190. data/lib/active_record/schema.rb +2 -11
  191. data/lib/active_record/schema_dumper.rb +54 -9
  192. data/lib/active_record/schema_migration.rb +7 -9
  193. data/lib/active_record/scoping.rb +8 -9
  194. data/lib/active_record/scoping/default.rb +4 -6
  195. data/lib/active_record/scoping/named.rb +17 -24
  196. data/lib/active_record/secure_token.rb +16 -8
  197. data/lib/active_record/serialization.rb +5 -3
  198. data/lib/active_record/signed_id.rb +116 -0
  199. data/lib/active_record/statement_cache.rb +49 -6
  200. data/lib/active_record/store.rb +88 -9
  201. data/lib/active_record/suppressor.rb +2 -2
  202. data/lib/active_record/table_metadata.rb +42 -43
  203. data/lib/active_record/tasks/database_tasks.rb +277 -81
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  207. data/lib/active_record/test_databases.rb +24 -0
  208. data/lib/active_record/test_fixtures.rb +246 -0
  209. data/lib/active_record/timestamp.rb +43 -32
  210. data/lib/active_record/touch_later.rb +23 -22
  211. data/lib/active_record/transactions.rb +62 -118
  212. data/lib/active_record/translation.rb +1 -1
  213. data/lib/active_record/type.rb +10 -5
  214. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  215. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  216. data/lib/active_record/type/serialized.rb +6 -3
  217. data/lib/active_record/type/time.rb +10 -0
  218. data/lib/active_record/type/type_map.rb +0 -1
  219. data/lib/active_record/type/unsigned_integer.rb +0 -1
  220. data/lib/active_record/type_caster/connection.rb +15 -15
  221. data/lib/active_record/type_caster/map.rb +8 -8
  222. data/lib/active_record/validations.rb +4 -3
  223. data/lib/active_record/validations/associated.rb +1 -2
  224. data/lib/active_record/validations/numericality.rb +35 -0
  225. data/lib/active_record/validations/uniqueness.rb +38 -30
  226. data/lib/arel.rb +54 -0
  227. data/lib/arel/alias_predication.rb +9 -0
  228. data/lib/arel/attributes/attribute.rb +41 -0
  229. data/lib/arel/collectors/bind.rb +29 -0
  230. data/lib/arel/collectors/composite.rb +39 -0
  231. data/lib/arel/collectors/plain_string.rb +20 -0
  232. data/lib/arel/collectors/sql_string.rb +27 -0
  233. data/lib/arel/collectors/substitute_binds.rb +35 -0
  234. data/lib/arel/crud.rb +42 -0
  235. data/lib/arel/delete_manager.rb +18 -0
  236. data/lib/arel/errors.rb +9 -0
  237. data/lib/arel/expressions.rb +29 -0
  238. data/lib/arel/factory_methods.rb +49 -0
  239. data/lib/arel/insert_manager.rb +49 -0
  240. data/lib/arel/math.rb +45 -0
  241. data/lib/arel/nodes.rb +70 -0
  242. data/lib/arel/nodes/and.rb +32 -0
  243. data/lib/arel/nodes/ascending.rb +23 -0
  244. data/lib/arel/nodes/binary.rb +126 -0
  245. data/lib/arel/nodes/bind_param.rb +44 -0
  246. data/lib/arel/nodes/case.rb +55 -0
  247. data/lib/arel/nodes/casted.rb +62 -0
  248. data/lib/arel/nodes/comment.rb +29 -0
  249. data/lib/arel/nodes/count.rb +12 -0
  250. data/lib/arel/nodes/delete_statement.rb +45 -0
  251. data/lib/arel/nodes/descending.rb +23 -0
  252. data/lib/arel/nodes/equality.rb +15 -0
  253. data/lib/arel/nodes/extract.rb +24 -0
  254. data/lib/arel/nodes/false.rb +16 -0
  255. data/lib/arel/nodes/full_outer_join.rb +8 -0
  256. data/lib/arel/nodes/function.rb +44 -0
  257. data/lib/arel/nodes/grouping.rb +11 -0
  258. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  259. data/lib/arel/nodes/in.rb +15 -0
  260. data/lib/arel/nodes/infix_operation.rb +92 -0
  261. data/lib/arel/nodes/inner_join.rb +8 -0
  262. data/lib/arel/nodes/insert_statement.rb +37 -0
  263. data/lib/arel/nodes/join_source.rb +20 -0
  264. data/lib/arel/nodes/matches.rb +18 -0
  265. data/lib/arel/nodes/named_function.rb +23 -0
  266. data/lib/arel/nodes/node.rb +51 -0
  267. data/lib/arel/nodes/node_expression.rb +13 -0
  268. data/lib/arel/nodes/ordering.rb +27 -0
  269. data/lib/arel/nodes/outer_join.rb +8 -0
  270. data/lib/arel/nodes/over.rb +15 -0
  271. data/lib/arel/nodes/regexp.rb +16 -0
  272. data/lib/arel/nodes/right_outer_join.rb +8 -0
  273. data/lib/arel/nodes/select_core.rb +67 -0
  274. data/lib/arel/nodes/select_statement.rb +41 -0
  275. data/lib/arel/nodes/sql_literal.rb +19 -0
  276. data/lib/arel/nodes/string_join.rb +11 -0
  277. data/lib/arel/nodes/table_alias.rb +31 -0
  278. data/lib/arel/nodes/terminal.rb +16 -0
  279. data/lib/arel/nodes/true.rb +16 -0
  280. data/lib/arel/nodes/unary.rb +44 -0
  281. data/lib/arel/nodes/unary_operation.rb +20 -0
  282. data/lib/arel/nodes/unqualified_column.rb +22 -0
  283. data/lib/arel/nodes/update_statement.rb +41 -0
  284. data/lib/arel/nodes/values_list.rb +9 -0
  285. data/lib/arel/nodes/window.rb +126 -0
  286. data/lib/arel/nodes/with.rb +11 -0
  287. data/lib/arel/order_predications.rb +13 -0
  288. data/lib/arel/predications.rb +250 -0
  289. data/lib/arel/select_manager.rb +270 -0
  290. data/lib/arel/table.rb +118 -0
  291. data/lib/arel/tree_manager.rb +72 -0
  292. data/lib/arel/update_manager.rb +34 -0
  293. data/lib/arel/visitors.rb +13 -0
  294. data/lib/arel/visitors/dot.rb +308 -0
  295. data/lib/arel/visitors/mysql.rb +93 -0
  296. data/lib/arel/visitors/postgresql.rb +120 -0
  297. data/lib/arel/visitors/sqlite.rb +38 -0
  298. data/lib/arel/visitors/to_sql.rb +899 -0
  299. data/lib/arel/visitors/visitor.rb +45 -0
  300. data/lib/arel/window_predications.rb +9 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration.rb +19 -2
  303. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  304. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  305. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +119 -34
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -2,34 +2,41 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class PredicateBuilder # :nodoc:
5
- delegate :resolve_column_aliases, to: :table
5
+ require "active_record/relation/predicate_builder/array_handler"
6
+ require "active_record/relation/predicate_builder/basic_object_handler"
7
+ require "active_record/relation/predicate_builder/range_handler"
8
+ require "active_record/relation/predicate_builder/relation_handler"
9
+ require "active_record/relation/predicate_builder/association_query_value"
10
+ require "active_record/relation/predicate_builder/polymorphic_array_value"
11
+
12
+ # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")).
13
+ # TODO: Remove the constant alias once Rails 6.1 has released.
14
+ BaseHandler = BasicObjectHandler
6
15
 
7
16
  def initialize(table)
8
17
  @table = table
9
18
  @handlers = []
10
19
 
11
20
  register_handler(BasicObject, BasicObjectHandler.new(self))
12
- register_handler(Base, BaseHandler.new(self))
13
21
  register_handler(Range, RangeHandler.new(self))
14
22
  register_handler(Relation, RelationHandler.new)
15
23
  register_handler(Array, ArrayHandler.new(self))
16
24
  register_handler(Set, ArrayHandler.new(self))
17
25
  end
18
26
 
19
- def build_from_hash(attributes)
27
+ def build_from_hash(attributes, &block)
20
28
  attributes = convert_dot_notation_to_hash(attributes)
21
- expand_from_hash(attributes)
29
+ expand_from_hash(attributes, &block)
22
30
  end
23
31
 
24
32
  def self.references(attributes)
25
- attributes.map do |key, value|
33
+ attributes.each_with_object([]) do |(key, value), result|
26
34
  if value.is_a?(Hash)
27
- key
28
- else
29
- key = key.to_s
30
- key.split(".".freeze).first if key.include?(".".freeze)
35
+ result << Arel.sql(key)
36
+ elsif key.include?(".")
37
+ result << Arel.sql(key.split(".").first)
31
38
  end
32
- end.compact
39
+ end
33
40
  end
34
41
 
35
42
  # Define how a class is converted to Arel nodes when passed to +where+.
@@ -47,30 +54,37 @@ module ActiveRecord
47
54
  @handlers.unshift([klass, handler])
48
55
  end
49
56
 
50
- def build(attribute, value)
51
- if table.type(attribute.name).force_equality?(value)
57
+ def [](attr_name, value, operator = nil)
58
+ build(table.arel_table[attr_name], value, operator)
59
+ end
60
+
61
+ def build(attribute, value, operator = nil)
62
+ value = value.id if value.respond_to?(:id)
63
+ if operator ||= table.type(attribute.name).force_equality?(value) && :eq
52
64
  bind = build_bind_attribute(attribute.name, value)
53
- attribute.eq(bind)
65
+ attribute.public_send(operator, bind)
54
66
  else
55
67
  handler_for(value).call(attribute, value)
56
68
  end
57
69
  end
58
70
 
59
71
  def build_bind_attribute(column_name, value)
60
- attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
72
+ attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name))
61
73
  Arel::Nodes::BindParam.new(attr)
62
74
  end
63
75
 
64
- protected
65
-
66
- attr_reader :table
76
+ def resolve_arel_attribute(table_name, column_name, &block)
77
+ table.associated_table(table_name, &block).arel_table[column_name]
78
+ end
67
79
 
68
- def expand_from_hash(attributes)
80
+ protected
81
+ def expand_from_hash(attributes, &block)
69
82
  return ["1=0"] if attributes.empty?
70
83
 
71
84
  attributes.flat_map do |key, value|
72
85
  if value.is_a?(Hash) && !table.has_column?(key)
73
- associated_predicate_builder(key).expand_from_hash(value)
86
+ table.associated_table(key, &block)
87
+ .predicate_builder.expand_from_hash(value.stringify_keys)
74
88
  elsif table.associated_with?(key)
75
89
  # Find the foreign key when using queries such as:
76
90
  # Post.where(author: author)
@@ -79,18 +93,22 @@ module ActiveRecord
79
93
  # PriceEstimate.where(estimate_of: treasure)
80
94
  associated_table = table.associated_table(key)
81
95
  if associated_table.polymorphic_association?
82
- case value.is_a?(Array) ? value.first : value
83
- when Base, Relation
84
- value = [value] unless value.is_a?(Array)
85
- klass = PolymorphicArrayValue
86
- end
96
+ value = [value] unless value.is_a?(Array)
97
+ klass = PolymorphicArrayValue
98
+ elsif associated_table.through_association?
99
+ next associated_table.predicate_builder.expand_from_hash(
100
+ associated_table.primary_key => value
101
+ )
87
102
  end
88
103
 
89
104
  klass ||= AssociationQueryValue
90
- queries = klass.new(associated_table, value).queries.map do |query|
91
- expand_from_hash(query).reduce(&:and)
105
+ queries = klass.new(associated_table, value).queries.map! do |query|
106
+ # If the query produced is identical to attributes don't go any deeper.
107
+ # Prevents stack level too deep errors when association and foreign_key are identical.
108
+ query == attributes ? self[key, value] : expand_from_hash(query)
92
109
  end
93
- queries.reduce(&:or)
110
+
111
+ grouping_queries(queries)
94
112
  elsif table.aggregated_with?(key)
95
113
  mapping = table.reflect_on_aggregation(key).mapping
96
114
  values = value.nil? ? [nil] : Array.wrap(value)
@@ -99,34 +117,42 @@ module ActiveRecord
99
117
  values = values.map do |object|
100
118
  object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
101
119
  end
102
- build(table.arel_attribute(column_name), values)
120
+ self[column_name, values]
103
121
  else
104
122
  queries = values.map do |object|
105
123
  mapping.map do |field_attr, aggregate_attr|
106
- build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
107
- end.reduce(&:and)
124
+ self[field_attr, object.try!(aggregate_attr)]
125
+ end
108
126
  end
109
- queries.reduce(&:or)
127
+
128
+ grouping_queries(queries)
110
129
  end
111
130
  else
112
- build(table.arel_attribute(key), value)
131
+ self[key, value]
113
132
  end
114
133
  end
115
134
  end
116
135
 
117
136
  private
137
+ attr_reader :table
118
138
 
119
- def associated_predicate_builder(association_name)
120
- self.class.new(table.associated_table(association_name))
139
+ def grouping_queries(queries)
140
+ if queries.one?
141
+ queries.first
142
+ else
143
+ queries.map! { |query| query.reduce(&:and) }
144
+ queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
145
+ Arel::Nodes::Grouping.new(queries)
146
+ end
121
147
  end
122
148
 
123
149
  def convert_dot_notation_to_hash(attributes)
124
150
  dot_notation = attributes.select do |k, v|
125
- k.include?(".".freeze) && !v.is_a?(Hash)
151
+ k.include?(".") && !v.is_a?(Hash)
126
152
  end
127
153
 
128
154
  dot_notation.each_key do |key|
129
- table_name, column_name = key.split(".".freeze)
155
+ table_name, column_name = key.split(".")
130
156
  value = attributes.delete(key)
131
157
  attributes[table_name] ||= {}
132
158
 
@@ -141,12 +167,3 @@ module ActiveRecord
141
167
  end
142
168
  end
143
169
  end
144
-
145
- require "active_record/relation/predicate_builder/array_handler"
146
- require "active_record/relation/predicate_builder/base_handler"
147
- require "active_record/relation/predicate_builder/basic_object_handler"
148
- require "active_record/relation/predicate_builder/range_handler"
149
- require "active_record/relation/predicate_builder/relation_handler"
150
-
151
- require "active_record/relation/predicate_builder/association_query_value"
152
- require "active_record/relation/predicate_builder/polymorphic_array_value"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/array/extract"
4
+
3
5
  module ActiveRecord
4
6
  class PredicateBuilder
5
7
  class ArrayHandler # :nodoc:
@@ -11,31 +13,29 @@ module ActiveRecord
11
13
  return attribute.in([]) if value.empty?
12
14
 
13
15
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
14
- nils, values = values.partition(&:nil?)
15
- ranges, values = values.partition { |v| v.is_a?(Range) }
16
+ nils = values.extract!(&:nil?)
17
+ ranges = values.extract! { |v| v.is_a?(Range) }
16
18
 
17
19
  values_predicate =
18
20
  case values.length
19
21
  when 0 then NullPredicate
20
22
  when 1 then predicate_builder.build(attribute, values.first)
21
- else
22
- values.map! do |v|
23
- predicate_builder.build_bind_attribute(attribute.name, v)
24
- end
25
- values.empty? ? NullPredicate : attribute.in(values)
23
+ else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
26
24
  end
27
25
 
28
26
  unless nils.empty?
29
- values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
27
+ values_predicate = values_predicate.or(attribute.eq(nil))
30
28
  end
31
29
 
32
- array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
33
- array_predicates.unshift(values_predicate)
34
- array_predicates.inject(&:or)
30
+ if ranges.empty?
31
+ values_predicate
32
+ else
33
+ array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
34
+ array_predicates.inject(values_predicate, &:or)
35
+ end
35
36
  end
36
37
 
37
- protected
38
-
38
+ private
39
39
  attr_reader :predicate_builder
40
40
 
41
41
  module NullPredicate # :nodoc:
@@ -9,15 +9,12 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [associated_table.association_join_foreign_key.to_s => ids]
12
+ [ associated_table.join_foreign_key => ids ]
13
13
  end
14
14
 
15
- # TODO Change this to private once we've dropped Ruby 2.2 support.
16
- # Workaround for Ruby 2.2 "private attribute?" warning.
17
- protected
15
+ private
18
16
  attr_reader :associated_table, :value
19
17
 
20
- private
21
18
  def ids
22
19
  case value
23
20
  when Relation
@@ -30,13 +27,12 @@ module ActiveRecord
30
27
  end
31
28
 
32
29
  def primary_key
33
- associated_table.association_join_primary_key
30
+ associated_table.join_primary_key
34
31
  end
35
32
 
36
33
  def convert_to_id(value)
37
- case value
38
- when Base
39
- value._read_attribute(primary_key)
34
+ if value.respond_to?(primary_key)
35
+ value.public_send(primary_key)
40
36
  else
41
37
  value
42
38
  end
@@ -12,8 +12,7 @@ module ActiveRecord
12
12
  attribute.eq(bind)
13
13
  end
14
14
 
15
- protected
16
-
15
+ private
17
16
  attr_reader :predicate_builder
18
17
  end
19
18
  end
@@ -9,29 +9,28 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
+ return [ associated_table.join_foreign_key => values ] if values.empty?
13
+
12
14
  type_to_ids_mapping.map do |type, ids|
13
- {
14
- associated_table.association_foreign_type.to_s => type,
15
- associated_table.association_foreign_key.to_s => ids
16
- }
15
+ query = {}
16
+ query[associated_table.join_foreign_type] = type if type
17
+ query[associated_table.join_foreign_key] = ids
18
+ query
17
19
  end
18
20
  end
19
21
 
20
- # TODO Change this to private once we've dropped Ruby 2.2 support.
21
- # Workaround for Ruby 2.2 "private attribute?" warning.
22
- protected
22
+ private
23
23
  attr_reader :associated_table, :values
24
24
 
25
- private
26
25
  def type_to_ids_mapping
27
26
  default_hash = Hash.new { |hsh, key| hsh[key] = [] }
28
27
  values.each_with_object(default_hash) do |value, hash|
29
- hash[klass(value).polymorphic_name] << convert_to_id(value)
28
+ hash[klass(value)&.polymorphic_name] << convert_to_id(value)
30
29
  end
31
30
  end
32
31
 
33
32
  def primary_key(value)
34
- associated_table.association_join_primary_key(klass(value))
33
+ associated_table.join_primary_key(klass(value))
35
34
  end
36
35
 
37
36
  def klass(value)
@@ -49,6 +48,8 @@ module ActiveRecord
49
48
  value._read_attribute(primary_key(value))
50
49
  when Relation
51
50
  value.select(primary_key(value))
51
+ else
52
+ value
52
53
  end
53
54
  end
54
55
  end
@@ -3,11 +3,7 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class RangeHandler # :nodoc:
6
- class RangeWithBinds < Struct.new(:begin, :end)
7
- def exclude_end?
8
- false
9
- end
10
- end
6
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
11
7
 
12
8
  def initialize(predicate_builder)
13
9
  @predicate_builder = predicate_builder
@@ -16,26 +12,10 @@ module ActiveRecord
16
12
  def call(attribute, value)
17
13
  begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
18
14
  end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
19
-
20
- if begin_bind.value.infinity?
21
- if end_bind.value.infinity?
22
- attribute.not_in([])
23
- elsif value.exclude_end?
24
- attribute.lt(end_bind)
25
- else
26
- attribute.lteq(end_bind)
27
- end
28
- elsif end_bind.value.infinity?
29
- attribute.gteq(begin_bind)
30
- elsif value.exclude_end?
31
- attribute.gteq(begin_bind).and(attribute.lt(end_bind))
32
- else
33
- attribute.between(RangeWithBinds.new(begin_bind, end_bind))
34
- end
15
+ attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
35
16
  end
36
17
 
37
- protected
38
-
18
+ private
39
19
  attr_reader :predicate_builder
40
20
  end
41
21
  end
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- value = value.select(value.arel_attribute(value.klass.primary_key))
12
+ value = value.select(value.table[value.klass.primary_key])
13
13
  end
14
14
 
15
15
  attribute.in(value.arel)
@@ -22,22 +22,27 @@ module ActiveRecord
22
22
  value_before_type_cast.nil? ||
23
23
  type.respond_to?(:subtype, true) && value_for_database.nil?
24
24
  end
25
+ rescue ::RangeError
25
26
  end
26
27
 
27
- def boundable?
28
- return @_boundable if defined?(@_boundable)
29
- value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
30
- @_boundable = true
28
+ def infinite?
29
+ infinity?(value_before_type_cast) || infinity?(value_for_database)
31
30
  rescue ::RangeError
32
- @_boundable = false
33
31
  end
34
32
 
35
- def infinity?
36
- _infinity?(value_before_type_cast) || boundable? && _infinity?(value_for_database)
33
+ def unboundable?
34
+ if defined?(@_unboundable)
35
+ @_unboundable
36
+ else
37
+ value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
38
+ @_unboundable = nil
39
+ end
40
+ rescue ::RangeError
41
+ @_unboundable = type.cast(value_before_type_cast) <=> 0
37
42
  end
38
43
 
39
44
  private
40
- def _infinity?(value)
45
+ def infinity?(value)
41
46
  value.respond_to?(:infinite?) && value.infinite?
42
47
  end
43
48
  end
@@ -3,8 +3,8 @@
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_record/relation/where_clause_factory"
7
6
  require "active_model/forbidden_attributes_protection"
7
+ require "active_support/core_ext/array/wrap"
8
8
 
9
9
  module ActiveRecord
10
10
  module QueryMethods
@@ -15,8 +15,6 @@ module ActiveRecord
15
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
16
  # In this case, #where must be chained with #not to return a new relation.
17
17
  class WhereChain
18
- include ActiveModel::ForbiddenAttributesProtection
19
-
20
18
  def initialize(scope)
21
19
  @scope = scope
22
20
  end
@@ -43,14 +41,41 @@ module ActiveRecord
43
41
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
44
42
  #
45
43
  # User.where.not(name: "Jon", role: "admin")
46
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
44
+ # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
47
45
  def not(opts, *rest)
48
- opts = sanitize_forbidden_attributes(opts)
49
-
50
- where_clause = @scope.send(:where_clause_factory).build(opts, rest)
46
+ where_clause = @scope.send(:build_where_clause, opts, rest)
51
47
 
52
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
53
48
  @scope.where_clause += where_clause.invert
49
+
50
+ @scope
51
+ end
52
+
53
+ # Returns a new relation with left outer joins and where clause to identify
54
+ # missing relations.
55
+ #
56
+ # For example, posts that are missing a related author:
57
+ #
58
+ # Post.where.missing(:author)
59
+ # # SELECT "posts".* FROM "posts"
60
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
61
+ # # WHERE "authors"."id" IS NULL
62
+ #
63
+ # Additionally, multiple relations can be combined. This will return posts
64
+ # that are missing both an author and any comments:
65
+ #
66
+ # Post.where.missing(:author, :comments)
67
+ # # SELECT "posts".* FROM "posts"
68
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
+ # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
71
+ def missing(*args)
72
+ args.each do |arg|
73
+ reflection = @scope.klass._reflect_on_association(arg)
74
+ opts = { reflection.table_name => { reflection.association_primary_key => nil } }
75
+ @scope.left_outer_joins!(arg)
76
+ @scope.where!(opts)
77
+ end
78
+
54
79
  @scope
55
80
  end
56
81
  end
@@ -59,20 +84,25 @@ module ActiveRecord
59
84
  FROZEN_EMPTY_HASH = {}.freeze
60
85
 
61
86
  Relation::VALUE_METHODS.each do |name|
62
- method_name = \
87
+ method_name, default =
63
88
  case name
64
- when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
65
- when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
66
- when *Relation::CLAUSE_METHODS then "#{name}_clause"
89
+ when *Relation::MULTI_VALUE_METHODS
90
+ ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
91
+ when *Relation::SINGLE_VALUE_METHODS
92
+ ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
93
+ when *Relation::CLAUSE_METHODS
94
+ ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
67
95
  end
68
- class_eval <<-CODE, __FILE__, __LINE__ + 1
69
- def #{method_name} # def includes_values
70
- get_value(#{name.inspect}) # get_value(:includes)
71
- end # end
72
96
 
73
- def #{method_name}=(value) # def includes_values=(value)
74
- set_value(#{name.inspect}, value) # set_value(:includes, value)
75
- end # end
97
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
98
+ def #{method_name} # def includes_values
99
+ @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
100
+ end # end
101
+
102
+ def #{method_name}=(value) # def includes_values=(value)
103
+ assert_mutability! # assert_mutability!
104
+ @values[:#{name}] = value # @values[:includes] = value
105
+ end # end
76
106
  CODE
77
107
  end
78
108
 
@@ -100,7 +130,7 @@ module ActiveRecord
100
130
  #
101
131
  # === conditions
102
132
  #
103
- # If you want to add conditions to your included models you'll have
133
+ # If you want to add string conditions to your included models, you'll have
104
134
  # to explicitly reference them. For example:
105
135
  #
106
136
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -111,15 +141,18 @@ module ActiveRecord
111
141
  #
112
142
  # Note that #includes works with association names while #references needs
113
143
  # the actual table name.
144
+ #
145
+ # If you pass the conditions via hash, you don't need to call #references
146
+ # explicitly, as #where references the tables for you. For example, this
147
+ # will work correctly:
148
+ #
149
+ # User.includes(:posts).where(posts: { name: 'example' })
114
150
  def includes(*args)
115
151
  check_if_method_has_arguments!(:includes, args)
116
152
  spawn.includes!(*args)
117
153
  end
118
154
 
119
155
  def includes!(*args) # :nodoc:
120
- args.reject!(&:blank?)
121
- args.flatten!
122
-
123
156
  self.includes_values |= args
124
157
  self
125
158
  end
@@ -136,7 +169,7 @@ module ActiveRecord
136
169
  end
137
170
 
138
171
  def eager_load!(*args) # :nodoc:
139
- self.eager_load_values += args
172
+ self.eager_load_values |= args
140
173
  self
141
174
  end
142
175
 
@@ -150,10 +183,23 @@ module ActiveRecord
150
183
  end
151
184
 
152
185
  def preload!(*args) # :nodoc:
153
- self.preload_values += args
186
+ self.preload_values |= args
154
187
  self
155
188
  end
156
189
 
190
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
191
+ # then the individual association records are collected from the relation. Like so:
192
+ #
193
+ # account.memberships.extract_associated(:user)
194
+ # # => Returns collection of User records
195
+ #
196
+ # This is short-hand for:
197
+ #
198
+ # account.memberships.preload(:user).collect(&:user)
199
+ def extract_associated(association)
200
+ preload(association).collect(&association)
201
+ end
202
+
157
203
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
158
204
  # and should therefore be JOINed in any query rather than loaded separately.
159
205
  # This method only works in conjunction with #includes.
@@ -170,9 +216,6 @@ module ActiveRecord
170
216
  end
171
217
 
172
218
  def references!(*table_names) # :nodoc:
173
- table_names.flatten!
174
- table_names.map!(&:to_s)
175
-
176
219
  self.references_values |= table_names
177
220
  self
178
221
  end
@@ -226,13 +269,33 @@ module ActiveRecord
226
269
  return super()
227
270
  end
228
271
 
229
- raise ArgumentError, "Call `select' with at least one field" if fields.empty?
272
+ check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
230
273
  spawn._select!(*fields)
231
274
  end
232
275
 
233
276
  def _select!(*fields) # :nodoc:
234
- fields.flatten!
235
- self.select_values += fields
277
+ self.select_values |= fields
278
+ self
279
+ end
280
+
281
+ # Allows you to change a previously set select statement.
282
+ #
283
+ # Post.select(:title, :body)
284
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
285
+ #
286
+ # Post.select(:title, :body).reselect(:created_at)
287
+ # # SELECT `posts`.`created_at` FROM `posts`
288
+ #
289
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
290
+ # Note that we're unscoping the entire select statement.
291
+ def reselect(*args)
292
+ check_if_method_has_arguments!(:reselect, args)
293
+ spawn.reselect!(*args)
294
+ end
295
+
296
+ # Same as #reselect but operates on relation in-place instead of copying.
297
+ def reselect!(*args) # :nodoc:
298
+ self.select_values = args
236
299
  self
237
300
  end
238
301
 
@@ -262,8 +325,6 @@ module ActiveRecord
262
325
  end
263
326
 
264
327
  def group!(*args) # :nodoc:
265
- args.flatten!
266
-
267
328
  self.group_values += args
268
329
  self
269
330
  end
@@ -288,15 +349,16 @@ module ActiveRecord
288
349
  # User.order('name DESC, email')
289
350
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
290
351
  def order(*args)
291
- check_if_method_has_arguments!(:order, args)
352
+ check_if_method_has_arguments!(:order, args) do
353
+ sanitize_order_arguments(args)
354
+ end
292
355
  spawn.order!(*args)
293
356
  end
294
357
 
295
358
  # Same as #order but operates on relation in-place instead of copying.
296
359
  def order!(*args) # :nodoc:
297
- preprocess_order_args(args)
298
-
299
- self.order_values += args
360
+ preprocess_order_args(args) unless args.empty?
361
+ self.order_values |= args
300
362
  self
301
363
  end
302
364
 
@@ -310,22 +372,24 @@ module ActiveRecord
310
372
  #
311
373
  # generates a query with 'ORDER BY id ASC, name ASC'.
312
374
  def reorder(*args)
313
- check_if_method_has_arguments!(:reorder, args)
375
+ check_if_method_has_arguments!(:reorder, args) do
376
+ sanitize_order_arguments(args) unless args.all?(&:blank?)
377
+ end
314
378
  spawn.reorder!(*args)
315
379
  end
316
380
 
317
381
  # Same as #reorder but operates on relation in-place instead of copying.
318
382
  def reorder!(*args) # :nodoc:
319
- preprocess_order_args(args)
320
-
383
+ preprocess_order_args(args) unless args.all?(&:blank?)
384
+ args.uniq!
321
385
  self.reordering_value = true
322
386
  self.order_values = args
323
387
  self
324
388
  end
325
389
 
326
390
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
327
- :limit, :offset, :joins, :left_outer_joins,
328
- :includes, :from, :readonly, :having])
391
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
392
+ :includes, :from, :readonly, :having, :optimizer_hints])
329
393
 
330
394
  # Removes an unwanted relation that is already defined on a chain of relations.
331
395
  # This is useful when passing around chains of relations and would like to
@@ -366,7 +430,6 @@ module ActiveRecord
366
430
  end
367
431
 
368
432
  def unscope!(*args) # :nodoc:
369
- args.flatten!
370
433
  self.unscope_values += args
371
434
 
372
435
  args.each do |scope|
@@ -376,14 +439,15 @@ module ActiveRecord
376
439
  if !VALID_UNSCOPING_VALUES.include?(scope)
377
440
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
378
441
  end
379
- set_value(scope, DEFAULT_VALUES[scope])
442
+ assert_mutability!
443
+ @values.delete(scope)
380
444
  when Hash
381
445
  scope.each do |key, target_value|
382
446
  if key != :where
383
447
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
384
448
  end
385
449
 
386
- target_values = Array(target_value).map(&:to_s)
450
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
387
451
  self.where_clause = where_clause.except(*target_values)
388
452
  end
389
453
  else
@@ -416,8 +480,7 @@ module ActiveRecord
416
480
  # # SELECT "users".*
417
481
  # # FROM "users"
418
482
  # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
419
- # # INNER JOIN "comments" "comments_posts"
420
- # # ON "comments_posts"."post_id" = "posts"."id"
483
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
421
484
  #
422
485
  # You can use strings in order to customize your joins:
423
486
  #
@@ -429,9 +492,7 @@ module ActiveRecord
429
492
  end
430
493
 
431
494
  def joins!(*args) # :nodoc:
432
- args.compact!
433
- args.flatten!
434
- self.joins_values += args
495
+ self.joins_values |= args
435
496
  self
436
497
  end
437
498
 
@@ -447,9 +508,7 @@ module ActiveRecord
447
508
  alias :left_joins :left_outer_joins
448
509
 
449
510
  def left_outer_joins!(*args) # :nodoc:
450
- args.compact!
451
- args.flatten!
452
- self.left_outer_joins_values += args
511
+ self.left_outer_joins_values |= args
453
512
  self
454
513
  end
455
514
 
@@ -572,20 +631,18 @@ module ActiveRecord
572
631
  #
573
632
  # If the condition is any blank-ish object, then #where is a no-op and returns
574
633
  # the current relation.
575
- def where(opts = :chain, *rest)
576
- if :chain == opts
634
+ def where(*args)
635
+ if args.empty?
577
636
  WhereChain.new(spawn)
578
- elsif opts.blank?
637
+ elsif args.length == 1 && args.first.blank?
579
638
  self
580
639
  else
581
- spawn.where!(opts, *rest)
640
+ spawn.where!(*args)
582
641
  end
583
642
  end
584
643
 
585
644
  def where!(opts, *rest) # :nodoc:
586
- opts = sanitize_forbidden_attributes(opts)
587
- references!(PredicateBuilder.references(opts)) if Hash === opts
588
- self.where_clause += where_clause_factory.build(opts, rest)
645
+ self.where_clause += build_where_clause(opts, rest)
589
646
  self
590
647
  end
591
648
 
@@ -603,7 +660,44 @@ module ActiveRecord
603
660
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
604
661
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
605
662
  def rewhere(conditions)
606
- unscope(where: conditions.keys).where(conditions)
663
+ scope = spawn
664
+ where_clause = scope.build_where_clause(conditions)
665
+
666
+ scope.unscope!(where: where_clause.extract_attributes)
667
+ scope.where_clause += where_clause
668
+ scope
669
+ end
670
+
671
+ # Returns a new relation, which is the logical intersection of this relation and the one passed
672
+ # as an argument.
673
+ #
674
+ # The two relations must be structurally compatible: they must be scoping the same model, and
675
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
676
+ # present).
677
+ #
678
+ # Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
679
+ # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
680
+ #
681
+ def and(other)
682
+ if other.is_a?(Relation)
683
+ spawn.and!(other)
684
+ else
685
+ raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
686
+ end
687
+ end
688
+
689
+ def and!(other) # :nodoc:
690
+ incompatible_values = structurally_incompatible_values_for(other)
691
+
692
+ unless incompatible_values.empty?
693
+ raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
694
+ end
695
+
696
+ self.where_clause |= other.where_clause
697
+ self.having_clause |= other.having_clause
698
+ self.references_values |= other.references_values
699
+
700
+ self
607
701
  end
608
702
 
609
703
  # Returns a new relation, which is the logical union of this relation and the one passed as an
@@ -611,21 +705,21 @@ module ActiveRecord
611
705
  #
612
706
  # The two relations must be structurally compatible: they must be scoping the same model, and
613
707
  # they must differ only by #where (if no #group has been defined) or #having (if a #group is
614
- # present). Neither relation may have a #limit, #offset, or #distinct set.
708
+ # present).
615
709
  #
616
710
  # Post.where("id = 1").or(Post.where("author_id = 3"))
617
711
  # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
618
712
  #
619
713
  def or(other)
620
- unless other.is_a? Relation
714
+ if other.is_a?(Relation)
715
+ spawn.or!(other)
716
+ else
621
717
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
622
718
  end
623
-
624
- spawn.or!(other)
625
719
  end
626
720
 
627
721
  def or!(other) # :nodoc:
628
- incompatible_values = structurally_incompatible_values_for_or(other)
722
+ incompatible_values = structurally_incompatible_values_for(other)
629
723
 
630
724
  unless incompatible_values.empty?
631
725
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
@@ -633,7 +727,7 @@ module ActiveRecord
633
727
 
634
728
  self.where_clause = self.where_clause.or(other.where_clause)
635
729
  self.having_clause = having_clause.or(other.having_clause)
636
- self.references_values += other.references_values
730
+ self.references_values |= other.references_values
637
731
 
638
732
  self
639
733
  end
@@ -647,10 +741,7 @@ module ActiveRecord
647
741
  end
648
742
 
649
743
  def having!(opts, *rest) # :nodoc:
650
- opts = sanitize_forbidden_attributes(opts)
651
- references!(PredicateBuilder.references(opts)) if Hash === opts
652
-
653
- self.having_clause += having_clause_factory.build(opts, rest)
744
+ self.having_clause += build_having_clause(opts, rest)
654
745
  self
655
746
  end
656
747
 
@@ -752,6 +843,21 @@ module ActiveRecord
752
843
  self
753
844
  end
754
845
 
846
+ # Sets the returned relation to strict_loading mode. This will raise an error
847
+ # if the record tries to lazily load an association.
848
+ #
849
+ # user = User.strict_loading.first
850
+ # user.comments.to_a
851
+ # => ActiveRecord::StrictLoadingViolationError
852
+ def strict_loading(value = true)
853
+ spawn.strict_loading!(value)
854
+ end
855
+
856
+ def strict_loading!(value = true) # :nodoc:
857
+ self.strict_loading_value = value
858
+ self
859
+ end
860
+
755
861
  # Sets attributes to be used when creating new records from a
756
862
  # relation object.
757
863
  #
@@ -876,6 +982,27 @@ module ActiveRecord
876
982
  self
877
983
  end
878
984
 
985
+ # Specify optimizer hints to be used in the SELECT statement.
986
+ #
987
+ # Example (for MySQL):
988
+ #
989
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
990
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
991
+ #
992
+ # Example (for PostgreSQL with pg_hint_plan):
993
+ #
994
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
995
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
996
+ def optimizer_hints(*args)
997
+ check_if_method_has_arguments!(:optimizer_hints, args)
998
+ spawn.optimizer_hints!(*args)
999
+ end
1000
+
1001
+ def optimizer_hints!(*args) # :nodoc:
1002
+ self.optimizer_hints_values |= args
1003
+ self
1004
+ end
1005
+
879
1006
  # Reverse the existing order clause on the relation.
880
1007
  #
881
1008
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -884,8 +1011,7 @@ module ActiveRecord
884
1011
  end
885
1012
 
886
1013
  def reverse_order! # :nodoc:
887
- orders = order_values.uniq
888
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
889
1015
  self.order_values = reverse_sql_order(orders)
890
1016
  self
891
1017
  end
@@ -895,25 +1021,112 @@ module ActiveRecord
895
1021
  self
896
1022
  end
897
1023
 
1024
+ def skip_preloading! # :nodoc:
1025
+ self.skip_preloading_value = true
1026
+ self
1027
+ end
1028
+
1029
+ # Adds an SQL comment to queries generated from this relation. For example:
1030
+ #
1031
+ # User.annotate("selecting user names").select(:name)
1032
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
1033
+ #
1034
+ # User.annotate("selecting", "user", "names").select(:name)
1035
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1036
+ #
1037
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1038
+ def annotate(*args)
1039
+ check_if_method_has_arguments!(:annotate, args)
1040
+ spawn.annotate!(*args)
1041
+ end
1042
+
1043
+ # Like #annotate, but modifies relation in place.
1044
+ def annotate!(*args) # :nodoc:
1045
+ self.annotate_values += args
1046
+ self
1047
+ end
1048
+
1049
+ # Deduplicate multiple values.
1050
+ def uniq!(name)
1051
+ if values = @values[name]
1052
+ values.uniq! if values.is_a?(Array) && !values.empty?
1053
+ end
1054
+ self
1055
+ end
1056
+
898
1057
  # Returns the Arel object associated with the relation.
899
1058
  def arel(aliases = nil) # :nodoc:
900
1059
  @arel ||= build_arel(aliases)
901
1060
  end
902
1061
 
903
- # Returns a relation value with a given name
904
- def get_value(name) # :nodoc:
905
- @values.fetch(name, DEFAULT_VALUES[name])
1062
+ def construct_join_dependency(associations, join_type) # :nodoc:
1063
+ ActiveRecord::Associations::JoinDependency.new(
1064
+ klass, table, associations, join_type
1065
+ )
906
1066
  end
907
1067
 
908
1068
  protected
1069
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1070
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
1071
+
1072
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1073
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1074
+ end
1075
+ end
1076
+
1077
+ def build_where_clause(opts, rest = []) # :nodoc:
1078
+ opts = sanitize_forbidden_attributes(opts)
1079
+
1080
+ case opts
1081
+ when String, Array
1082
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1083
+ when Hash
1084
+ opts = opts.transform_keys do |key|
1085
+ key = key.to_s
1086
+ klass.attribute_aliases[key] || key
1087
+ end
1088
+ references = PredicateBuilder.references(opts)
1089
+ self.references_values |= references unless references.empty?
1090
+
1091
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1092
+ lookup_table_klass_from_join_dependencies(table_name)
1093
+ end
1094
+ when Arel::Nodes::Node
1095
+ parts = [opts]
1096
+ else
1097
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1098
+ end
909
1099
 
910
- # Sets the relation value with the given name
911
- def set_value(name, value) # :nodoc:
912
- assert_mutability!
913
- @values[name] = value
1100
+ Relation::WhereClause.new(parts)
914
1101
  end
1102
+ alias :build_having_clause :build_where_clause
915
1103
 
916
1104
  private
1105
+ def lookup_table_klass_from_join_dependencies(table_name)
1106
+ each_join_dependencies do |join|
1107
+ return join.base_klass if table_name == join.table_name
1108
+ end
1109
+ nil
1110
+ end
1111
+
1112
+ def each_join_dependencies(join_dependencies = build_join_dependencies)
1113
+ join_dependencies.each do |join_dependency|
1114
+ join_dependency.each do |join|
1115
+ yield join
1116
+ end
1117
+ end
1118
+ end
1119
+
1120
+ def build_join_dependencies
1121
+ associations = joins_values | left_outer_joins_values
1122
+ associations |= eager_load_values unless eager_load_values.empty?
1123
+ associations |= includes_values unless includes_values.empty?
1124
+
1125
+ join_dependencies = []
1126
+ join_dependencies.unshift construct_join_dependency(
1127
+ select_association_list(associations, join_dependencies), nil
1128
+ )
1129
+ end
917
1130
 
918
1131
  def assert_mutability!
919
1132
  raise ImmutableRelation if @loaded
@@ -923,40 +1136,44 @@ module ActiveRecord
923
1136
  def build_arel(aliases)
924
1137
  arel = Arel::SelectManager.new(table)
925
1138
 
926
- aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
927
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
1139
+ build_joins(arel.join_sources, aliases)
928
1140
 
929
1141
  arel.where(where_clause.ast) unless where_clause.empty?
930
1142
  arel.having(having_clause.ast) unless having_clause.empty?
931
- if limit_value
932
- limit_attribute = ActiveModel::Attribute.with_cast_value(
933
- "LIMIT".freeze,
934
- connection.sanitize_limit(limit_value),
935
- Type.default_value,
936
- )
937
- arel.take(Arel::Nodes::BindParam.new(limit_attribute))
938
- end
939
- if offset_value
940
- offset_attribute = ActiveModel::Attribute.with_cast_value(
941
- "OFFSET".freeze,
942
- offset_value.to_i,
943
- Type.default_value,
944
- )
945
- arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
946
- end
947
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1143
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1144
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1145
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
948
1146
 
949
1147
  build_order(arel)
950
-
951
1148
  build_select(arel)
952
1149
 
1150
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
953
1151
  arel.distinct(distinct_value)
954
1152
  arel.from(build_from) unless from_clause.empty?
955
1153
  arel.lock(lock_value) if lock_value
956
1154
 
1155
+ unless annotate_values.empty?
1156
+ annotates = annotate_values
1157
+ annotates = annotates.uniq if annotates.size > 1
1158
+ unless annotates == annotate_values
1159
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
1160
+ Duplicated query annotations are no longer shown in queries in Rails 6.2.
1161
+ To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1162
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1163
+ MSG
1164
+ annotates = annotate_values
1165
+ end
1166
+ arel.comment(*annotates)
1167
+ end
1168
+
957
1169
  arel
958
1170
  end
959
1171
 
1172
+ def build_cast_value(name, value)
1173
+ cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1174
+ Arel::Nodes::BindParam.new(cast_value)
1175
+ end
1176
+
960
1177
  def build_from
961
1178
  opts = from_clause.value
962
1179
  name = from_clause.name
@@ -972,75 +1189,101 @@ module ActiveRecord
972
1189
  end
973
1190
  end
974
1191
 
975
- def build_left_outer_joins(manager, outer_joins, aliases)
976
- buckets = outer_joins.group_by do |join|
977
- case join
1192
+ def select_association_list(associations, stashed_joins = nil)
1193
+ result = []
1194
+ associations.each do |association|
1195
+ case association
978
1196
  when Hash, Symbol, Array
979
- :association_join
1197
+ result << association
980
1198
  when ActiveRecord::Associations::JoinDependency
981
- :stashed_join
1199
+ stashed_joins&.<< association
982
1200
  else
983
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1201
+ yield association if block_given?
984
1202
  end
985
1203
  end
1204
+ result
1205
+ end
986
1206
 
987
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1207
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
988
1208
  end
989
1209
 
990
- def build_joins(manager, joins, aliases)
991
- buckets = joins.group_by do |join|
992
- case join
993
- when String
994
- :string_join
995
- when Hash, Symbol, Array
996
- :association_join
997
- when ActiveRecord::Associations::JoinDependency
998
- :stashed_join
999
- when Arel::Nodes::Join
1000
- :join_node
1210
+ def build_join_buckets
1211
+ buckets = Hash.new { |h, k| h[k] = [] }
1212
+
1213
+ unless left_outer_joins_values.empty?
1214
+ stashed_left_joins = []
1215
+ left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1216
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1217
+ end
1218
+
1219
+ if joins_values.empty?
1220
+ buckets[:association_join] = left_joins
1221
+ buckets[:stashed_join] = stashed_left_joins
1222
+ return buckets, Arel::Nodes::OuterJoin
1223
+ else
1224
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1225
+ end
1226
+ end
1227
+
1228
+ joins = joins_values.dup
1229
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1230
+ stashed_eager_load = joins.pop if joins.last.base_klass == klass
1231
+ end
1232
+
1233
+ joins.each_with_index do |join, i|
1234
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
1235
+ end
1236
+
1237
+ while joins.first.is_a?(Arel::Nodes::Join)
1238
+ join_node = joins.shift
1239
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1240
+ buckets[:join_node] << join_node
1241
+ else
1242
+ buckets[:leading_join] << join_node
1243
+ end
1244
+ end
1245
+
1246
+ buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1247
+ if join.is_a?(Arel::Nodes::Join)
1248
+ buckets[:join_node] << join
1001
1249
  else
1002
1250
  raise "unknown class: %s" % join.class.name
1003
1251
  end
1004
1252
  end
1005
1253
 
1006
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1254
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1255
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1256
+
1257
+ return buckets, Arel::Nodes::InnerJoin
1007
1258
  end
1008
1259
 
1009
- def build_join_query(manager, buckets, join_type, aliases)
1010
- buckets.default = []
1260
+ def build_joins(join_sources, aliases = nil)
1261
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1262
+
1263
+ buckets, join_type = build_join_buckets
1011
1264
 
1012
1265
  association_joins = buckets[:association_join]
1013
1266
  stashed_joins = buckets[:stashed_join]
1014
- join_nodes = buckets[:join_node].uniq
1015
- string_joins = buckets[:string_join].map(&:strip).uniq
1016
-
1017
- join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1018
- alias_tracker = alias_tracker(join_list, aliases)
1019
-
1020
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1021
- klass, table, association_joins
1022
- )
1267
+ leading_joins = buckets[:leading_join]
1268
+ join_nodes = buckets[:join_node]
1023
1269
 
1024
- joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1025
- joins.each { |join| manager.from(join) }
1270
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1026
1271
 
1027
- manager.join_sources.concat(join_list)
1028
-
1029
- alias_tracker.aliases
1030
- end
1272
+ unless association_joins.empty? && stashed_joins.empty?
1273
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1274
+ join_dependency = construct_join_dependency(association_joins, join_type)
1275
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1276
+ end
1031
1277
 
1032
- def convert_join_strings_to_ast(joins)
1033
- joins
1034
- .flatten
1035
- .reject(&:blank?)
1036
- .map { |join| table.create_string_join(Arel.sql(join)) }
1278
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1279
+ join_sources
1037
1280
  end
1038
1281
 
1039
1282
  def build_select(arel)
1040
1283
  if select_values.any?
1041
- arel.project(*arel_columns(select_values.uniq))
1284
+ arel.project(*arel_columns(select_values))
1042
1285
  elsif klass.ignored_columns.any?
1043
- arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1286
+ arel.project(*klass.column_names.map { |field| table[field] })
1044
1287
  else
1045
1288
  arel.project(table[Arel.star])
1046
1289
  end
@@ -1064,23 +1307,30 @@ module ActiveRecord
1064
1307
  end
1065
1308
 
1066
1309
  def arel_column(field)
1067
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1310
+ field = klass.attribute_aliases[field] || field
1068
1311
  from = from_clause.name || from_clause.value
1069
1312
 
1070
1313
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1071
- arel_attribute(field)
1314
+ table[field]
1315
+ elsif field.match?(/\A\w+\.\w+\z/)
1316
+ table, column = field.split(".")
1317
+ predicate_builder.resolve_arel_attribute(table, column) do
1318
+ lookup_table_klass_from_join_dependencies(table)
1319
+ end
1072
1320
  else
1073
1321
  yield field
1074
1322
  end
1075
1323
  end
1076
1324
 
1077
1325
  def table_name_matches?(from)
1078
- /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1326
+ table_name = Regexp.escape(table.name)
1327
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1328
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1079
1329
  end
1080
1330
 
1081
1331
  def reverse_sql_order(order_query)
1082
1332
  if order_query.empty?
1083
- return [arel_attribute(primary_key).desc] if primary_key
1333
+ return [table[primary_key].desc] if primary_key
1084
1334
  raise IrreversibleOrderError,
1085
1335
  "Relation has no current order and table has no primary key to be used as default order"
1086
1336
  end
@@ -1091,9 +1341,11 @@ module ActiveRecord
1091
1341
  o.desc
1092
1342
  when Arel::Nodes::Ordering
1093
1343
  o.reverse
1344
+ when Arel::Nodes::NodeExpression
1345
+ o.desc
1094
1346
  when String
1095
1347
  if does_not_support_reverse?(o)
1096
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1348
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1097
1349
  end
1098
1350
  o.split(",").map! do |s|
1099
1351
  s.strip!
@@ -1113,13 +1365,11 @@ module ActiveRecord
1113
1365
  # Uses SQL function with multiple arguments.
1114
1366
  (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1115
1367
  # Uses "nulls first" like construction.
1116
- /nulls (first|last)\Z/i.match?(order)
1368
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1117
1369
  end
1118
1370
 
1119
1371
  def build_order(arel)
1120
- orders = order_values.uniq
1121
- orders.reject!(&:blank?)
1122
-
1372
+ orders = order_values.compact_blank
1123
1373
  arel.order(*orders) unless orders.empty?
1124
1374
  end
1125
1375
 
@@ -1139,21 +1389,15 @@ module ActiveRecord
1139
1389
  end
1140
1390
 
1141
1391
  def preprocess_order_args(order_args)
1142
- order_args.map! do |arg|
1143
- klass.sanitize_sql_for_order(arg)
1144
- end
1145
- order_args.flatten!
1146
-
1147
- @klass.enforce_raw_sql_whitelist(
1392
+ @klass.disallow_raw_sql!(
1148
1393
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1149
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
1394
+ permit: connection.column_name_with_order_matcher
1150
1395
  )
1151
1396
 
1152
1397
  validate_order_args(order_args)
1153
1398
 
1154
- references = order_args.grep(String)
1155
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1156
- references!(references) if references.any?
1399
+ references = column_references(order_args)
1400
+ self.references_values |= references unless references.empty?
1157
1401
 
1158
1402
  # if a symbol is given we prepend the quoted table name
1159
1403
  order_args.map! do |arg|
@@ -1164,9 +1408,9 @@ module ActiveRecord
1164
1408
  arg.map { |field, dir|
1165
1409
  case field
1166
1410
  when Arel::Nodes::SqlLiteral
1167
- field.send(dir.downcase)
1411
+ field.public_send(dir.downcase)
1168
1412
  else
1169
- order_column(field.to_s).send(dir.downcase)
1413
+ order_column(field.to_s).public_send(dir.downcase)
1170
1414
  end
1171
1415
  }
1172
1416
  else
@@ -1175,16 +1419,54 @@ module ActiveRecord
1175
1419
  end.flatten!
1176
1420
  end
1177
1421
 
1422
+ def sanitize_order_arguments(order_args)
1423
+ order_args.map! do |arg|
1424
+ klass.sanitize_sql_for_order(arg)
1425
+ end
1426
+ order_args.flatten!
1427
+ order_args.compact_blank!
1428
+ end
1429
+
1430
+ def column_references(order_args)
1431
+ references = order_args.grep(String)
1432
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1433
+ references
1434
+ end
1435
+
1178
1436
  def order_column(field)
1179
1437
  arel_column(field) do |attr_name|
1180
1438
  if attr_name == "count" && !group_values.empty?
1181
- arel_attribute(attr_name)
1439
+ table[attr_name]
1182
1440
  else
1183
1441
  Arel.sql(connection.quote_table_name(attr_name))
1184
1442
  end
1185
1443
  end
1186
1444
  end
1187
1445
 
1446
+ def resolve_arel_attributes(attrs)
1447
+ attrs.flat_map do |attr|
1448
+ case attr
1449
+ when Arel::Predications
1450
+ attr
1451
+ when Hash
1452
+ attr.flat_map do |table, columns|
1453
+ table = table.to_s
1454
+ Array(columns).map do |column|
1455
+ predicate_builder.resolve_arel_attribute(table, column)
1456
+ end
1457
+ end
1458
+ else
1459
+ attr = attr.to_s
1460
+ if attr.include?(".")
1461
+ table, column = attr.split(".", 2)
1462
+ predicate_builder.resolve_arel_attribute(table, column)
1463
+ else
1464
+ attr
1465
+ end
1466
+ end
1467
+ end
1468
+ end
1469
+
1188
1470
  # Checks to make sure that the arguments are not blank. Note that if some
1189
1471
  # blank-like object were initially passed into the query method, then this
1190
1472
  # method will not raise an error.
@@ -1201,33 +1483,40 @@ module ActiveRecord
1201
1483
  # check_if_method_has_arguments!("references", args)
1202
1484
  # ...
1203
1485
  # end
1204
- def check_if_method_has_arguments!(method_name, args)
1486
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1205
1487
  if args.blank?
1206
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1488
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1489
+ elsif block_given?
1490
+ yield args
1491
+ else
1492
+ args.flatten!
1493
+ args.compact_blank!
1207
1494
  end
1208
1495
  end
1209
1496
 
1210
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1211
- def structurally_incompatible_values_for_or(other)
1212
- STRUCTURAL_OR_METHODS.reject do |method|
1213
- get_value(method) == other.get_value(method)
1497
+ STRUCTURAL_VALUE_METHODS = (
1498
+ Relation::VALUE_METHODS -
1499
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1500
+ ).freeze # :nodoc:
1501
+
1502
+ def structurally_incompatible_values_for(other)
1503
+ values = other.values
1504
+ STRUCTURAL_VALUE_METHODS.reject do |method|
1505
+ v1, v2 = @values[method], values[method]
1506
+ if v1.is_a?(Array)
1507
+ next true unless v2.is_a?(Array)
1508
+ v1 = v1.uniq
1509
+ v2 = v2.uniq
1510
+ end
1511
+ v1 == v2
1214
1512
  end
1215
1513
  end
1514
+ end
1216
1515
 
1217
- def where_clause_factory
1218
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1219
- end
1220
- alias having_clause_factory where_clause_factory
1221
-
1222
- DEFAULT_VALUES = {
1223
- create_with: FROZEN_EMPTY_HASH,
1224
- where: Relation::WhereClause.empty,
1225
- having: Relation::WhereClause.empty,
1226
- from: Relation::FromClause.empty
1227
- }
1228
-
1229
- Relation::MULTI_VALUE_METHODS.each do |value|
1230
- DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1231
- end
1516
+ class Relation # :nodoc:
1517
+ # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1518
+ # TODO: Remove the class once Rails 6.1 has released.
1519
+ class WhereClauseFactory # :nodoc:
1520
+ end
1232
1521
  end
1233
1522
  end