activerecord 5.0.7.2 → 6.1.1

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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +829 -2015
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +11 -9
  5. data/examples/performance.rb +31 -29
  6. data/examples/simple.rb +5 -3
  7. data/lib/active_record.rb +37 -29
  8. data/lib/active_record/aggregations.rb +249 -247
  9. data/lib/active_record/association_relation.rb +30 -18
  10. data/lib/active_record/associations.rb +1714 -1596
  11. data/lib/active_record/associations/alias_tracker.rb +36 -42
  12. data/lib/active_record/associations/association.rb +143 -68
  13. data/lib/active_record/associations/association_scope.rb +98 -94
  14. data/lib/active_record/associations/belongs_to_association.rb +76 -46
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -28
  17. data/lib/active_record/associations/builder/belongs_to.rb +52 -60
  18. data/lib/active_record/associations/builder/collection_association.rb +12 -22
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
  20. data/lib/active_record/associations/builder/has_many.rb +10 -2
  21. data/lib/active_record/associations/builder/has_one.rb +35 -2
  22. data/lib/active_record/associations/builder/singular_association.rb +5 -1
  23. data/lib/active_record/associations/collection_association.rb +104 -259
  24. data/lib/active_record/associations/collection_proxy.rb +169 -125
  25. data/lib/active_record/associations/foreign_association.rb +22 -0
  26. data/lib/active_record/associations/has_many_association.rb +46 -31
  27. data/lib/active_record/associations/has_many_through_association.rb +66 -46
  28. data/lib/active_record/associations/has_one_association.rb +71 -52
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +169 -180
  31. data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +97 -104
  35. data/lib/active_record/associations/preloader/association.rb +109 -97
  36. data/lib/active_record/associations/preloader/through_association.rb +77 -76
  37. data/lib/active_record/associations/singular_association.rb +12 -45
  38. data/lib/active_record/associations/through_association.rb +27 -15
  39. data/lib/active_record/attribute_assignment.rb +55 -60
  40. data/lib/active_record/attribute_methods.rb +111 -141
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
  42. data/lib/active_record/attribute_methods/dirty.rb +172 -112
  43. data/lib/active_record/attribute_methods/primary_key.rb +88 -91
  44. data/lib/active_record/attribute_methods/query.rb +6 -8
  45. data/lib/active_record/attribute_methods/read.rb +18 -50
  46. data/lib/active_record/attribute_methods/serialization.rb +38 -10
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
  48. data/lib/active_record/attribute_methods/write.rb +25 -32
  49. data/lib/active_record/attributes.rb +69 -31
  50. data/lib/active_record/autosave_association.rb +102 -66
  51. data/lib/active_record/base.rb +16 -25
  52. data/lib/active_record/callbacks.rb +202 -43
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +11 -12
  55. data/lib/active_record/connection_adapters.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
  69. data/lib/active_record/connection_adapters/column.rb +55 -13
  70. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  71. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
  82. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  83. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
  85. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
  86. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
  93. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
  95. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
  98. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
  106. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
  107. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  109. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  110. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
  121. data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
  123. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  124. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
  131. data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
  132. data/lib/active_record/connection_handling.rb +287 -45
  133. data/lib/active_record/core.rb +385 -181
  134. data/lib/active_record/counter_cache.rb +60 -28
  135. data/lib/active_record/database_configurations.rb +272 -0
  136. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  137. data/lib/active_record/database_configurations/database_config.rb +80 -0
  138. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  139. data/lib/active_record/database_configurations/url_config.rb +53 -0
  140. data/lib/active_record/delegated_type.rb +209 -0
  141. data/lib/active_record/destroy_association_async_job.rb +36 -0
  142. data/lib/active_record/dynamic_matchers.rb +87 -87
  143. data/lib/active_record/enum.rb +122 -47
  144. data/lib/active_record/errors.rb +153 -22
  145. data/lib/active_record/explain.rb +13 -8
  146. data/lib/active_record/explain_registry.rb +3 -1
  147. data/lib/active_record/explain_subscriber.rb +9 -4
  148. data/lib/active_record/fixture_set/file.rb +20 -22
  149. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  150. data/lib/active_record/fixture_set/render_context.rb +17 -0
  151. data/lib/active_record/fixture_set/table_row.rb +152 -0
  152. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  153. data/lib/active_record/fixtures.rb +246 -507
  154. data/lib/active_record/gem_version.rb +6 -4
  155. data/lib/active_record/inheritance.rb +168 -95
  156. data/lib/active_record/insert_all.rb +208 -0
  157. data/lib/active_record/integration.rb +114 -25
  158. data/lib/active_record/internal_metadata.rb +30 -24
  159. data/lib/active_record/legacy_yaml_adapter.rb +11 -5
  160. data/lib/active_record/locking/optimistic.rb +81 -85
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +68 -31
  163. data/lib/active_record/middleware/database_selector.rb +77 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  166. data/lib/active_record/migration.rb +439 -342
  167. data/lib/active_record/migration/command_recorder.rb +152 -98
  168. data/lib/active_record/migration/compatibility.rb +229 -60
  169. data/lib/active_record/migration/join_table.rb +8 -7
  170. data/lib/active_record/model_schema.rb +230 -122
  171. data/lib/active_record/nested_attributes.rb +213 -203
  172. data/lib/active_record/no_touching.rb +11 -2
  173. data/lib/active_record/null_relation.rb +12 -34
  174. data/lib/active_record/persistence.rb +471 -97
  175. data/lib/active_record/query_cache.rb +23 -12
  176. data/lib/active_record/querying.rb +43 -25
  177. data/lib/active_record/railtie.rb +155 -43
  178. data/lib/active_record/railties/console_sandbox.rb +2 -0
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +507 -195
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +245 -269
  183. data/lib/active_record/relation.rb +475 -324
  184. data/lib/active_record/relation/batches.rb +125 -72
  185. data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
  186. data/lib/active_record/relation/calculations.rb +267 -171
  187. data/lib/active_record/relation/delegation.rb +73 -69
  188. data/lib/active_record/relation/finder_methods.rb +238 -248
  189. data/lib/active_record/relation/from_clause.rb +7 -9
  190. data/lib/active_record/relation/merger.rb +95 -77
  191. data/lib/active_record/relation/predicate_builder.rb +109 -110
  192. data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
  193. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  194. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
  195. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
  196. data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
  197. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  198. data/lib/active_record/relation/query_attribute.rb +33 -2
  199. data/lib/active_record/relation/query_methods.rb +654 -374
  200. data/lib/active_record/relation/record_fetch_warning.rb +8 -6
  201. data/lib/active_record/relation/spawn_methods.rb +15 -14
  202. data/lib/active_record/relation/where_clause.rb +171 -109
  203. data/lib/active_record/result.rb +88 -51
  204. data/lib/active_record/runtime_registry.rb +5 -3
  205. data/lib/active_record/sanitization.rb +73 -100
  206. data/lib/active_record/schema.rb +7 -14
  207. data/lib/active_record/schema_dumper.rb +101 -69
  208. data/lib/active_record/schema_migration.rb +16 -12
  209. data/lib/active_record/scoping.rb +20 -20
  210. data/lib/active_record/scoping/default.rb +92 -95
  211. data/lib/active_record/scoping/named.rb +39 -30
  212. data/lib/active_record/secure_token.rb +19 -9
  213. data/lib/active_record/serialization.rb +7 -3
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +80 -29
  216. data/lib/active_record/store.rb +122 -42
  217. data/lib/active_record/suppressor.rb +6 -3
  218. data/lib/active_record/table_metadata.rb +51 -39
  219. data/lib/active_record/tasks/database_tasks.rb +332 -115
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +246 -0
  225. data/lib/active_record/timestamp.rb +70 -38
  226. data/lib/active_record/touch_later.rb +26 -24
  227. data/lib/active_record/transactions.rb +121 -184
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type.rb +29 -17
  230. data/lib/active_record/type/adapter_specific_registry.rb +44 -48
  231. data/lib/active_record/type/date.rb +2 -0
  232. data/lib/active_record/type/date_time.rb +2 -0
  233. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  234. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  235. data/lib/active_record/type/internal/timezone.rb +2 -0
  236. data/lib/active_record/type/json.rb +30 -0
  237. data/lib/active_record/type/serialized.rb +20 -9
  238. data/lib/active_record/type/text.rb +11 -0
  239. data/lib/active_record/type/time.rb +12 -1
  240. data/lib/active_record/type/type_map.rb +14 -17
  241. data/lib/active_record/type/unsigned_integer.rb +16 -0
  242. data/lib/active_record/type_caster.rb +4 -2
  243. data/lib/active_record/type_caster/connection.rb +17 -13
  244. data/lib/active_record/type_caster/map.rb +10 -6
  245. data/lib/active_record/validations.rb +8 -5
  246. data/lib/active_record/validations/absence.rb +2 -0
  247. data/lib/active_record/validations/associated.rb +4 -3
  248. data/lib/active_record/validations/length.rb +2 -0
  249. data/lib/active_record/validations/numericality.rb +35 -0
  250. data/lib/active_record/validations/presence.rb +4 -2
  251. data/lib/active_record/validations/uniqueness.rb +52 -45
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/arel.rb +54 -0
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes.rb +70 -0
  269. data/lib/arel/nodes/and.rb +32 -0
  270. data/lib/arel/nodes/ascending.rb +23 -0
  271. data/lib/arel/nodes/binary.rb +126 -0
  272. data/lib/arel/nodes/bind_param.rb +44 -0
  273. data/lib/arel/nodes/case.rb +55 -0
  274. data/lib/arel/nodes/casted.rb +62 -0
  275. data/lib/arel/nodes/comment.rb +29 -0
  276. data/lib/arel/nodes/count.rb +12 -0
  277. data/lib/arel/nodes/delete_statement.rb +45 -0
  278. data/lib/arel/nodes/descending.rb +23 -0
  279. data/lib/arel/nodes/equality.rb +15 -0
  280. data/lib/arel/nodes/extract.rb +24 -0
  281. data/lib/arel/nodes/false.rb +16 -0
  282. data/lib/arel/nodes/full_outer_join.rb +8 -0
  283. data/lib/arel/nodes/function.rb +44 -0
  284. data/lib/arel/nodes/grouping.rb +11 -0
  285. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  286. data/lib/arel/nodes/in.rb +15 -0
  287. data/lib/arel/nodes/infix_operation.rb +92 -0
  288. data/lib/arel/nodes/inner_join.rb +8 -0
  289. data/lib/arel/nodes/insert_statement.rb +37 -0
  290. data/lib/arel/nodes/join_source.rb +20 -0
  291. data/lib/arel/nodes/matches.rb +18 -0
  292. data/lib/arel/nodes/named_function.rb +23 -0
  293. data/lib/arel/nodes/node.rb +51 -0
  294. data/lib/arel/nodes/node_expression.rb +13 -0
  295. data/lib/arel/nodes/ordering.rb +27 -0
  296. data/lib/arel/nodes/outer_join.rb +8 -0
  297. data/lib/arel/nodes/over.rb +15 -0
  298. data/lib/arel/nodes/regexp.rb +16 -0
  299. data/lib/arel/nodes/right_outer_join.rb +8 -0
  300. data/lib/arel/nodes/select_core.rb +67 -0
  301. data/lib/arel/nodes/select_statement.rb +41 -0
  302. data/lib/arel/nodes/sql_literal.rb +19 -0
  303. data/lib/arel/nodes/string_join.rb +11 -0
  304. data/lib/arel/nodes/table_alias.rb +31 -0
  305. data/lib/arel/nodes/terminal.rb +16 -0
  306. data/lib/arel/nodes/true.rb +16 -0
  307. data/lib/arel/nodes/unary.rb +44 -0
  308. data/lib/arel/nodes/unary_operation.rb +20 -0
  309. data/lib/arel/nodes/unqualified_column.rb +22 -0
  310. data/lib/arel/nodes/update_statement.rb +41 -0
  311. data/lib/arel/nodes/values_list.rb +9 -0
  312. data/lib/arel/nodes/window.rb +126 -0
  313. data/lib/arel/nodes/with.rb +11 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors.rb +13 -0
  321. data/lib/arel/visitors/dot.rb +308 -0
  322. data/lib/arel/visitors/mysql.rb +93 -0
  323. data/lib/arel/visitors/postgresql.rb +120 -0
  324. data/lib/arel/visitors/sqlite.rb +38 -0
  325. data/lib/arel/visitors/to_sql.rb +899 -0
  326. data/lib/arel/visitors/visitor.rb +45 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/rails/generators/active_record.rb +7 -5
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  331. data/lib/rails/generators/active_record/migration.rb +22 -3
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
  333. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
  335. data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +10 -1
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. metadata +141 -57
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -20
  347. data/lib/active_record/attribute.rb +0 -213
  348. data/lib/active_record/attribute/user_provided_default.rb +0 -28
  349. data/lib/active_record/attribute_decorators.rb +0 -67
  350. data/lib/active_record/attribute_mutation_tracker.rb +0 -70
  351. data/lib/active_record/attribute_set.rb +0 -110
  352. data/lib/active_record/attribute_set/builder.rb +0 -132
  353. data/lib/active_record/collection_cache_key.rb +0 -50
  354. data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
  355. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
  356. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  359. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
  360. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
  361. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
  362. data/lib/active_record/relation/where_clause_factory.rb +0 -38
  363. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract"
4
+
1
5
  module ActiveRecord
2
6
  class PredicateBuilder
3
7
  class ArrayHandler # :nodoc:
@@ -6,38 +10,39 @@ module ActiveRecord
6
10
  end
7
11
 
8
12
  def call(attribute, value)
9
- values = value.map { |x| x.is_a?(Base) ? x.id : x }
10
- nils, values = values.partition(&:nil?)
13
+ return attribute.in([]) if value.empty?
11
14
 
12
- return attribute.in([]) if values.empty? && nils.empty?
13
-
14
- ranges, values = values.partition { |v| v.is_a?(Range) }
15
+ values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
+ nils = values.extract!(&:nil?)
17
+ ranges = values.extract! { |v| v.is_a?(Range) }
15
18
 
16
19
  values_predicate =
17
20
  case values.length
18
21
  when 0 then NullPredicate
19
22
  when 1 then predicate_builder.build(attribute, values.first)
20
- else attribute.in(values)
23
+ else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
21
24
  end
22
25
 
23
26
  unless nils.empty?
24
- values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
27
+ values_predicate = values_predicate.or(attribute.eq(nil))
25
28
  end
26
29
 
27
- array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
28
- array_predicates.unshift(values_predicate)
29
- array_predicates.inject { |composite, predicate| composite.or(predicate) }
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
30
36
  end
31
37
 
32
- protected
33
-
34
- attr_reader :predicate_builder
38
+ private
39
+ attr_reader :predicate_builder
35
40
 
36
- module NullPredicate # :nodoc:
37
- def self.or(other)
38
- other
41
+ module NullPredicate # :nodoc:
42
+ def self.or(other)
43
+ other
44
+ end
39
45
  end
40
- end
41
46
  end
42
47
  end
43
48
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class AssociationQueryValue # :nodoc:
6
+ def initialize(associated_table, value)
7
+ @associated_table = associated_table
8
+ @value = value
9
+ end
10
+
11
+ def queries
12
+ [associated_table.join_foreign_key => ids]
13
+ end
14
+
15
+ private
16
+ attr_reader :associated_table, :value
17
+
18
+ def ids
19
+ case value
20
+ when Relation
21
+ value.select_values.empty? ? value.select(primary_key) : value
22
+ when Array
23
+ value.map { |v| convert_to_id(v) }
24
+ else
25
+ convert_to_id(value)
26
+ end
27
+ end
28
+
29
+ def primary_key
30
+ associated_table.join_primary_key
31
+ end
32
+
33
+ def convert_to_id(value)
34
+ if value.respond_to?(primary_key)
35
+ value.public_send(primary_key)
36
+ else
37
+ value
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder
3
5
  class BasicObjectHandler # :nodoc:
@@ -6,12 +8,12 @@ module ActiveRecord
6
8
  end
7
9
 
8
10
  def call(attribute, value)
9
- attribute.eq(value)
11
+ bind = predicate_builder.build_bind_attribute(attribute.name, value)
12
+ attribute.eq(bind)
10
13
  end
11
14
 
12
- protected
13
-
14
- attr_reader :predicate_builder
15
+ private
16
+ attr_reader :predicate_builder
15
17
  end
16
18
  end
17
19
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class PolymorphicArrayValue # :nodoc:
6
+ def initialize(associated_table, values)
7
+ @associated_table = associated_table
8
+ @values = values
9
+ end
10
+
11
+ def queries
12
+ type_to_ids_mapping.map do |type, ids|
13
+ query = {}
14
+ query[associated_table.join_foreign_type] = type if type
15
+ query[associated_table.join_foreign_key] = ids
16
+ query
17
+ end
18
+ end
19
+
20
+ private
21
+ attr_reader :associated_table, :values
22
+
23
+ def type_to_ids_mapping
24
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
25
+ values.each_with_object(default_hash) do |value, hash|
26
+ hash[klass(value)&.polymorphic_name] << convert_to_id(value)
27
+ end
28
+ end
29
+
30
+ def primary_key(value)
31
+ associated_table.join_primary_key(klass(value))
32
+ end
33
+
34
+ def klass(value)
35
+ case value
36
+ when Base
37
+ value.class
38
+ when Relation
39
+ value.klass
40
+ end
41
+ end
42
+
43
+ def convert_to_id(value)
44
+ case value
45
+ when Base
46
+ value._read_attribute(primary_key(value))
47
+ when Relation
48
+ value.select(primary_key(value))
49
+ else
50
+ value
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder
3
5
  class RangeHandler # :nodoc:
@@ -8,26 +10,13 @@ module ActiveRecord
8
10
  end
9
11
 
10
12
  def call(attribute, value)
11
- if value.begin.respond_to?(:infinite?) && value.begin.infinite?
12
- if value.end.respond_to?(:infinite?) && value.end.infinite?
13
- attribute.not_in([])
14
- elsif value.exclude_end?
15
- attribute.lt(value.end)
16
- else
17
- attribute.lteq(value.end)
18
- end
19
- elsif value.end.respond_to?(:infinite?) && value.end.infinite?
20
- attribute.gteq(value.begin)
21
- elsif value.exclude_end?
22
- attribute.gteq(value.begin).and(attribute.lt(value.end))
23
- else
24
- attribute.between(value)
25
- end
13
+ begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
14
+ end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
15
+ attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
26
16
  end
27
17
 
28
- protected
29
-
30
- attr_reader :predicate_builder
18
+ private
19
+ attr_reader :predicate_builder
31
20
  end
32
21
  end
33
22
  end
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder
3
5
  class RelationHandler # :nodoc:
4
6
  def call(attribute, value)
7
+ if value.eager_loading?
8
+ value = value.send(:apply_join_dependency)
9
+ end
10
+
5
11
  if value.select_values.empty?
6
- value = value.select(value.arel_attribute(value.klass.primary_key))
12
+ value = value.select(value.table[value.klass.primary_key])
7
13
  end
8
14
 
9
15
  attribute.in(value.arel)
@@ -1,8 +1,10 @@
1
- require 'active_record/attribute'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute"
2
4
 
3
5
  module ActiveRecord
4
6
  class Relation
5
- class QueryAttribute < Attribute # :nodoc:
7
+ class QueryAttribute < ActiveModel::Attribute # :nodoc:
6
8
  def type_cast(value)
7
9
  value
8
10
  end
@@ -14,6 +16,35 @@ module ActiveRecord
14
16
  def with_cast_value(value)
15
17
  QueryAttribute.new(name, value, type)
16
18
  end
19
+
20
+ def nil?
21
+ unless value_before_type_cast.is_a?(StatementCache::Substitute)
22
+ value_before_type_cast.nil? ||
23
+ type.respond_to?(:subtype, true) && value_for_database.nil?
24
+ end
25
+ rescue ::RangeError
26
+ end
27
+
28
+ def infinite?
29
+ infinity?(value_before_type_cast) || infinity?(value_for_database)
30
+ rescue ::RangeError
31
+ end
32
+
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
42
+ end
43
+
44
+ private
45
+ def infinity?(value)
46
+ value.respond_to?(:infinite?) && value.infinite?
47
+ end
17
48
  end
18
49
  end
19
50
  end
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/relation/from_clause"
2
4
  require "active_record/relation/query_attribute"
3
5
  require "active_record/relation/where_clause"
4
- require "active_record/relation/where_clause_factory"
5
- require 'active_model/forbidden_attributes_protection'
6
- require 'active_support/core_ext/string/filters'
6
+ require "active_model/forbidden_attributes_protection"
7
+ require "active_support/core_ext/array/wrap"
7
8
 
8
9
  module ActiveRecord
9
10
  module QueryMethods
@@ -14,8 +15,6 @@ module ActiveRecord
14
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
15
16
  # In this case, #where must be chained with #not to return a new relation.
16
17
  class WhereChain
17
- include ActiveModel::ForbiddenAttributesProtection
18
-
19
18
  def initialize(scope)
20
19
  @scope = scope
21
20
  end
@@ -42,92 +41,71 @@ module ActiveRecord
42
41
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
43
42
  #
44
43
  # User.where.not(name: "Jon", role: "admin")
45
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
44
+ # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
46
45
  def not(opts, *rest)
47
- opts = sanitize_forbidden_attributes(opts)
48
-
49
- where_clause = @scope.send(:where_clause_factory).build(opts, rest)
46
+ where_clause = @scope.send(:build_where_clause, opts, rest)
50
47
 
51
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
52
48
  @scope.where_clause += where_clause.invert
49
+
53
50
  @scope
54
51
  end
55
- end
56
52
 
57
- FROZEN_EMPTY_ARRAY = [].freeze
58
- Relation::MULTI_VALUE_METHODS.each do |name|
59
- class_eval <<-CODE, __FILE__, __LINE__ + 1
60
- def #{name}_values
61
- @values[:#{name}] || FROZEN_EMPTY_ARRAY
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)
62
77
  end
63
78
 
64
- def #{name}_values=(values)
65
- assert_mutability!
66
- @values[:#{name}] = values
67
- end
68
- CODE
79
+ @scope
80
+ end
69
81
  end
70
82
 
71
- (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
72
- class_eval <<-CODE, __FILE__, __LINE__ + 1
73
- def #{name}_value # def readonly_value
74
- @values[:#{name}] # @values[:readonly]
75
- end # end
76
- CODE
77
- end
83
+ FROZEN_EMPTY_ARRAY = [].freeze
84
+ FROZEN_EMPTY_HASH = {}.freeze
78
85
 
79
- Relation::SINGLE_VALUE_METHODS.each do |name|
80
- class_eval <<-CODE, __FILE__, __LINE__ + 1
81
- def #{name}_value=(value) # def readonly_value=(value)
82
- assert_mutability! # assert_mutability!
83
- @values[:#{name}] = value # @values[:readonly] = value
84
- end # end
85
- CODE
86
- end
86
+ Relation::VALUE_METHODS.each do |name|
87
+ method_name, default =
88
+ case name
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"]
95
+ end
87
96
 
88
- Relation::CLAUSE_METHODS.each do |name|
89
97
  class_eval <<-CODE, __FILE__, __LINE__ + 1
90
- def #{name}_clause # def where_clause
91
- @values[:#{name}] || new_#{name}_clause # @values[:where] || new_where_clause
92
- end # end
93
- #
94
- def #{name}_clause=(value) # def where_clause=(value)
95
- assert_mutability! # assert_mutability!
96
- @values[:#{name}] = value # @values[:where] = value
97
- end # end
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
98
106
  CODE
99
107
  end
100
108
 
101
- def bound_attributes
102
- if limit_value && !string_containing_comma?(limit_value)
103
- limit_bind = Attribute.with_cast_value(
104
- "LIMIT".freeze,
105
- connection.sanitize_limit(limit_value),
106
- Type::Value.new,
107
- )
108
- end
109
- if offset_value
110
- offset_bind = Attribute.with_cast_value(
111
- "OFFSET".freeze,
112
- offset_value.to_i,
113
- Type::Value.new,
114
- )
115
- end
116
- connection.combine_bind_parameters(
117
- from_clause: from_clause.binds,
118
- join_clause: arel.bind_values,
119
- where_clause: where_clause.binds,
120
- having_clause: having_clause.binds,
121
- limit: limit_bind,
122
- offset: offset_bind,
123
- )
124
- end
125
-
126
- FROZEN_EMPTY_HASH = {}.freeze
127
- def create_with_value # :nodoc:
128
- @values[:create_with] || FROZEN_EMPTY_HASH
129
- end
130
-
131
109
  alias extensions extending_values
132
110
 
133
111
  # Specify relationships to be included in the result set. For
@@ -152,7 +130,7 @@ module ActiveRecord
152
130
  #
153
131
  # === conditions
154
132
  #
155
- # 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
156
134
  # to explicitly reference them. For example:
157
135
  #
158
136
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -163,15 +141,18 @@ module ActiveRecord
163
141
  #
164
142
  # Note that #includes works with association names while #references needs
165
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' })
166
150
  def includes(*args)
167
151
  check_if_method_has_arguments!(:includes, args)
168
152
  spawn.includes!(*args)
169
153
  end
170
154
 
171
155
  def includes!(*args) # :nodoc:
172
- args.reject!(&:blank?)
173
- args.flatten!
174
-
175
156
  self.includes_values |= args
176
157
  self
177
158
  end
@@ -188,7 +169,7 @@ module ActiveRecord
188
169
  end
189
170
 
190
171
  def eager_load!(*args) # :nodoc:
191
- self.eager_load_values += args
172
+ self.eager_load_values |= args
192
173
  self
193
174
  end
194
175
 
@@ -202,10 +183,23 @@ module ActiveRecord
202
183
  end
203
184
 
204
185
  def preload!(*args) # :nodoc:
205
- self.preload_values += args
186
+ self.preload_values |= args
206
187
  self
207
188
  end
208
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
+
209
203
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
210
204
  # and should therefore be JOINed in any query rather than loaded separately.
211
205
  # This method only works in conjunction with #includes.
@@ -222,21 +216,19 @@ module ActiveRecord
222
216
  end
223
217
 
224
218
  def references!(*table_names) # :nodoc:
225
- table_names.flatten!
226
- table_names.map!(&:to_s)
227
-
228
219
  self.references_values |= table_names
229
220
  self
230
221
  end
231
222
 
232
223
  # Works in two unique ways.
233
224
  #
234
- # First: takes a block so it can be used just like +Array#select+.
225
+ # First: takes a block so it can be used just like <tt>Array#select</tt>.
235
226
  #
236
227
  # Model.all.select { |m| m.field == value }
237
228
  #
238
229
  # This will build an array of objects from the database for the scope,
239
- # converting them into an array and iterating through them using +Array#select+.
230
+ # converting them into an array and iterating through them using
231
+ # <tt>Array#select</tt>.
240
232
  #
241
233
  # Second: Modifies the SELECT statement for the query so that only certain
242
234
  # fields are retrieved:
@@ -269,17 +261,41 @@ module ActiveRecord
269
261
  # Model.select(:field).first.other_field
270
262
  # # => ActiveModel::MissingAttributeError: missing attribute: other_field
271
263
  def select(*fields)
272
- return super if block_given?
273
- raise ArgumentError, 'Call this with at least one field' if fields.empty?
264
+ if block_given?
265
+ if fields.any?
266
+ raise ArgumentError, "`select' with block doesn't take arguments."
267
+ end
268
+
269
+ return super()
270
+ end
271
+
272
+ check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
274
273
  spawn._select!(*fields)
275
274
  end
276
275
 
277
276
  def _select!(*fields) # :nodoc:
278
- fields.flatten!
279
- fields.map! do |field|
280
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
281
- end
282
- 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
283
299
  self
284
300
  end
285
301
 
@@ -309,8 +325,6 @@ module ActiveRecord
309
325
  end
310
326
 
311
327
  def group!(*args) # :nodoc:
312
- args.flatten!
313
-
314
328
  self.group_values += args
315
329
  self
316
330
  end
@@ -335,14 +349,16 @@ module ActiveRecord
335
349
  # User.order('name DESC, email')
336
350
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
337
351
  def order(*args)
338
- check_if_method_has_arguments!(:order, args)
352
+ check_if_method_has_arguments!(:order, args) do
353
+ sanitize_order_arguments(args)
354
+ end
339
355
  spawn.order!(*args)
340
356
  end
341
357
 
358
+ # Same as #order but operates on relation in-place instead of copying.
342
359
  def order!(*args) # :nodoc:
343
- preprocess_order_args(args)
344
-
345
- self.order_values += args
360
+ preprocess_order_args(args) unless args.empty?
361
+ self.order_values |= args
346
362
  self
347
363
  end
348
364
 
@@ -356,21 +372,24 @@ module ActiveRecord
356
372
  #
357
373
  # generates a query with 'ORDER BY id ASC, name ASC'.
358
374
  def reorder(*args)
359
- 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
360
378
  spawn.reorder!(*args)
361
379
  end
362
380
 
381
+ # Same as #reorder but operates on relation in-place instead of copying.
363
382
  def reorder!(*args) # :nodoc:
364
- preprocess_order_args(args)
365
-
383
+ preprocess_order_args(args) unless args.all?(&:blank?)
384
+ args.uniq!
366
385
  self.reordering_value = true
367
386
  self.order_values = args
368
387
  self
369
388
  end
370
389
 
371
390
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
372
- :limit, :offset, :joins, :includes, :from,
373
- :readonly, :having])
391
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
392
+ :includes, :from, :readonly, :having, :optimizer_hints])
374
393
 
375
394
  # Removes an unwanted relation that is already defined on a chain of relations.
376
395
  # This is useful when passing around chains of relations and would like to
@@ -411,20 +430,24 @@ module ActiveRecord
411
430
  end
412
431
 
413
432
  def unscope!(*args) # :nodoc:
414
- args.flatten!
415
433
  self.unscope_values += args
416
434
 
417
435
  args.each do |scope|
418
436
  case scope
419
437
  when Symbol
420
- symbol_unscoping(scope)
438
+ scope = :left_outer_joins if scope == :left_joins
439
+ if !VALID_UNSCOPING_VALUES.include?(scope)
440
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
441
+ end
442
+ assert_mutability!
443
+ @values.delete(scope)
421
444
  when Hash
422
445
  scope.each do |key, target_value|
423
446
  if key != :where
424
447
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
425
448
  end
426
449
 
427
- target_values = Array(target_value).map(&:to_s)
450
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
428
451
  self.where_clause = where_clause.except(*target_values)
429
452
  end
430
453
  else
@@ -457,8 +480,7 @@ module ActiveRecord
457
480
  # # SELECT "users".*
458
481
  # # FROM "users"
459
482
  # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
460
- # # INNER JOIN "comments" "comments_posts"
461
- # # ON "comments_posts"."post_id" = "posts"."id"
483
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
462
484
  #
463
485
  # You can use strings in order to customize your joins:
464
486
  #
@@ -470,9 +492,7 @@ module ActiveRecord
470
492
  end
471
493
 
472
494
  def joins!(*args) # :nodoc:
473
- args.compact!
474
- args.flatten!
475
- self.joins_values += args
495
+ self.joins_values |= args
476
496
  self
477
497
  end
478
498
 
@@ -482,20 +502,15 @@ module ActiveRecord
482
502
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
483
503
  #
484
504
  def left_outer_joins(*args)
485
- check_if_method_has_arguments!(:left_outer_joins, args)
486
-
487
- args.compact!
488
- args.flatten!
489
-
505
+ check_if_method_has_arguments!(__callee__, args)
490
506
  spawn.left_outer_joins!(*args)
491
507
  end
492
508
  alias :left_joins :left_outer_joins
493
509
 
494
510
  def left_outer_joins!(*args) # :nodoc:
495
- self.left_outer_joins_values += args
511
+ self.left_outer_joins_values |= args
496
512
  self
497
513
  end
498
- alias :left_joins! :left_outer_joins!
499
514
 
500
515
  # Returns a new relation, which is the result of filtering the current relation
501
516
  # according to the conditions in the arguments.
@@ -616,20 +631,18 @@ module ActiveRecord
616
631
  #
617
632
  # If the condition is any blank-ish object, then #where is a no-op and returns
618
633
  # the current relation.
619
- def where(opts = :chain, *rest)
620
- if :chain == opts
634
+ def where(*args)
635
+ if args.empty?
621
636
  WhereChain.new(spawn)
622
- elsif opts.blank?
637
+ elsif args.length == 1 && args.first.blank?
623
638
  self
624
639
  else
625
- spawn.where!(opts, *rest)
640
+ spawn.where!(*args)
626
641
  end
627
642
  end
628
643
 
629
644
  def where!(opts, *rest) # :nodoc:
630
- opts = sanitize_forbidden_attributes(opts)
631
- references!(PredicateBuilder.references(opts)) if Hash === opts
632
- self.where_clause += where_clause_factory.build(opts, rest)
645
+ self.where_clause += build_where_clause(opts, rest)
633
646
  self
634
647
  end
635
648
 
@@ -647,7 +660,44 @@ module ActiveRecord
647
660
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
648
661
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
649
662
  def rewhere(conditions)
650
- 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
651
701
  end
652
702
 
653
703
  # Returns a new relation, which is the logical union of this relation and the one passed as an
@@ -655,28 +705,29 @@ module ActiveRecord
655
705
  #
656
706
  # The two relations must be structurally compatible: they must be scoping the same model, and
657
707
  # they must differ only by #where (if no #group has been defined) or #having (if a #group is
658
- # present). Neither relation may have a #limit, #offset, or #distinct set.
708
+ # present).
659
709
  #
660
710
  # Post.where("id = 1").or(Post.where("author_id = 3"))
661
- # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
711
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
662
712
  #
663
713
  def or(other)
664
- unless other.is_a? Relation
714
+ if other.is_a?(Relation)
715
+ spawn.or!(other)
716
+ else
665
717
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
666
718
  end
667
-
668
- spawn.or!(other)
669
719
  end
670
720
 
671
721
  def or!(other) # :nodoc:
672
- incompatible_values = structurally_incompatible_values_for_or(other)
722
+ incompatible_values = structurally_incompatible_values_for(other)
673
723
 
674
724
  unless incompatible_values.empty?
675
725
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
676
726
  end
677
727
 
678
728
  self.where_clause = self.where_clause.or(other.where_clause)
679
- self.having_clause = self.having_clause.or(other.having_clause)
729
+ self.having_clause = having_clause.or(other.having_clause)
730
+ self.references_values |= other.references_values
680
731
 
681
732
  self
682
733
  end
@@ -690,10 +741,7 @@ module ActiveRecord
690
741
  end
691
742
 
692
743
  def having!(opts, *rest) # :nodoc:
693
- opts = sanitize_forbidden_attributes(opts)
694
- references!(PredicateBuilder.references(opts)) if Hash === opts
695
-
696
- self.having_clause += having_clause_factory.build(opts, rest)
744
+ self.having_clause += build_having_clause(opts, rest)
697
745
  self
698
746
  end
699
747
 
@@ -707,13 +755,6 @@ module ActiveRecord
707
755
  end
708
756
 
709
757
  def limit!(value) # :nodoc:
710
- if string_containing_comma?(value)
711
- # Remove `string_containing_comma?` when removing this deprecation
712
- ActiveSupport::Deprecation.warn(<<-WARNING.squish)
713
- Passing a string to limit in the form "1,2" is deprecated and will be
714
- removed in Rails 5.1. Please call `offset` explicitly instead.
715
- WARNING
716
- end
717
758
  self.limit_value = value
718
759
  self
719
760
  end
@@ -780,7 +821,7 @@ module ActiveRecord
780
821
  # end
781
822
  #
782
823
  def none
783
- where("1=0").extending!(NullRelation)
824
+ spawn.none!
784
825
  end
785
826
 
786
827
  def none! # :nodoc:
@@ -802,6 +843,21 @@ module ActiveRecord
802
843
  self
803
844
  end
804
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
+
805
861
  # Sets attributes to be used when creating new records from a
806
862
  # relation object.
807
863
  #
@@ -824,7 +880,7 @@ module ActiveRecord
824
880
  value = sanitize_forbidden_attributes(value)
825
881
  self.create_with_value = create_with_value.merge(value)
826
882
  else
827
- self.create_with_value = {}
883
+ self.create_with_value = FROZEN_EMPTY_HASH
828
884
  end
829
885
 
830
886
  self
@@ -865,16 +921,12 @@ module ActiveRecord
865
921
  def distinct(value = true)
866
922
  spawn.distinct!(value)
867
923
  end
868
- alias uniq distinct
869
- deprecate uniq: :distinct
870
924
 
871
925
  # Like #distinct, but modifies relation in place.
872
926
  def distinct!(value = true) # :nodoc:
873
927
  self.distinct_value = value
874
928
  self
875
929
  end
876
- alias uniq! distinct!
877
- deprecate uniq!: :distinct!
878
930
 
879
931
  # Used to extend a scope with additional methods, either through
880
932
  # a module or through a block provided.
@@ -930,6 +982,27 @@ module ActiveRecord
930
982
  self
931
983
  end
932
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
+
933
1006
  # Reverse the existing order clause on the relation.
934
1007
  #
935
1008
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -938,305 +1011,512 @@ module ActiveRecord
938
1011
  end
939
1012
 
940
1013
  def reverse_order! # :nodoc:
941
- orders = order_values.uniq
942
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
943
1015
  self.order_values = reverse_sql_order(orders)
944
1016
  self
945
1017
  end
946
1018
 
947
- # Returns the Arel object associated with the relation.
948
- def arel # :nodoc:
949
- @arel ||= build_arel
1019
+ def skip_query_cache!(value = true) # :nodoc:
1020
+ self.skip_query_cache_value = value
1021
+ self
950
1022
  end
951
1023
 
952
- private
1024
+ def skip_preloading! # :nodoc:
1025
+ self.skip_preloading_value = true
1026
+ self
1027
+ end
953
1028
 
954
- def assert_mutability!
955
- raise ImmutableRelation if @loaded
956
- raise ImmutableRelation if defined?(@arel) && @arel
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)
957
1041
  end
958
1042
 
959
- def build_arel
960
- arel = Arel::SelectManager.new(table)
1043
+ # Like #annotate, but modifies relation in place.
1044
+ def annotate!(*args) # :nodoc:
1045
+ self.annotate_values += args
1046
+ self
1047
+ end
961
1048
 
962
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
963
- build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty?
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
964
1056
 
965
- arel.where(where_clause.ast) unless where_clause.empty?
966
- arel.having(having_clause.ast) unless having_clause.empty?
967
- if limit_value
968
- if string_containing_comma?(limit_value)
969
- arel.take(connection.sanitize_limit(limit_value))
970
- else
971
- arel.take(Arel::Nodes::BindParam.new)
1057
+ # Returns the Arel object associated with the relation.
1058
+ def arel(aliases = nil) # :nodoc:
1059
+ @arel ||= build_arel(aliases)
1060
+ end
1061
+
1062
+ def construct_join_dependency(associations, join_type) # :nodoc:
1063
+ ActiveRecord::Associations::JoinDependency.new(
1064
+ klass, table, associations, join_type
1065
+ )
1066
+ end
1067
+
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?
972
1074
  end
973
1075
  end
974
- arel.skip(Arel::Nodes::BindParam.new) if offset_value
975
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
976
-
977
- build_order(arel)
978
1076
 
979
- build_select(arel)
1077
+ def build_where_clause(opts, rest = []) # :nodoc:
1078
+ opts = sanitize_forbidden_attributes(opts)
980
1079
 
981
- arel.distinct(distinct_value)
982
- arel.from(build_from) unless from_clause.empty?
983
- arel.lock(lock_value) if lock_value
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?
984
1090
 
985
- arel
986
- end
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
987
1099
 
988
- def symbol_unscoping(scope)
989
- if !VALID_UNSCOPING_VALUES.include?(scope)
990
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
1100
+ Relation::WhereClause.new(parts)
991
1101
  end
1102
+ alias :build_having_clause :build_where_clause
992
1103
 
993
- clause_method = Relation::CLAUSE_METHODS.include?(scope)
994
- multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope)
995
- if clause_method
996
- unscope_code = "#{scope}_clause="
997
- else
998
- unscope_code = "#{scope}_value#{'s' if multi_val_method}="
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
999
1110
  end
1000
1111
 
1001
- case scope
1002
- when :order
1003
- result = []
1004
- else
1005
- result = [] if multi_val_method
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
1006
1118
  end
1007
1119
 
1008
- self.send(unscope_code, result)
1009
- end
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?
1010
1124
 
1011
- def association_for_table(table_name)
1012
- table_name = table_name.to_s
1013
- @klass._reflect_on_association(table_name) ||
1014
- @klass._reflect_on_association(table_name.singularize)
1015
- end
1125
+ join_dependencies = []
1126
+ join_dependencies.unshift construct_join_dependency(
1127
+ select_association_list(associations, join_dependencies), nil
1128
+ )
1129
+ end
1016
1130
 
1017
- def build_from
1018
- opts = from_clause.value
1019
- name = from_clause.name
1020
- case opts
1021
- when Relation
1022
- name ||= 'subquery'
1023
- opts.arel.as(name.to_s)
1024
- else
1025
- opts
1131
+ def assert_mutability!
1132
+ raise ImmutableRelation if @loaded
1133
+ raise ImmutableRelation if defined?(@arel) && @arel
1026
1134
  end
1027
- end
1028
1135
 
1029
- def build_left_outer_joins(manager, outer_joins)
1030
- buckets = outer_joins.group_by do |join|
1031
- case join
1032
- when Hash, Symbol, Array
1033
- :association_join
1034
- else
1035
- raise ArgumentError, 'only Hash, Symbol and Array are allowed'
1136
+ def build_arel(aliases)
1137
+ arel = Arel::SelectManager.new(table)
1138
+
1139
+ build_joins(arel.join_sources, aliases)
1140
+
1141
+ arel.where(where_clause.ast) unless where_clause.empty?
1142
+ arel.having(having_clause.ast) unless having_clause.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?
1146
+
1147
+ build_order(arel)
1148
+ build_select(arel)
1149
+
1150
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1151
+ arel.distinct(distinct_value)
1152
+ arel.from(build_from) unless from_clause.empty?
1153
+ arel.lock(lock_value) if lock_value
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)
1036
1167
  end
1168
+
1169
+ arel
1037
1170
  end
1038
1171
 
1039
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
1040
- end
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
1041
1176
 
1042
- def build_joins(manager, joins)
1043
- buckets = joins.group_by do |join|
1044
- case join
1045
- when String
1046
- :string_join
1047
- when Hash, Symbol, Array
1048
- :association_join
1049
- when ActiveRecord::Associations::JoinDependency
1050
- :stashed_join
1051
- when Arel::Nodes::Join
1052
- :join_node
1177
+ def build_from
1178
+ opts = from_clause.value
1179
+ name = from_clause.name
1180
+ case opts
1181
+ when Relation
1182
+ if opts.eager_loading?
1183
+ opts = opts.send(:apply_join_dependency)
1184
+ end
1185
+ name ||= "subquery"
1186
+ opts.arel.as(name.to_s)
1053
1187
  else
1054
- raise 'unknown class: %s' % join.class.name
1188
+ opts
1055
1189
  end
1056
1190
  end
1057
1191
 
1058
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin)
1059
- end
1192
+ def select_association_list(associations, stashed_joins = nil)
1193
+ result = []
1194
+ associations.each do |association|
1195
+ case association
1196
+ when Hash, Symbol, Array
1197
+ result << association
1198
+ when ActiveRecord::Associations::JoinDependency
1199
+ stashed_joins&.<< association
1200
+ else
1201
+ yield association if block_given?
1202
+ end
1203
+ end
1204
+ result
1205
+ end
1060
1206
 
1061
- def build_join_query(manager, buckets, join_type)
1062
- buckets.default = []
1207
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1208
+ end
1063
1209
 
1064
- association_joins = buckets[:association_join]
1065
- stashed_association_joins = buckets[:stashed_join]
1066
- join_nodes = buckets[:join_node].uniq
1067
- string_joins = buckets[:string_join].map(&:strip).uniq
1210
+ def build_join_buckets
1211
+ buckets = Hash.new { |h, k| h[k] = [] }
1068
1212
 
1069
- join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
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
1070
1218
 
1071
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1072
- @klass,
1073
- association_joins,
1074
- join_list
1075
- )
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
1076
1245
 
1077
- join_infos = join_dependency.join_constraints stashed_association_joins, join_type
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
1249
+ else
1250
+ raise "unknown class: %s" % join.class.name
1251
+ end
1252
+ end
1078
1253
 
1079
- join_infos.each do |info|
1080
- info.joins.each { |join| manager.from(join) }
1081
- manager.bind_values.concat info.binds
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
1082
1258
  end
1083
1259
 
1084
- manager.join_sources.concat(join_list)
1260
+ def build_joins(join_sources, aliases = nil)
1261
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1085
1262
 
1086
- manager
1087
- end
1263
+ buckets, join_type = build_join_buckets
1088
1264
 
1089
- def convert_join_strings_to_ast(table, joins)
1090
- joins
1091
- .flatten
1092
- .reject(&:blank?)
1093
- .map { |join| table.create_string_join(Arel.sql(join)) }
1094
- end
1265
+ association_joins = buckets[:association_join]
1266
+ stashed_joins = buckets[:stashed_join]
1267
+ leading_joins = buckets[:leading_join]
1268
+ join_nodes = buckets[:join_node]
1095
1269
 
1096
- def build_select(arel)
1097
- if select_values.any?
1098
- arel.project(*arel_columns(select_values.uniq))
1099
- else
1100
- arel.project(@klass.arel_table[Arel.star])
1270
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1271
+
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
1277
+
1278
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1279
+ join_sources
1101
1280
  end
1102
- end
1103
1281
 
1104
- def arel_columns(columns)
1105
- columns.map do |field|
1106
- if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
1107
- arel_attribute(field)
1108
- elsif Symbol === field
1109
- connection.quote_table_name(field.to_s)
1282
+ def build_select(arel)
1283
+ if select_values.any?
1284
+ arel.project(*arel_columns(select_values))
1285
+ elsif klass.ignored_columns.any?
1286
+ arel.project(*klass.column_names.map { |field| table[field] })
1110
1287
  else
1111
- field
1288
+ arel.project(table[Arel.star])
1112
1289
  end
1113
1290
  end
1114
- end
1115
1291
 
1116
- def reverse_sql_order(order_query)
1117
- if order_query.empty?
1118
- return [arel_attribute(primary_key).desc] if primary_key
1119
- raise IrreversibleOrderError,
1120
- "Relation has no current order and table has no primary key to be used as default order"
1292
+ def arel_columns(columns)
1293
+ columns.flat_map do |field|
1294
+ case field
1295
+ when Symbol
1296
+ arel_column(field.to_s) do |attr_name|
1297
+ connection.quote_table_name(attr_name)
1298
+ end
1299
+ when String
1300
+ arel_column(field, &:itself)
1301
+ when Proc
1302
+ field.call
1303
+ else
1304
+ field
1305
+ end
1306
+ end
1121
1307
  end
1122
1308
 
1123
- order_query.flat_map do |o|
1124
- case o
1125
- when Arel::Attribute
1126
- o.desc
1127
- when Arel::Nodes::Ordering
1128
- o.reverse
1129
- when String
1130
- if does_not_support_reverse?(o)
1131
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1132
- end
1133
- o.split(',').map! do |s|
1134
- s.strip!
1135
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
1309
+ def arel_column(field)
1310
+ field = klass.attribute_aliases[field] || field
1311
+ from = from_clause.name || from_clause.value
1312
+
1313
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
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)
1136
1319
  end
1137
1320
  else
1138
- o
1321
+ yield field
1139
1322
  end
1140
1323
  end
1141
- end
1142
1324
 
1143
- def does_not_support_reverse?(order)
1144
- # Uses SQL function with multiple arguments.
1145
- (order.include?(',') && order.split(',').find { |section| section.count('(') != section.count(')')}) ||
1146
- # Uses "nulls first" like construction.
1147
- order =~ /nulls (first|last)\Z/i
1148
- end
1325
+ def table_name_matches?(from)
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)
1329
+ end
1149
1330
 
1150
- def build_order(arel)
1151
- orders = order_values.uniq
1152
- orders.reject!(&:blank?)
1331
+ def reverse_sql_order(order_query)
1332
+ if order_query.empty?
1333
+ return [table[primary_key].desc] if primary_key
1334
+ raise IrreversibleOrderError,
1335
+ "Relation has no current order and table has no primary key to be used as default order"
1336
+ end
1153
1337
 
1154
- arel.order(*orders) unless orders.empty?
1155
- end
1338
+ order_query.flat_map do |o|
1339
+ case o
1340
+ when Arel::Attribute
1341
+ o.desc
1342
+ when Arel::Nodes::Ordering
1343
+ o.reverse
1344
+ when Arel::Nodes::NodeExpression
1345
+ o.desc
1346
+ when String
1347
+ if does_not_support_reverse?(o)
1348
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1349
+ end
1350
+ o.split(",").map! do |s|
1351
+ s.strip!
1352
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1353
+ end
1354
+ else
1355
+ o
1356
+ end
1357
+ end
1358
+ end
1156
1359
 
1157
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1158
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
1360
+ def does_not_support_reverse?(order)
1361
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
1362
+ # override methods like #count.
1363
+ order = String.new(order) unless order.instance_of?(String)
1159
1364
 
1160
- def validate_order_args(args)
1161
- args.each do |arg|
1162
- next unless arg.is_a?(Hash)
1163
- arg.each do |_key, value|
1164
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
1165
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
1166
- end
1365
+ # Uses SQL function with multiple arguments.
1366
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1367
+ # Uses "nulls first" like construction.
1368
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1167
1369
  end
1168
- end
1169
1370
 
1170
- def preprocess_order_args(order_args)
1171
- order_args.map! do |arg|
1172
- klass.send(:sanitize_sql_for_order, arg)
1371
+ def build_order(arel)
1372
+ orders = order_values.compact_blank
1373
+ arel.order(*orders) unless orders.empty?
1173
1374
  end
1174
- order_args.flatten!
1175
- validate_order_args(order_args)
1176
1375
 
1177
- references = order_args.grep(String)
1178
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1179
- references!(references) if references.any?
1376
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1377
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1180
1378
 
1181
- # if a symbol is given we prepend the quoted table name
1182
- order_args.map! do |arg|
1183
- case arg
1184
- when Symbol
1185
- arel_attribute(arg).asc
1186
- when Hash
1187
- arg.map { |field, dir|
1188
- arel_attribute(field).send(dir.downcase)
1189
- }
1190
- else
1191
- arg
1379
+ def validate_order_args(args)
1380
+ args.each do |arg|
1381
+ next unless arg.is_a?(Hash)
1382
+ arg.each do |_key, value|
1383
+ unless VALID_DIRECTIONS.include?(value)
1384
+ raise ArgumentError,
1385
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1386
+ end
1387
+ end
1192
1388
  end
1193
- end.flatten!
1194
- end
1389
+ end
1195
1390
 
1196
- # Checks to make sure that the arguments are not blank. Note that if some
1197
- # blank-like object were initially passed into the query method, then this
1198
- # method will not raise an error.
1199
- #
1200
- # Example:
1201
- #
1202
- # Post.references() # raises an error
1203
- # Post.references([]) # does not raise an error
1204
- #
1205
- # This particular method should be called with a method_name and the args
1206
- # passed into that method as an input. For example:
1207
- #
1208
- # def references(*args)
1209
- # check_if_method_has_arguments!("references", args)
1210
- # ...
1211
- # end
1212
- def check_if_method_has_arguments!(method_name, args)
1213
- if args.blank?
1214
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1391
+ def preprocess_order_args(order_args)
1392
+ @klass.disallow_raw_sql!(
1393
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1394
+ permit: connection.column_name_with_order_matcher
1395
+ )
1396
+
1397
+ validate_order_args(order_args)
1398
+
1399
+ references = column_references(order_args)
1400
+ self.references_values |= references unless references.empty?
1401
+
1402
+ # if a symbol is given we prepend the quoted table name
1403
+ order_args.map! do |arg|
1404
+ case arg
1405
+ when Symbol
1406
+ order_column(arg.to_s).asc
1407
+ when Hash
1408
+ arg.map { |field, dir|
1409
+ case field
1410
+ when Arel::Nodes::SqlLiteral
1411
+ field.public_send(dir.downcase)
1412
+ else
1413
+ order_column(field.to_s).public_send(dir.downcase)
1414
+ end
1415
+ }
1416
+ else
1417
+ arg
1418
+ end
1419
+ end.flatten!
1215
1420
  end
1216
- end
1217
1421
 
1218
- def structurally_incompatible_values_for_or(other)
1219
- Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
1220
- (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
1221
- (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
1222
- end
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
1223
1429
 
1224
- def new_where_clause
1225
- Relation::WhereClause.empty
1226
- end
1227
- alias new_having_clause new_where_clause
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
1228
1435
 
1229
- def where_clause_factory
1230
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1231
- end
1232
- alias having_clause_factory where_clause_factory
1436
+ def order_column(field)
1437
+ arel_column(field) do |attr_name|
1438
+ if attr_name == "count" && !group_values.empty?
1439
+ table[attr_name]
1440
+ else
1441
+ Arel.sql(connection.quote_table_name(attr_name))
1442
+ end
1443
+ end
1444
+ end
1233
1445
 
1234
- def new_from_clause
1235
- Relation::FromClause.empty
1236
- end
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
+
1470
+ # Checks to make sure that the arguments are not blank. Note that if some
1471
+ # blank-like object were initially passed into the query method, then this
1472
+ # method will not raise an error.
1473
+ #
1474
+ # Example:
1475
+ #
1476
+ # Post.references() # raises an error
1477
+ # Post.references([]) # does not raise an error
1478
+ #
1479
+ # This particular method should be called with a method_name and the args
1480
+ # passed into that method as an input. For example:
1481
+ #
1482
+ # def references(*args)
1483
+ # check_if_method_has_arguments!("references", args)
1484
+ # ...
1485
+ # end
1486
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1487
+ if args.blank?
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!
1494
+ end
1495
+ end
1496
+
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
1512
+ end
1513
+ end
1514
+ end
1237
1515
 
1238
- def string_containing_comma?(value)
1239
- ::String === value && value.include?(",")
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:
1240
1520
  end
1241
1521
  end
1242
1522
  end