activerecord 4.2.9 → 6.1.4.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 (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +964 -1382
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +15 -14
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +266 -251
  8. data/lib/active_record/association_relation.rb +40 -15
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +162 -69
  11. data/lib/active_record/associations/association_scope.rb +105 -130
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -65
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  14. data/lib/active_record/associations/builder/association.rb +57 -43
  15. data/lib/active_record/associations/builder/belongs_to.rb +74 -57
  16. data/lib/active_record/associations/builder/collection_association.rb +15 -37
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
  18. data/lib/active_record/associations/builder/has_many.rb +13 -5
  19. data/lib/active_record/associations/builder/has_one.rb +44 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +148 -287
  22. data/lib/active_record/associations/collection_proxy.rb +252 -150
  23. data/lib/active_record/associations/foreign_association.rb +23 -1
  24. data/lib/active_record/associations/has_many_association.rb +56 -98
  25. data/lib/active_record/associations/has_many_through_association.rb +68 -89
  26. data/lib/active_record/associations/has_one_association.rb +73 -47
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +174 -169
  32. data/lib/active_record/associations/preloader/association.rb +108 -115
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +97 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -39
  36. data/lib/active_record/associations/through_association.rb +39 -19
  37. data/lib/active_record/associations.rb +1845 -1598
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -148
  41. data/lib/active_record/attribute_methods/primary_key.rb +93 -83
  42. data/lib/active_record/attribute_methods/query.rb +8 -10
  43. data/lib/active_record/attribute_methods/read.rb +19 -79
  44. data/lib/active_record/attribute_methods/serialization.rb +49 -24
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
  46. data/lib/active_record/attribute_methods/write.rb +24 -55
  47. data/lib/active_record/attribute_methods.rb +149 -154
  48. data/lib/active_record/attributes.rb +234 -78
  49. data/lib/active_record/autosave_association.rb +133 -60
  50. data/lib/active_record/base.rb +46 -46
  51. data/lib/active_record/callbacks.rb +234 -79
  52. data/lib/active_record/coders/json.rb +3 -1
  53. data/lib/active_record/coders/yaml_column.rb +34 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
  67. data/lib/active_record/connection_adapters/column.rb +67 -40
  68. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  69. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
  72. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
  80. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  81. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  101. data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  109. data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
  110. data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
  111. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  112. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  114. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
  116. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  117. data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
  118. data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
  119. data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
  120. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  121. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  122. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  123. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -373
  129. data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
  130. data/lib/active_record/connection_adapters.rb +52 -0
  131. data/lib/active_record/connection_handling.rb +314 -41
  132. data/lib/active_record/core.rb +458 -241
  133. data/lib/active_record/counter_cache.rb +70 -49
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  135. data/lib/active_record/database_configurations/database_config.rb +80 -0
  136. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  137. data/lib/active_record/database_configurations/url_config.rb +53 -0
  138. data/lib/active_record/database_configurations.rb +272 -0
  139. data/lib/active_record/delegated_type.rb +209 -0
  140. data/lib/active_record/destroy_association_async_job.rb +36 -0
  141. data/lib/active_record/dynamic_matchers.rb +87 -106
  142. data/lib/active_record/enum.rb +211 -92
  143. data/lib/active_record/errors.rb +224 -54
  144. data/lib/active_record/explain.rb +27 -11
  145. data/lib/active_record/explain_registry.rb +4 -2
  146. data/lib/active_record/explain_subscriber.rb +10 -5
  147. data/lib/active_record/fixture_set/file.rb +33 -14
  148. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  149. data/lib/active_record/fixture_set/render_context.rb +17 -0
  150. data/lib/active_record/fixture_set/table_row.rb +152 -0
  151. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  152. data/lib/active_record/fixtures.rb +275 -500
  153. data/lib/active_record/gem_version.rb +6 -4
  154. data/lib/active_record/inheritance.rb +175 -110
  155. data/lib/active_record/insert_all.rb +212 -0
  156. data/lib/active_record/integration.rb +121 -29
  157. data/lib/active_record/internal_metadata.rb +62 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +27 -5
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +98 -92
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +93 -31
  163. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector.rb +77 -0
  166. data/lib/active_record/migration/command_recorder.rb +185 -90
  167. data/lib/active_record/migration/compatibility.rb +295 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +673 -325
  170. data/lib/active_record/model_schema.rb +418 -113
  171. data/lib/active_record/nested_attributes.rb +263 -224
  172. data/lib/active_record/no_touching.rb +15 -2
  173. data/lib/active_record/null_relation.rb +24 -38
  174. data/lib/active_record/persistence.rb +572 -136
  175. data/lib/active_record/query_cache.rb +29 -23
  176. data/lib/active_record/querying.rb +50 -31
  177. data/lib/active_record/railtie.rb +170 -51
  178. data/lib/active_record/railties/console_sandbox.rb +3 -3
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +523 -199
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +454 -291
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
  184. data/lib/active_record/relation/batches.rb +217 -59
  185. data/lib/active_record/relation/calculations.rb +324 -249
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +316 -242
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +95 -103
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  192. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  193. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +57 -0
  194. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  195. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  196. data/lib/active_record/relation/predicate_builder.rb +136 -122
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -413
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +18 -20
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -343
  203. data/lib/active_record/result.rb +91 -47
  204. data/lib/active_record/runtime_registry.rb +6 -4
  205. data/lib/active_record/sanitization.rb +134 -122
  206. data/lib/active_record/schema.rb +21 -24
  207. data/lib/active_record/schema_dumper.rb +141 -92
  208. data/lib/active_record/schema_migration.rb +24 -23
  209. data/lib/active_record/scoping/default.rb +96 -83
  210. data/lib/active_record/scoping/named.rb +78 -36
  211. data/lib/active_record/scoping.rb +45 -27
  212. data/lib/active_record/secure_token.rb +48 -0
  213. data/lib/active_record/serialization.rb +8 -6
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +89 -36
  216. data/lib/active_record/store.rb +128 -43
  217. data/lib/active_record/suppressor.rb +61 -0
  218. data/lib/active_record/table_metadata.rb +81 -0
  219. data/lib/active_record/tasks/database_tasks.rb +364 -130
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +287 -0
  225. data/lib/active_record/timestamp.rb +86 -43
  226. data/lib/active_record/touch_later.rb +65 -0
  227. data/lib/active_record/transactions.rb +182 -163
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type/adapter_specific_registry.rb +126 -0
  230. data/lib/active_record/type/date.rb +4 -45
  231. data/lib/active_record/type/date_time.rb +4 -49
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  234. data/lib/active_record/type/internal/timezone.rb +17 -0
  235. data/lib/active_record/type/json.rb +30 -0
  236. data/lib/active_record/type/serialized.rb +27 -15
  237. data/lib/active_record/type/text.rb +2 -2
  238. data/lib/active_record/type/time.rb +21 -16
  239. data/lib/active_record/type/type_map.rb +16 -19
  240. data/lib/active_record/type/unsigned_integer.rb +9 -8
  241. data/lib/active_record/type.rb +84 -23
  242. data/lib/active_record/type_caster/connection.rb +33 -0
  243. data/lib/active_record/type_caster/map.rb +23 -0
  244. data/lib/active_record/type_caster.rb +9 -0
  245. data/lib/active_record/validations/absence.rb +25 -0
  246. data/lib/active_record/validations/associated.rb +12 -4
  247. data/lib/active_record/validations/length.rb +26 -0
  248. data/lib/active_record/validations/numericality.rb +35 -0
  249. data/lib/active_record/validations/presence.rb +14 -13
  250. data/lib/active_record/validations/uniqueness.rb +63 -56
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +42 -29
  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/and.rb +32 -0
  269. data/lib/arel/nodes/ascending.rb +23 -0
  270. data/lib/arel/nodes/binary.rb +126 -0
  271. data/lib/arel/nodes/bind_param.rb +44 -0
  272. data/lib/arel/nodes/case.rb +55 -0
  273. data/lib/arel/nodes/casted.rb +62 -0
  274. data/lib/arel/nodes/comment.rb +29 -0
  275. data/lib/arel/nodes/count.rb +12 -0
  276. data/lib/arel/nodes/delete_statement.rb +45 -0
  277. data/lib/arel/nodes/descending.rb +23 -0
  278. data/lib/arel/nodes/equality.rb +15 -0
  279. data/lib/arel/nodes/extract.rb +24 -0
  280. data/lib/arel/nodes/false.rb +16 -0
  281. data/lib/arel/nodes/full_outer_join.rb +8 -0
  282. data/lib/arel/nodes/function.rb +44 -0
  283. data/lib/arel/nodes/grouping.rb +11 -0
  284. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  285. data/lib/arel/nodes/in.rb +15 -0
  286. data/lib/arel/nodes/infix_operation.rb +92 -0
  287. data/lib/arel/nodes/inner_join.rb +8 -0
  288. data/lib/arel/nodes/insert_statement.rb +37 -0
  289. data/lib/arel/nodes/join_source.rb +20 -0
  290. data/lib/arel/nodes/matches.rb +18 -0
  291. data/lib/arel/nodes/named_function.rb +23 -0
  292. data/lib/arel/nodes/node.rb +51 -0
  293. data/lib/arel/nodes/node_expression.rb +13 -0
  294. data/lib/arel/nodes/ordering.rb +27 -0
  295. data/lib/arel/nodes/outer_join.rb +8 -0
  296. data/lib/arel/nodes/over.rb +15 -0
  297. data/lib/arel/nodes/regexp.rb +16 -0
  298. data/lib/arel/nodes/right_outer_join.rb +8 -0
  299. data/lib/arel/nodes/select_core.rb +67 -0
  300. data/lib/arel/nodes/select_statement.rb +41 -0
  301. data/lib/arel/nodes/sql_literal.rb +19 -0
  302. data/lib/arel/nodes/string_join.rb +11 -0
  303. data/lib/arel/nodes/table_alias.rb +31 -0
  304. data/lib/arel/nodes/terminal.rb +16 -0
  305. data/lib/arel/nodes/true.rb +16 -0
  306. data/lib/arel/nodes/unary.rb +44 -0
  307. data/lib/arel/nodes/unary_operation.rb +20 -0
  308. data/lib/arel/nodes/unqualified_column.rb +22 -0
  309. data/lib/arel/nodes/update_statement.rb +41 -0
  310. data/lib/arel/nodes/values_list.rb +9 -0
  311. data/lib/arel/nodes/window.rb +126 -0
  312. data/lib/arel/nodes/with.rb +11 -0
  313. data/lib/arel/nodes.rb +70 -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/dot.rb +308 -0
  321. data/lib/arel/visitors/mysql.rb +93 -0
  322. data/lib/arel/visitors/postgresql.rb +120 -0
  323. data/lib/arel/visitors/sqlite.rb +38 -0
  324. data/lib/arel/visitors/to_sql.rb +899 -0
  325. data/lib/arel/visitors/visitor.rb +45 -0
  326. data/lib/arel/visitors.rb +13 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/arel.rb +54 -0
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  331. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
  332. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
  333. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
  334. data/lib/rails/generators/active_record/migration.rb +35 -1
  335. data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
  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.tt +22 -0
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. data/lib/rails/generators/active_record.rb +7 -5
  340. metadata +172 -65
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_decorators.rb +0 -66
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/attribute_set.rb +0 -81
  352. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  353. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  354. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  355. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  356. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  357. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  358. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  359. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  360. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  361. data/lib/active_record/type/big_integer.rb +0 -13
  362. data/lib/active_record/type/binary.rb +0 -50
  363. data/lib/active_record/type/boolean.rb +0 -31
  364. data/lib/active_record/type/decimal.rb +0 -64
  365. data/lib/active_record/type/decorator.rb +0 -14
  366. data/lib/active_record/type/float.rb +0 -19
  367. data/lib/active_record/type/integer.rb +0 -59
  368. data/lib/active_record/type/mutable.rb +0 -16
  369. data/lib/active_record/type/numeric.rb +0 -36
  370. data/lib/active_record/type/string.rb +0 -40
  371. data/lib/active_record/type/time_value.rb +0 -38
  372. data/lib/active_record/type/value.rb +0 -110
  373. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  374. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,6 +1,10 @@
1
- require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_model/forbidden_attributes_protection'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/from_clause"
4
+ require "active_record/relation/query_attribute"
5
+ require "active_record/relation/where_clause"
6
+ require "active_model/forbidden_attributes_protection"
7
+ require "active_support/core_ext/array/wrap"
4
8
 
5
9
  module ActiveRecord
6
10
  module QueryMethods
@@ -18,7 +22,7 @@ module ActiveRecord
18
22
  # Returns a new relation expressing WHERE + NOT condition according to
19
23
  # the conditions in the arguments.
20
24
  #
21
- # +not+ accepts conditions as a string, array, or hash. See #where for
25
+ # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
22
26
  # more details on each format.
23
27
  #
24
28
  # User.where.not("name = 'Jon'")
@@ -37,75 +41,71 @@ module ActiveRecord
37
41
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
38
42
  #
39
43
  # User.where.not(name: "Jon", role: "admin")
40
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
44
+ # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
41
45
  def not(opts, *rest)
42
- where_value = @scope.send(:build_where, opts, rest).map do |rel|
43
- case rel
44
- when NilClass
45
- raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
46
- when Arel::Nodes::In
47
- Arel::Nodes::NotIn.new(rel.left, rel.right)
48
- when Arel::Nodes::Equality
49
- Arel::Nodes::NotEqual.new(rel.left, rel.right)
50
- when String
51
- Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
52
- else
53
- Arel::Nodes::Not.new(rel)
54
- end
46
+ where_clause = @scope.send(:build_where_clause, opts, rest)
47
+
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)
55
77
  end
56
78
 
57
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
58
- @scope.where_values += where_value
59
79
  @scope
60
80
  end
61
81
  end
62
82
 
63
- Relation::MULTI_VALUE_METHODS.each do |name|
64
- class_eval <<-CODE, __FILE__, __LINE__ + 1
65
- def #{name}_values # def select_values
66
- @values[:#{name}] || [] # @values[:select] || []
67
- end # end
68
- #
69
- def #{name}_values=(values) # def select_values=(values)
70
- raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
71
- check_cached_relation
72
- @values[:#{name}] = values # @values[:select] = values
73
- end # end
74
- CODE
75
- end
83
+ FROZEN_EMPTY_ARRAY = [].freeze
84
+ FROZEN_EMPTY_HASH = {}.freeze
76
85
 
77
- (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
78
- class_eval <<-CODE, __FILE__, __LINE__ + 1
79
- def #{name}_value # def readonly_value
80
- @values[:#{name}] # @values[:readonly]
81
- end # end
82
- CODE
83
- 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
84
96
 
85
- Relation::SINGLE_VALUE_METHODS.each do |name|
86
97
  class_eval <<-CODE, __FILE__, __LINE__ + 1
87
- def #{name}_value=(value) # def readonly_value=(value)
88
- raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
89
- check_cached_relation
90
- @values[:#{name}] = value # @values[:readonly] = value
91
- 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
92
106
  CODE
93
107
  end
94
108
 
95
- def check_cached_relation # :nodoc:
96
- if defined?(@arel) && @arel
97
- @arel = nil
98
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
99
- Modifying already cached Relation. The cache will be reset. Use a
100
- cloned Relation to prevent this warning.
101
- MSG
102
- end
103
- end
104
-
105
- def create_with_value # :nodoc:
106
- @values[:create_with] || {}
107
- end
108
-
109
109
  alias extensions extending_values
110
110
 
111
111
  # Specify relationships to be included in the result set. For
@@ -118,7 +118,7 @@ module ActiveRecord
118
118
  #
119
119
  # allows you to access the +address+ attribute of the +User+ model without
120
120
  # firing an additional query. This will often result in a
121
- # performance improvement over a simple +join+.
121
+ # performance improvement over a simple join.
122
122
  #
123
123
  # You can also specify multiple relationships, like this:
124
124
  #
@@ -130,7 +130,7 @@ module ActiveRecord
130
130
  #
131
131
  # === conditions
132
132
  #
133
- # 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
134
134
  # to explicitly reference them. For example:
135
135
  #
136
136
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -139,17 +139,20 @@ module ActiveRecord
139
139
  #
140
140
  # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
141
141
  #
142
- # Note that +includes+ works with association names while +references+ needs
142
+ # Note that #includes works with association names while #references needs
143
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' })
144
150
  def includes(*args)
145
151
  check_if_method_has_arguments!(:includes, args)
146
152
  spawn.includes!(*args)
147
153
  end
148
154
 
149
155
  def includes!(*args) # :nodoc:
150
- args.reject!(&:blank?)
151
- args.flatten!
152
-
153
156
  self.includes_values |= args
154
157
  self
155
158
  end
@@ -157,64 +160,75 @@ module ActiveRecord
157
160
  # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
158
161
  #
159
162
  # User.eager_load(:posts)
160
- # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
161
- # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
162
- # "users"."id"
163
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
164
+ # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
165
+ # # "users"."id"
163
166
  def eager_load(*args)
164
167
  check_if_method_has_arguments!(:eager_load, args)
165
168
  spawn.eager_load!(*args)
166
169
  end
167
170
 
168
171
  def eager_load!(*args) # :nodoc:
169
- self.eager_load_values += args
172
+ self.eager_load_values |= args
170
173
  self
171
174
  end
172
175
 
173
- # Allows preloading of +args+, in the same way that +includes+ does:
176
+ # Allows preloading of +args+, in the same way that #includes does:
174
177
  #
175
178
  # User.preload(:posts)
176
- # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
179
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
177
180
  def preload(*args)
178
181
  check_if_method_has_arguments!(:preload, args)
179
182
  spawn.preload!(*args)
180
183
  end
181
184
 
182
185
  def preload!(*args) # :nodoc:
183
- self.preload_values += args
186
+ self.preload_values |= args
184
187
  self
185
188
  end
186
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
+
187
203
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
188
204
  # and should therefore be JOINed in any query rather than loaded separately.
189
- # This method only works in conjunction with +includes+.
205
+ # This method only works in conjunction with #includes.
190
206
  # See #includes for more details.
191
207
  #
192
208
  # User.includes(:posts).where("posts.name = 'foo'")
193
- # # => Doesn't JOIN the posts table, resulting in an error.
209
+ # # Doesn't JOIN the posts table, resulting in an error.
194
210
  #
195
211
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
196
- # # => Query now knows the string references posts, so adds a JOIN
212
+ # # Query now knows the string references posts, so adds a JOIN
197
213
  def references(*table_names)
198
214
  check_if_method_has_arguments!(:references, table_names)
199
215
  spawn.references!(*table_names)
200
216
  end
201
217
 
202
218
  def references!(*table_names) # :nodoc:
203
- table_names.flatten!
204
- table_names.map!(&:to_s)
205
-
206
219
  self.references_values |= table_names
207
220
  self
208
221
  end
209
222
 
210
223
  # Works in two unique ways.
211
224
  #
212
- # 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>.
213
226
  #
214
227
  # Model.all.select { |m| m.field == value }
215
228
  #
216
229
  # This will build an array of objects from the database for the scope,
217
- # 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>.
218
232
  #
219
233
  # Second: Modifies the SELECT statement for the query so that only certain
220
234
  # fields are retrieved:
@@ -242,55 +256,75 @@ module ActiveRecord
242
256
  # # => "value"
243
257
  #
244
258
  # Accessing attributes of an object that do not have fields retrieved by a select
245
- # except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
259
+ # except +id+ will throw ActiveModel::MissingAttributeError:
246
260
  #
247
261
  # Model.select(:field).first.other_field
248
262
  # # => ActiveModel::MissingAttributeError: missing attribute: other_field
249
263
  def select(*fields)
250
264
  if block_given?
251
- to_a.select { |*block_args| yield(*block_args) }
252
- else
253
- raise ArgumentError, 'Call this with at least one field' if fields.empty?
254
- spawn._select!(*fields)
265
+ if fields.any?
266
+ raise ArgumentError, "`select' with block doesn't take arguments."
267
+ end
268
+
269
+ return super()
255
270
  end
271
+
272
+ check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
273
+ spawn._select!(*fields)
256
274
  end
257
275
 
258
276
  def _select!(*fields) # :nodoc:
259
- fields.flatten!
260
- fields.map! do |field|
261
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
262
- end
263
- 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
264
299
  self
265
300
  end
266
301
 
267
302
  # Allows to specify a group attribute:
268
303
  #
269
304
  # User.group(:name)
270
- # => SELECT "users".* FROM "users" GROUP BY name
305
+ # # SELECT "users".* FROM "users" GROUP BY name
271
306
  #
272
307
  # Returns an array with distinct records based on the +group+ attribute:
273
308
  #
274
309
  # User.select([:id, :name])
275
- # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
310
+ # # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
276
311
  #
277
312
  # User.group(:name)
278
- # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
313
+ # # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
279
314
  #
280
315
  # User.group('name AS grouped_name, age')
281
- # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
316
+ # # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
282
317
  #
283
318
  # Passing in an array of attributes to group by is also supported.
319
+ #
284
320
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
285
- # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
321
+ # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
286
322
  def group(*args)
287
323
  check_if_method_has_arguments!(:group, args)
288
324
  spawn.group!(*args)
289
325
  end
290
326
 
291
327
  def group!(*args) # :nodoc:
292
- args.flatten!
293
-
294
328
  self.group_values += args
295
329
  self
296
330
  end
@@ -298,31 +332,33 @@ module ActiveRecord
298
332
  # Allows to specify an order attribute:
299
333
  #
300
334
  # User.order(:name)
301
- # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
335
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
302
336
  #
303
337
  # User.order(email: :desc)
304
- # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
338
+ # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
305
339
  #
306
340
  # User.order(:name, email: :desc)
307
- # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
341
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
308
342
  #
309
343
  # User.order('name')
310
- # => SELECT "users".* FROM "users" ORDER BY name
344
+ # # SELECT "users".* FROM "users" ORDER BY name
311
345
  #
312
346
  # User.order('name DESC')
313
- # => SELECT "users".* FROM "users" ORDER BY name DESC
347
+ # # SELECT "users".* FROM "users" ORDER BY name DESC
314
348
  #
315
349
  # User.order('name DESC, email')
316
- # => SELECT "users".* FROM "users" ORDER BY name DESC, email
350
+ # # SELECT "users".* FROM "users" ORDER BY name DESC, email
317
351
  def order(*args)
318
- check_if_method_has_arguments!(:order, args)
352
+ check_if_method_has_arguments!(:order, args) do
353
+ sanitize_order_arguments(args)
354
+ end
319
355
  spawn.order!(*args)
320
356
  end
321
357
 
358
+ # Same as #order but operates on relation in-place instead of copying.
322
359
  def order!(*args) # :nodoc:
323
- preprocess_order_args(args)
324
-
325
- self.order_values += args
360
+ preprocess_order_args(args) unless args.empty?
361
+ self.order_values |= args
326
362
  self
327
363
  end
328
364
 
@@ -336,21 +372,24 @@ module ActiveRecord
336
372
  #
337
373
  # generates a query with 'ORDER BY id ASC, name ASC'.
338
374
  def reorder(*args)
339
- 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
340
378
  spawn.reorder!(*args)
341
379
  end
342
380
 
381
+ # Same as #reorder but operates on relation in-place instead of copying.
343
382
  def reorder!(*args) # :nodoc:
344
- preprocess_order_args(args)
345
-
383
+ preprocess_order_args(args) unless args.all?(&:blank?)
384
+ args.uniq!
346
385
  self.reordering_value = true
347
386
  self.order_values = args
348
387
  self
349
388
  end
350
389
 
351
390
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
352
- :limit, :offset, :joins, :includes, :from,
353
- :readonly, :having])
391
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
392
+ :includes, :from, :readonly, :having, :optimizer_hints])
354
393
 
355
394
  # Removes an unwanted relation that is already defined on a chain of relations.
356
395
  # This is useful when passing around chains of relations and would like to
@@ -365,15 +404,15 @@ module ActiveRecord
365
404
  # User.order('email DESC').select('id').where(name: "John")
366
405
  # .unscope(:order, :select, :where) == User.all
367
406
  #
368
- # One can additionally pass a hash as an argument to unscope specific :where values.
407
+ # One can additionally pass a hash as an argument to unscope specific +:where+ values.
369
408
  # This is done by passing a hash with a single key-value pair. The key should be
370
- # :where and the value should be the where value to unscope. For example:
409
+ # +:where+ and the value should be the where value to unscope. For example:
371
410
  #
372
411
  # User.where(name: "John", active: true).unscope(where: :name)
373
412
  # == User.where(active: true)
374
413
  #
375
- # This method is similar to <tt>except</tt>, but unlike
376
- # <tt>except</tt>, it persists across merges:
414
+ # This method is similar to #except, but unlike
415
+ # #except, it persists across merges:
377
416
  #
378
417
  # User.order('email').merge(User.except(:order))
379
418
  # == User.order('email')
@@ -383,7 +422,7 @@ module ActiveRecord
383
422
  #
384
423
  # This means it can be used in association definitions:
385
424
  #
386
- # has_many :comments, -> { unscope where: :trashed }
425
+ # has_many :comments, -> { unscope(where: :trashed) }
387
426
  #
388
427
  def unscope(*args)
389
428
  check_if_method_has_arguments!(:unscope, args)
@@ -391,22 +430,25 @@ module ActiveRecord
391
430
  end
392
431
 
393
432
  def unscope!(*args) # :nodoc:
394
- args.flatten!
395
433
  self.unscope_values += args
396
434
 
397
435
  args.each do |scope|
398
436
  case scope
399
437
  when Symbol
400
- 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)
401
444
  when Hash
402
445
  scope.each do |key, target_value|
403
446
  if key != :where
404
447
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
405
448
  end
406
449
 
407
- Array(target_value).each do |val|
408
- where_unscoping(val)
409
- end
450
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
451
+ self.where_clause = where_clause.except(*target_values)
410
452
  end
411
453
  else
412
454
  raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
@@ -416,33 +458,57 @@ module ActiveRecord
416
458
  self
417
459
  end
418
460
 
419
- # Performs a joins on +args+:
461
+ # Performs a joins on +args+. The given symbol(s) should match the name of
462
+ # the association(s).
420
463
  #
421
464
  # User.joins(:posts)
422
- # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
465
+ # # SELECT "users".*
466
+ # # FROM "users"
467
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
468
+ #
469
+ # Multiple joins:
470
+ #
471
+ # User.joins(:posts, :account)
472
+ # # SELECT "users".*
473
+ # # FROM "users"
474
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
475
+ # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
476
+ #
477
+ # Nested joins:
478
+ #
479
+ # User.joins(posts: [:comments])
480
+ # # SELECT "users".*
481
+ # # FROM "users"
482
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
483
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
423
484
  #
424
485
  # You can use strings in order to customize your joins:
425
486
  #
426
487
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
427
- # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
488
+ # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
428
489
  def joins(*args)
429
490
  check_if_method_has_arguments!(:joins, args)
430
491
  spawn.joins!(*args)
431
492
  end
432
493
 
433
494
  def joins!(*args) # :nodoc:
434
- args.compact!
435
- args.flatten!
436
- self.joins_values += args
495
+ self.joins_values |= args
437
496
  self
438
497
  end
439
498
 
440
- def bind(value) # :nodoc:
441
- spawn.bind!(value)
499
+ # Performs a left outer joins on +args+:
500
+ #
501
+ # User.left_outer_joins(:posts)
502
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
503
+ #
504
+ def left_outer_joins(*args)
505
+ check_if_method_has_arguments!(__callee__, args)
506
+ spawn.left_outer_joins!(*args)
442
507
  end
508
+ alias :left_joins :left_outer_joins
443
509
 
444
- def bind!(value) # :nodoc:
445
- self.bind_values += [value]
510
+ def left_outer_joins!(*args) # :nodoc:
511
+ self.left_outer_joins_values |= args
446
512
  self
447
513
  end
448
514
 
@@ -489,7 +555,7 @@ module ActiveRecord
489
555
  # than the previous methods; you are responsible for ensuring that the values in the template
490
556
  # are properly quoted. The values are passed to the connector for quoting, but the caller
491
557
  # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
492
- # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
558
+ # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
493
559
  #
494
560
  # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
495
561
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
@@ -565,36 +631,105 @@ module ActiveRecord
565
631
  #
566
632
  # If the condition is any blank-ish object, then #where is a no-op and returns
567
633
  # the current relation.
568
- def where(opts = :chain, *rest)
569
- if opts == :chain
634
+ def where(*args)
635
+ if args.empty?
570
636
  WhereChain.new(spawn)
571
- elsif opts.blank?
637
+ elsif args.length == 1 && args.first.blank?
572
638
  self
573
639
  else
574
- spawn.where!(opts, *rest)
640
+ spawn.where!(*args)
575
641
  end
576
642
  end
577
643
 
578
644
  def where!(opts, *rest) # :nodoc:
579
- if Hash === opts
580
- opts = sanitize_forbidden_attributes(opts)
581
- references!(PredicateBuilder.references(opts))
582
- end
583
-
584
- self.where_values += build_where(opts, rest)
645
+ self.where_clause += build_where_clause(opts, rest)
585
646
  self
586
647
  end
587
648
 
588
649
  # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
589
650
  #
590
- # Post.where(trashed: true).where(trashed: false) # => WHERE `trashed` = 1 AND `trashed` = 0
591
- # Post.where(trashed: true).rewhere(trashed: false) # => WHERE `trashed` = 0
592
- # Post.where(active: true).where(trashed: true).rewhere(trashed: false) # => WHERE `active` = 1 AND `trashed` = 0
651
+ # Post.where(trashed: true).where(trashed: false)
652
+ # # WHERE `trashed` = 1 AND `trashed` = 0
593
653
  #
594
- # This is short-hand for unscope(where: conditions.keys).where(conditions). Note that unlike reorder, we're only unscoping
595
- # the named conditions -- not the entire where statement.
654
+ # Post.where(trashed: true).rewhere(trashed: false)
655
+ # # WHERE `trashed` = 0
656
+ #
657
+ # Post.where(active: true).where(trashed: true).rewhere(trashed: false)
658
+ # # WHERE `active` = 1 AND `trashed` = 0
659
+ #
660
+ # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
661
+ # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
596
662
  def rewhere(conditions)
597
- 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
701
+ end
702
+
703
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
704
+ # argument.
705
+ #
706
+ # The two relations must be structurally compatible: they must be scoping the same model, and
707
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
708
+ # present).
709
+ #
710
+ # Post.where("id = 1").or(Post.where("author_id = 3"))
711
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
712
+ #
713
+ def or(other)
714
+ if other.is_a?(Relation)
715
+ spawn.or!(other)
716
+ else
717
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
718
+ end
719
+ end
720
+
721
+ def or!(other) # :nodoc:
722
+ incompatible_values = structurally_incompatible_values_for(other)
723
+
724
+ unless incompatible_values.empty?
725
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
726
+ end
727
+
728
+ self.where_clause = self.where_clause.or(other.where_clause)
729
+ self.having_clause = having_clause.or(other.having_clause)
730
+ self.references_values |= other.references_values
731
+
732
+ self
598
733
  end
599
734
 
600
735
  # Allows to specify a HAVING clause. Note that you can't use HAVING
@@ -606,9 +741,7 @@ module ActiveRecord
606
741
  end
607
742
 
608
743
  def having!(opts, *rest) # :nodoc:
609
- references!(PredicateBuilder.references(opts)) if Hash === opts
610
-
611
- self.having_values += build_where(opts, rest)
744
+ self.having_clause += build_having_clause(opts, rest)
612
745
  self
613
746
  end
614
747
 
@@ -643,7 +776,7 @@ module ActiveRecord
643
776
  end
644
777
 
645
778
  # Specifies locking settings (default to +true+). For more information
646
- # on locking, please see +ActiveRecord::Locking+.
779
+ # on locking, please see ActiveRecord::Locking.
647
780
  def lock(locks = true)
648
781
  spawn.lock!(locks)
649
782
  end
@@ -674,7 +807,7 @@ module ActiveRecord
674
807
  # For example:
675
808
  #
676
809
  # @posts = current_user.visible_posts.where(name: params[:name])
677
- # # => the visible_posts method is expected to return a chainable Relation
810
+ # # the visible_posts method is expected to return a chainable Relation
678
811
  #
679
812
  # def visible_posts
680
813
  # case role
@@ -688,7 +821,7 @@ module ActiveRecord
688
821
  # end
689
822
  #
690
823
  def none
691
- where("1=0").extending!(NullRelation)
824
+ spawn.none!
692
825
  end
693
826
 
694
827
  def none! # :nodoc:
@@ -700,7 +833,7 @@ module ActiveRecord
700
833
  #
701
834
  # users = User.readonly
702
835
  # users.first.save
703
- # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
836
+ # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
704
837
  def readonly(value = true)
705
838
  spawn.readonly!(value)
706
839
  end
@@ -710,6 +843,21 @@ module ActiveRecord
710
843
  self
711
844
  end
712
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
+
713
861
  # Sets attributes to be used when creating new records from a
714
862
  # relation object.
715
863
  #
@@ -719,7 +867,7 @@ module ActiveRecord
719
867
  # users = users.create_with(name: 'DHH')
720
868
  # users.new.name # => 'DHH'
721
869
  #
722
- # You can pass +nil+ to +create_with+ to reset attributes:
870
+ # You can pass +nil+ to #create_with to reset attributes:
723
871
  #
724
872
  # users = users.create_with(nil)
725
873
  # users.new.name # => 'Oscar'
@@ -732,7 +880,7 @@ module ActiveRecord
732
880
  value = sanitize_forbidden_attributes(value)
733
881
  self.create_with_value = create_with_value.merge(value)
734
882
  else
735
- self.create_with_value = {}
883
+ self.create_with_value = FROZEN_EMPTY_HASH
736
884
  end
737
885
 
738
886
  self
@@ -741,49 +889,44 @@ module ActiveRecord
741
889
  # Specifies table from which the records will be fetched. For example:
742
890
  #
743
891
  # Topic.select('title').from('posts')
744
- # # => SELECT title FROM posts
892
+ # # SELECT title FROM posts
745
893
  #
746
894
  # Can accept other relation objects. For example:
747
895
  #
748
896
  # Topic.select('title').from(Topic.approved)
749
- # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
897
+ # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
750
898
  #
751
899
  # Topic.select('a.title').from(Topic.approved, :a)
752
- # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
900
+ # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
753
901
  #
754
902
  def from(value, subquery_name = nil)
755
903
  spawn.from!(value, subquery_name)
756
904
  end
757
905
 
758
906
  def from!(value, subquery_name = nil) # :nodoc:
759
- self.from_value = [value, subquery_name]
760
- if value.is_a? Relation
761
- self.bind_values = value.arel.bind_values + value.bind_values + bind_values
762
- end
907
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
763
908
  self
764
909
  end
765
910
 
766
911
  # Specifies whether the records should be unique or not. For example:
767
912
  #
768
913
  # User.select(:name)
769
- # # => Might return two records with the same name
914
+ # # Might return two records with the same name
770
915
  #
771
916
  # User.select(:name).distinct
772
- # # => Returns 1 record per distinct name
917
+ # # Returns 1 record per distinct name
773
918
  #
774
919
  # User.select(:name).distinct.distinct(false)
775
- # # => You can also remove the uniqueness
920
+ # # You can also remove the uniqueness
776
921
  def distinct(value = true)
777
922
  spawn.distinct!(value)
778
923
  end
779
- alias uniq distinct
780
924
 
781
925
  # Like #distinct, but modifies relation in place.
782
926
  def distinct!(value = true) # :nodoc:
783
927
  self.distinct_value = value
784
928
  self
785
929
  end
786
- alias uniq! distinct!
787
930
 
788
931
  # Used to extend a scope with additional methods, either through
789
932
  # a module or through a block provided.
@@ -839,6 +982,27 @@ module ActiveRecord
839
982
  self
840
983
  end
841
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
+
842
1006
  # Reverse the existing order clause on the relation.
843
1007
  #
844
1008
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -847,332 +1011,512 @@ module ActiveRecord
847
1011
  end
848
1012
 
849
1013
  def reverse_order! # :nodoc:
850
- orders = order_values.uniq
851
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
852
1015
  self.order_values = reverse_sql_order(orders)
853
1016
  self
854
1017
  end
855
1018
 
856
- # Returns the Arel object associated with the relation.
857
- def arel # :nodoc:
858
- @arel ||= build_arel
1019
+ def skip_query_cache!(value = true) # :nodoc:
1020
+ self.skip_query_cache_value = value
1021
+ self
859
1022
  end
860
1023
 
861
- private
1024
+ def skip_preloading! # :nodoc:
1025
+ self.skip_preloading_value = true
1026
+ self
1027
+ end
862
1028
 
863
- def build_arel
864
- arel = Arel::SelectManager.new(table.engine, table)
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
865
1042
 
866
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
1043
+ # Like #annotate, but modifies relation in place.
1044
+ def annotate!(*args) # :nodoc:
1045
+ self.annotate_values += args
1046
+ self
1047
+ end
867
1048
 
868
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
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
869
1056
 
870
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
1057
+ # Returns the Arel object associated with the relation.
1058
+ def arel(aliases = nil) # :nodoc:
1059
+ @arel ||= build_arel(aliases)
1060
+ end
871
1061
 
872
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
873
- arel.skip(offset_value.to_i) if offset_value
874
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1062
+ def construct_join_dependency(associations, join_type) # :nodoc:
1063
+ ActiveRecord::Associations::JoinDependency.new(
1064
+ klass, table, associations, join_type
1065
+ )
1066
+ end
875
1067
 
876
- build_order(arel)
1068
+ protected
1069
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1070
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
877
1071
 
878
- build_select(arel)
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
879
1076
 
880
- arel.distinct(distinct_value)
881
- arel.from(build_from) if from_value
882
- arel.lock(lock_value) if lock_value
1077
+ def build_where_clause(opts, rest = []) # :nodoc:
1078
+ opts = sanitize_forbidden_attributes(opts)
883
1079
 
884
- arel
885
- end
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
886
1099
 
887
- def symbol_unscoping(scope)
888
- if !VALID_UNSCOPING_VALUES.include?(scope)
889
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
1100
+ Relation::WhereClause.new(parts)
890
1101
  end
1102
+ alias :build_having_clause :build_where_clause
891
1103
 
892
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
893
- unscope_code = "#{scope}_value#{'s' unless single_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
1110
+ end
894
1111
 
895
- case scope
896
- when :order
897
- result = []
898
- when :where
899
- self.bind_values = []
900
- else
901
- result = [] unless single_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
902
1118
  end
903
1119
 
904
- self.send(unscope_code, result)
905
- 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?
906
1124
 
907
- def where_unscoping(target_value)
908
- target_value = target_value.to_s
1125
+ join_dependencies = []
1126
+ join_dependencies.unshift construct_join_dependency(
1127
+ select_association_list(associations, join_dependencies), nil
1128
+ )
1129
+ end
909
1130
 
910
- self.where_values = where_values.reject do |rel|
911
- case rel
912
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
913
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
914
- subrelation.name == target_value
915
- end
1131
+ def assert_mutability!
1132
+ raise ImmutableRelation if @loaded
1133
+ raise ImmutableRelation if defined?(@arel) && @arel
916
1134
  end
917
1135
 
918
- bind_values.reject! { |col,_| col.name == target_value }
919
- end
1136
+ def build_arel(aliases = nil)
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)
1167
+ end
920
1168
 
921
- def custom_join_ast(table, joins)
922
- joins = joins.reject(&:blank?)
1169
+ arel
1170
+ end
923
1171
 
924
- return [] if joins.empty?
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
925
1176
 
926
- joins.map! do |join|
927
- case join
928
- when Array
929
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
930
- when String
931
- join = Arel.sql(join)
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)
1187
+ else
1188
+ opts
932
1189
  end
933
- table.create_string_join(join)
934
1190
  end
935
- end
936
1191
 
937
- def collapse_wheres(arel, wheres)
938
- predicates = wheres.map do |where|
939
- next where if ::Arel::Nodes::Equality === where
940
- where = Arel.sql(where) if String === where
941
- Arel::Nodes::Grouping.new(where)
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
942
1205
  end
943
1206
 
944
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
945
- end
1207
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1208
+ end
946
1209
 
947
- def build_where(opts, other = [])
948
- case opts
949
- when String, Array
950
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
951
- when Hash
952
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
1210
+ def build_join_buckets
1211
+ buckets = Hash.new { |h, k| h[k] = [] }
953
1212
 
954
- tmp_opts, bind_values = create_binds(opts)
955
- self.bind_values += bind_values
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
956
1218
 
957
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
958
- add_relations_to_bind_values(attributes)
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
959
1227
 
960
- PredicateBuilder.build_from_hash(klass, attributes, table)
961
- else
962
- [opts]
963
- end
964
- end
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
965
1232
 
966
- def create_binds(opts)
967
- bindable, non_binds = opts.partition do |column, value|
968
- PredicateBuilder.can_be_bound?(value) &&
969
- @klass.columns_hash.include?(column.to_s) &&
970
- !@klass.reflect_on_aggregation(column)
971
- end
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
972
1236
 
973
- association_binds, non_binds = non_binds.partition do |column, value|
974
- value.is_a?(Hash) && association_for_table(column)
975
- end
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
976
1245
 
977
- new_opts = {}
978
- binds = []
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
979
1253
 
980
- connection = self.connection
1254
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1255
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
981
1256
 
982
- bindable.each do |(column,value)|
983
- binds.push [@klass.columns_hash[column.to_s], value]
984
- new_opts[column] = connection.substitute_at(column)
1257
+ return buckets, Arel::Nodes::InnerJoin
985
1258
  end
986
1259
 
987
- association_binds.each do |(column, value)|
988
- association_relation = association_for_table(column).klass.send(:relation)
989
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
990
- new_opts[column] = association_new_opts
991
- binds += association_bind
992
- end
1260
+ def build_joins(join_sources, aliases = nil)
1261
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
993
1262
 
994
- non_binds.each { |column,value| new_opts[column] = value }
1263
+ buckets, join_type = build_join_buckets
995
1264
 
996
- [new_opts, binds]
997
- 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]
998
1269
 
999
- def association_for_table(table_name)
1000
- table_name = table_name.to_s
1001
- @klass._reflect_on_association(table_name) ||
1002
- @klass._reflect_on_association(table_name.singularize)
1003
- end
1270
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1004
1271
 
1005
- def build_from
1006
- opts, name = from_value
1007
- case opts
1008
- when Relation
1009
- name ||= 'subquery'
1010
- opts.arel.as(name.to_s)
1011
- else
1012
- opts
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
1013
1280
  end
1014
- end
1015
1281
 
1016
- def build_joins(manager, joins)
1017
- buckets = joins.group_by do |join|
1018
- case join
1019
- when String
1020
- :string_join
1021
- when Hash, Symbol, Array
1022
- :association_join
1023
- when ActiveRecord::Associations::JoinDependency
1024
- :stashed_join
1025
- when Arel::Nodes::Join
1026
- :join_node
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] })
1027
1287
  else
1028
- raise 'unknown class: %s' % join.class.name
1288
+ arel.project(table[Arel.star])
1029
1289
  end
1030
1290
  end
1031
1291
 
1032
- association_joins = buckets[:association_join] || []
1033
- stashed_association_joins = buckets[:stashed_join] || []
1034
- join_nodes = (buckets[:join_node] || []).uniq
1035
- string_joins = (buckets[:string_join] || []).map(&:strip).uniq
1036
-
1037
- join_list = join_nodes + custom_join_ast(manager, string_joins)
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
1307
+ end
1038
1308
 
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
1309
+ def arel_column(field)
1310
+ field = klass.attribute_aliases[field] || field
1311
+ from = from_clause.name || from_clause.value
1044
1312
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
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)
1319
+ end
1320
+ else
1321
+ yield field
1322
+ end
1323
+ end
1046
1324
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
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)
1050
1329
  end
1051
1330
 
1052
- manager.join_sources.concat(join_list)
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
1053
1337
 
1054
- manager
1055
- 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
1056
1359
 
1057
- def build_select(arel)
1058
- if select_values.any?
1059
- arel.project(*arel_columns(select_values.uniq))
1060
- else
1061
- arel.project(@klass.arel_table[Arel.star])
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)
1364
+
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)
1062
1369
  end
1063
- end
1064
1370
 
1065
- def arel_columns(columns)
1066
- columns.map do |field|
1067
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value
1068
- arel_table[field]
1069
- elsif Symbol === field
1070
- connection.quote_table_name(field.to_s)
1071
- else
1072
- field
1073
- end
1371
+ def build_order(arel)
1372
+ orders = order_values.compact_blank
1373
+ arel.order(*orders) unless orders.empty?
1074
1374
  end
1075
- end
1076
1375
 
1077
- def reverse_sql_order(order_query)
1078
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1376
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1377
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1079
1378
 
1080
- order_query.flat_map do |o|
1081
- case o
1082
- when Arel::Nodes::Ordering
1083
- o.reverse
1084
- when String
1085
- o.to_s.split(',').map! do |s|
1086
- s.strip!
1087
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
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
1088
1387
  end
1089
- else
1090
- o
1091
1388
  end
1092
1389
  end
1093
- end
1094
1390
 
1095
- def array_of_strings?(o)
1096
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
1097
- end
1098
-
1099
- def build_order(arel)
1100
- orders = order_values.uniq
1101
- orders.reject!(&:blank?)
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!
1420
+ end
1102
1421
 
1103
- arel.order(*orders) unless orders.empty?
1104
- 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
1105
1429
 
1106
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1107
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
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
1108
1435
 
1109
- def validate_order_args(args)
1110
- args.each do |arg|
1111
- next unless arg.is_a?(Hash)
1112
- arg.each do |_key, value|
1113
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
1114
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
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
1115
1443
  end
1116
1444
  end
1117
- end
1118
1445
 
1119
- def preprocess_order_args(order_args)
1120
- order_args.flatten!
1121
- validate_order_args(order_args)
1122
-
1123
- references = order_args.grep(String)
1124
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1125
- references!(references) if references.any?
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
1126
1469
 
1127
- # if a symbol is given we prepend the quoted table name
1128
- order_args.map! do |arg|
1129
- case arg
1130
- when Symbol
1131
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
1132
- table[arg].asc
1133
- when Hash
1134
- arg.map { |field, dir|
1135
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1136
- table[field].send(dir.downcase)
1137
- }
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
1138
1491
  else
1139
- arg
1492
+ args.flatten!
1493
+ args.compact_blank!
1140
1494
  end
1141
- end.flatten!
1142
- end
1143
-
1144
- # Checks to make sure that the arguments are not blank. Note that if some
1145
- # blank-like object were initially passed into the query method, then this
1146
- # method will not raise an error.
1147
- #
1148
- # Example:
1149
- #
1150
- # Post.references() # => raises an error
1151
- # Post.references([]) # => does not raise an error
1152
- #
1153
- # This particular method should be called with a method_name and the args
1154
- # passed into that method as an input. For example:
1155
- #
1156
- # def references(*args)
1157
- # check_if_method_has_arguments!("references", args)
1158
- # ...
1159
- # end
1160
- def check_if_method_has_arguments!(method_name, args)
1161
- if args.blank?
1162
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1163
1495
  end
1164
- end
1165
1496
 
1166
- def add_relations_to_bind_values(attributes)
1167
- if attributes.is_a?(Hash)
1168
- attributes.each_value do |value|
1169
- if value.is_a?(ActiveRecord::Relation)
1170
- self.bind_values += value.arel.bind_values + value.bind_values
1171
- else
1172
- add_relations_to_bind_values(value)
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
1173
1510
  end
1511
+ v1 == v2
1174
1512
  end
1175
1513
  end
1514
+ end
1515
+
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:
1176
1520
  end
1177
1521
  end
1178
1522
  end