activerecord 4.2.0 → 6.1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1221 -796
  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 +267 -249
  8. data/lib/active_record/association_relation.rb +45 -7
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +172 -67
  11. data/lib/active_record/associations/association_scope.rb +105 -129
  12. data/lib/active_record/associations/belongs_to_association.rb +85 -59
  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 -33
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -70
  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 +168 -279
  22. data/lib/active_record/associations/collection_proxy.rb +263 -155
  23. data/lib/active_record/associations/foreign_association.rb +33 -0
  24. data/lib/active_record/associations/has_many_association.rb +57 -84
  25. data/lib/active_record/associations/has_many_through_association.rb +70 -82
  26. data/lib/active_record/associations/has_one_association.rb +74 -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 -73
  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 +175 -164
  32. data/lib/active_record/associations/preloader/association.rb +107 -112
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +99 -96
  35. data/lib/active_record/associations/singular_association.rb +18 -45
  36. data/lib/active_record/associations/through_association.rb +49 -24
  37. data/lib/active_record/associations.rb +1845 -1597
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +20 -7
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -138
  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 +59 -36
  46. data/lib/active_record/attribute_methods/write.rb +25 -56
  47. data/lib/active_record/attribute_methods.rb +153 -162
  48. data/lib/active_record/attributes.rb +234 -70
  49. data/lib/active_record/autosave_association.rb +157 -69
  50. data/lib/active_record/base.rb +49 -50
  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 +46 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -317
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +301 -113
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +187 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +9 -7
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +485 -253
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +909 -263
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +254 -92
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +492 -221
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +580 -608
  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 +196 -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 +271 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +81 -199
  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 +78 -161
  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 -57
  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 +5 -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 +17 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +6 -3
  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 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
  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 +171 -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 +499 -293
  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 +595 -382
  119. data/lib/active_record/connection_adapters/schema_cache.rb +191 -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 +146 -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 -389
  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 +488 -243
  133. data/lib/active_record/counter_cache.rb +71 -50
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -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 +273 -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 +212 -94
  143. data/lib/active_record/errors.rb +225 -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 +11 -6
  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 +273 -496
  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 +64 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +52 -0
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +103 -95
  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 +298 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +685 -309
  170. data/lib/active_record/model_schema.rb +420 -113
  171. data/lib/active_record/nested_attributes.rb +265 -216
  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 +574 -135
  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 +175 -54
  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 +533 -216
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +485 -310
  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 +326 -244
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +318 -256
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +99 -84
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -25
  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 +139 -96
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -409
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +23 -21
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -342
  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 -26
  209. data/lib/active_record/scoping/default.rb +96 -82
  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 +133 -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 +366 -129
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +68 -100
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +87 -39
  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 +291 -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 +181 -152
  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 -41
  231. data/lib/active_record/type/date_time.rb +4 -38
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +12 -5
  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 +33 -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 +65 -48
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +44 -28
  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 -10
  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.rb +7 -5
  339. metadata +175 -65
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  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 -23
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  347. data/lib/active_record/attribute.rb +0 -149
  348. data/lib/active_record/attribute_decorators.rb +0 -66
  349. data/lib/active_record/attribute_set/builder.rb +0 -86
  350. data/lib/active_record/attribute_set.rb +0 -77
  351. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  352. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  353. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  354. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  355. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  356. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  357. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  358. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  359. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  360. data/lib/active_record/type/big_integer.rb +0 -13
  361. data/lib/active_record/type/binary.rb +0 -50
  362. data/lib/active_record/type/boolean.rb +0 -30
  363. data/lib/active_record/type/decimal.rb +0 -40
  364. data/lib/active_record/type/decorator.rb +0 -14
  365. data/lib/active_record/type/float.rb +0 -19
  366. data/lib/active_record/type/integer.rb +0 -55
  367. data/lib/active_record/type/mutable.rb +0 -16
  368. data/lib/active_record/type/numeric.rb +0 -36
  369. data/lib/active_record/type/string.rb +0 -36
  370. data/lib/active_record/type/time_value.rb +0 -38
  371. data/lib/active_record/type/value.rb +0 -101
  372. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
  373. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
  374. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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) : 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
653
+ #
654
+ # Post.where(trashed: true).rewhere(trashed: false)
655
+ # # WHERE `trashed` = 0
593
656
  #
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.
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,46 +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]
907
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
760
908
  self
761
909
  end
762
910
 
763
911
  # Specifies whether the records should be unique or not. For example:
764
912
  #
765
913
  # User.select(:name)
766
- # # => Might return two records with the same name
914
+ # # Might return two records with the same name
767
915
  #
768
916
  # User.select(:name).distinct
769
- # # => Returns 1 record per distinct name
917
+ # # Returns 1 record per distinct name
770
918
  #
771
919
  # User.select(:name).distinct.distinct(false)
772
- # # => You can also remove the uniqueness
920
+ # # You can also remove the uniqueness
773
921
  def distinct(value = true)
774
922
  spawn.distinct!(value)
775
923
  end
776
- alias uniq distinct
777
924
 
778
925
  # Like #distinct, but modifies relation in place.
779
926
  def distinct!(value = true) # :nodoc:
780
927
  self.distinct_value = value
781
928
  self
782
929
  end
783
- alias uniq! distinct!
784
930
 
785
931
  # Used to extend a scope with additional methods, either through
786
932
  # a module or through a block provided.
@@ -836,6 +982,27 @@ module ActiveRecord
836
982
  self
837
983
  end
838
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
+
839
1006
  # Reverse the existing order clause on the relation.
840
1007
  #
841
1008
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -844,333 +1011,514 @@ module ActiveRecord
844
1011
  end
845
1012
 
846
1013
  def reverse_order! # :nodoc:
847
- orders = order_values.uniq
848
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
849
1015
  self.order_values = reverse_sql_order(orders)
850
1016
  self
851
1017
  end
852
1018
 
853
- # Returns the Arel object associated with the relation.
854
- def arel # :nodoc:
855
- @arel ||= build_arel
1019
+ def skip_query_cache!(value = true) # :nodoc:
1020
+ self.skip_query_cache_value = value
1021
+ self
856
1022
  end
857
1023
 
858
- private
1024
+ def skip_preloading! # :nodoc:
1025
+ self.skip_preloading_value = true
1026
+ self
1027
+ end
859
1028
 
860
- def build_arel
861
- 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
+ #
1039
+ # Some escaping is performed, however untrusted user input should not be used.
1040
+ def annotate(*args)
1041
+ check_if_method_has_arguments!(:annotate, args)
1042
+ spawn.annotate!(*args)
1043
+ end
862
1044
 
863
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
1045
+ # Like #annotate, but modifies relation in place.
1046
+ def annotate!(*args) # :nodoc:
1047
+ self.annotate_values += args
1048
+ self
1049
+ end
864
1050
 
865
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
1051
+ # Deduplicate multiple values.
1052
+ def uniq!(name)
1053
+ if values = @values[name]
1054
+ values.uniq! if values.is_a?(Array) && !values.empty?
1055
+ end
1056
+ self
1057
+ end
866
1058
 
867
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
1059
+ # Returns the Arel object associated with the relation.
1060
+ def arel(aliases = nil) # :nodoc:
1061
+ @arel ||= build_arel(aliases)
1062
+ end
868
1063
 
869
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
870
- arel.skip(offset_value.to_i) if offset_value
1064
+ def construct_join_dependency(associations, join_type) # :nodoc:
1065
+ ActiveRecord::Associations::JoinDependency.new(
1066
+ klass, table, associations, join_type
1067
+ )
1068
+ end
871
1069
 
872
- arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
1070
+ protected
1071
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1072
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
873
1073
 
874
- build_order(arel)
1074
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1075
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1076
+ end
1077
+ end
875
1078
 
876
- build_select(arel, select_values.uniq)
1079
+ def build_where_clause(opts, rest = []) # :nodoc:
1080
+ opts = sanitize_forbidden_attributes(opts)
877
1081
 
878
- arel.distinct(distinct_value)
879
- arel.from(build_from) if from_value
880
- arel.lock(lock_value) if lock_value
1082
+ case opts
1083
+ when String, Array
1084
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1085
+ when Hash
1086
+ opts = opts.transform_keys do |key|
1087
+ key = key.to_s
1088
+ klass.attribute_aliases[key] || key
1089
+ end
1090
+ references = PredicateBuilder.references(opts)
1091
+ self.references_values |= references unless references.empty?
881
1092
 
882
- arel
883
- end
1093
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1094
+ lookup_table_klass_from_join_dependencies(table_name)
1095
+ end
1096
+ when Arel::Nodes::Node
1097
+ parts = [opts]
1098
+ else
1099
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1100
+ end
884
1101
 
885
- def symbol_unscoping(scope)
886
- if !VALID_UNSCOPING_VALUES.include?(scope)
887
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
1102
+ Relation::WhereClause.new(parts)
888
1103
  end
1104
+ alias :build_having_clause :build_where_clause
889
1105
 
890
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
891
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
1106
+ private
1107
+ def lookup_table_klass_from_join_dependencies(table_name)
1108
+ each_join_dependencies do |join|
1109
+ return join.base_klass if table_name == join.table_name
1110
+ end
1111
+ nil
1112
+ end
892
1113
 
893
- case scope
894
- when :order
895
- result = []
896
- when :where
897
- self.bind_values = []
898
- else
899
- result = [] unless single_val_method
1114
+ def each_join_dependencies(join_dependencies = build_join_dependencies)
1115
+ join_dependencies.each do |join_dependency|
1116
+ join_dependency.each do |join|
1117
+ yield join
1118
+ end
1119
+ end
900
1120
  end
901
1121
 
902
- self.send(unscope_code, result)
903
- end
1122
+ def build_join_dependencies
1123
+ associations = joins_values | left_outer_joins_values
1124
+ associations |= eager_load_values unless eager_load_values.empty?
1125
+ associations |= includes_values unless includes_values.empty?
904
1126
 
905
- def where_unscoping(target_value)
906
- target_value = target_value.to_s
1127
+ join_dependencies = []
1128
+ join_dependencies.unshift construct_join_dependency(
1129
+ select_association_list(associations, join_dependencies), nil
1130
+ )
1131
+ end
907
1132
 
908
- where_values.reject! do |rel|
909
- case rel
910
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
911
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
912
- subrelation.name == target_value
913
- end
1133
+ def assert_mutability!
1134
+ raise ImmutableRelation if @loaded
1135
+ raise ImmutableRelation if defined?(@arel) && @arel
914
1136
  end
915
1137
 
916
- bind_values.reject! { |col,_| col.name == target_value }
917
- end
1138
+ def build_arel(aliases = nil)
1139
+ arel = Arel::SelectManager.new(table)
1140
+
1141
+ build_joins(arel.join_sources, aliases)
1142
+
1143
+ arel.where(where_clause.ast) unless where_clause.empty?
1144
+ arel.having(having_clause.ast) unless having_clause.empty?
1145
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1146
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1147
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1148
+
1149
+ build_order(arel)
1150
+ build_select(arel)
1151
+
1152
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1153
+ arel.distinct(distinct_value)
1154
+ arel.from(build_from) unless from_clause.empty?
1155
+ arel.lock(lock_value) if lock_value
1156
+
1157
+ unless annotate_values.empty?
1158
+ annotates = annotate_values
1159
+ annotates = annotates.uniq if annotates.size > 1
1160
+ unless annotates == annotate_values
1161
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
1162
+ Duplicated query annotations are no longer shown in queries in Rails 7.0.
1163
+ To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1164
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1165
+ MSG
1166
+ annotates = annotate_values
1167
+ end
1168
+ arel.comment(*annotates)
1169
+ end
918
1170
 
919
- def custom_join_ast(table, joins)
920
- joins = joins.reject(&:blank?)
1171
+ arel
1172
+ end
921
1173
 
922
- return [] if joins.empty?
1174
+ def build_cast_value(name, value)
1175
+ cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1176
+ Arel::Nodes::BindParam.new(cast_value)
1177
+ end
923
1178
 
924
- joins.map! do |join|
925
- case join
926
- when Array
927
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
928
- when String
929
- join = Arel.sql(join)
1179
+ def build_from
1180
+ opts = from_clause.value
1181
+ name = from_clause.name
1182
+ case opts
1183
+ when Relation
1184
+ if opts.eager_loading?
1185
+ opts = opts.send(:apply_join_dependency)
1186
+ end
1187
+ name ||= "subquery"
1188
+ opts.arel.as(name.to_s)
1189
+ else
1190
+ opts
930
1191
  end
931
- table.create_string_join(join)
932
1192
  end
933
- end
934
1193
 
935
- def collapse_wheres(arel, wheres)
936
- predicates = wheres.map do |where|
937
- next where if ::Arel::Nodes::Equality === where
938
- where = Arel.sql(where) if String === where
939
- Arel::Nodes::Grouping.new(where)
1194
+ def select_association_list(associations, stashed_joins = nil)
1195
+ result = []
1196
+ associations.each do |association|
1197
+ case association
1198
+ when Hash, Symbol, Array
1199
+ result << association
1200
+ when ActiveRecord::Associations::JoinDependency
1201
+ stashed_joins&.<< association
1202
+ else
1203
+ yield association if block_given?
1204
+ end
1205
+ end
1206
+ result
940
1207
  end
941
1208
 
942
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
943
- end
1209
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1210
+ end
944
1211
 
945
- def build_where(opts, other = [])
946
- case opts
947
- when String, Array
948
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
949
- when Hash
950
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
1212
+ def build_join_buckets
1213
+ buckets = Hash.new { |h, k| h[k] = [] }
951
1214
 
952
- tmp_opts, bind_values = create_binds(opts)
953
- self.bind_values += bind_values
1215
+ unless left_outer_joins_values.empty?
1216
+ stashed_left_joins = []
1217
+ left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1218
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1219
+ end
954
1220
 
955
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
956
- add_relations_to_bind_values(attributes)
1221
+ if joins_values.empty?
1222
+ buckets[:association_join] = left_joins
1223
+ buckets[:stashed_join] = stashed_left_joins
1224
+ return buckets, Arel::Nodes::OuterJoin
1225
+ else
1226
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1227
+ end
1228
+ end
957
1229
 
958
- PredicateBuilder.build_from_hash(klass, attributes, table)
959
- else
960
- [opts]
961
- end
962
- end
1230
+ joins = joins_values.dup
1231
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1232
+ stashed_eager_load = joins.pop if joins.last.base_klass == klass
1233
+ end
963
1234
 
964
- def create_binds(opts)
965
- bindable, non_binds = opts.partition do |column, value|
966
- case value
967
- when String, Integer, ActiveRecord::StatementCache::Substitute
968
- @klass.columns_hash.include? column.to_s
969
- else
970
- false
1235
+ joins.each_with_index do |join, i|
1236
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
971
1237
  end
972
- end
973
1238
 
974
- association_binds, non_binds = non_binds.partition do |column, value|
975
- value.is_a?(Hash) && association_for_table(column)
976
- end
1239
+ while joins.first.is_a?(Arel::Nodes::Join)
1240
+ join_node = joins.shift
1241
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1242
+ buckets[:join_node] << join_node
1243
+ else
1244
+ buckets[:leading_join] << join_node
1245
+ end
1246
+ end
977
1247
 
978
- new_opts = {}
979
- binds = []
1248
+ buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1249
+ if join.is_a?(Arel::Nodes::Join)
1250
+ buckets[:join_node] << join
1251
+ else
1252
+ raise "unknown class: %s" % join.class.name
1253
+ end
1254
+ end
980
1255
 
981
- bindable.each do |(column,value)|
982
- binds.push [@klass.columns_hash[column.to_s], value]
983
- new_opts[column] = connection.substitute_at(column)
984
- end
1256
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1257
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
985
1258
 
986
- association_binds.each do |(column, value)|
987
- association_relation = association_for_table(column).klass.send(:relation)
988
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
989
- new_opts[column] = association_new_opts
990
- binds += association_bind
1259
+ return buckets, Arel::Nodes::InnerJoin
991
1260
  end
992
1261
 
993
- non_binds.each { |column,value| new_opts[column] = value }
1262
+ def build_joins(join_sources, aliases = nil)
1263
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
994
1264
 
995
- [new_opts, binds]
996
- end
1265
+ buckets, join_type = build_join_buckets
997
1266
 
998
- def association_for_table(table_name)
999
- table_name = table_name.to_s
1000
- @klass._reflect_on_association(table_name) ||
1001
- @klass._reflect_on_association(table_name.singularize)
1002
- end
1267
+ association_joins = buckets[:association_join]
1268
+ stashed_joins = buckets[:stashed_join]
1269
+ leading_joins = buckets[:leading_join]
1270
+ join_nodes = buckets[:join_node]
1003
1271
 
1004
- def build_from
1005
- opts, name = from_value
1006
- case opts
1007
- when Relation
1008
- name ||= 'subquery'
1009
- self.bind_values = opts.bind_values + self.bind_values
1010
- opts.arel.as(name.to_s)
1011
- else
1012
- opts
1272
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1273
+
1274
+ unless association_joins.empty? && stashed_joins.empty?
1275
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1276
+ join_dependency = construct_join_dependency(association_joins, join_type)
1277
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1278
+ end
1279
+
1280
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1281
+ join_sources
1013
1282
  end
1014
- end
1015
1283
 
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
1284
+ def build_select(arel)
1285
+ if select_values.any?
1286
+ arel.project(*arel_columns(select_values))
1287
+ elsif klass.ignored_columns.any?
1288
+ arel.project(*klass.column_names.map { |field| table[field] })
1027
1289
  else
1028
- raise 'unknown class: %s' % join.class.name
1290
+ arel.project(table[Arel.star])
1029
1291
  end
1030
1292
  end
1031
1293
 
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)
1038
-
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
1294
+ def arel_columns(columns)
1295
+ columns.flat_map do |field|
1296
+ case field
1297
+ when Symbol
1298
+ arel_column(field.to_s) do |attr_name|
1299
+ connection.quote_table_name(attr_name)
1300
+ end
1301
+ when String
1302
+ arel_column(field, &:itself)
1303
+ when Proc
1304
+ field.call
1305
+ else
1306
+ field
1307
+ end
1308
+ end
1309
+ end
1044
1310
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
1311
+ def arel_column(field)
1312
+ field = klass.attribute_aliases[field] || field
1313
+ from = from_clause.name || from_clause.value
1046
1314
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
1315
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1316
+ table[field]
1317
+ elsif field.match?(/\A\w+\.\w+\z/)
1318
+ table, column = field.split(".")
1319
+ predicate_builder.resolve_arel_attribute(table, column) do
1320
+ lookup_table_klass_from_join_dependencies(table)
1321
+ end
1322
+ else
1323
+ yield field
1324
+ end
1050
1325
  end
1051
1326
 
1052
- manager.join_sources.concat(join_list)
1327
+ def table_name_matches?(from)
1328
+ table_name = Regexp.escape(table.name)
1329
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1330
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1331
+ end
1053
1332
 
1054
- manager
1055
- end
1333
+ def reverse_sql_order(order_query)
1334
+ if order_query.empty?
1335
+ return [table[primary_key].desc] if primary_key
1336
+ raise IrreversibleOrderError,
1337
+ "Relation has no current order and table has no primary key to be used as default order"
1338
+ end
1056
1339
 
1057
- def build_select(arel, selects)
1058
- if !selects.empty?
1059
- expanded_select = selects.map do |field|
1060
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
1061
- arel_table[field]
1340
+ order_query.flat_map do |o|
1341
+ case o
1342
+ when Arel::Attribute
1343
+ o.desc
1344
+ when Arel::Nodes::Ordering
1345
+ o.reverse
1346
+ when Arel::Nodes::NodeExpression
1347
+ o.desc
1348
+ when String
1349
+ if does_not_support_reverse?(o)
1350
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1351
+ end
1352
+ o.split(",").map! do |s|
1353
+ s.strip!
1354
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1355
+ end
1062
1356
  else
1063
- field
1357
+ o
1064
1358
  end
1065
1359
  end
1360
+ end
1066
1361
 
1067
- arel.project(*expanded_select)
1068
- else
1069
- arel.project(@klass.arel_table[Arel.star])
1362
+ def does_not_support_reverse?(order)
1363
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
1364
+ # override methods like #count.
1365
+ order = String.new(order) unless order.instance_of?(String)
1366
+
1367
+ # Uses SQL function with multiple arguments.
1368
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1369
+ # Uses "nulls first" like construction.
1370
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1371
+ end
1372
+
1373
+ def build_order(arel)
1374
+ orders = order_values.compact_blank
1375
+ arel.order(*orders) unless orders.empty?
1070
1376
  end
1071
- end
1072
1377
 
1073
- def reverse_sql_order(order_query)
1074
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1378
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1379
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1075
1380
 
1076
- order_query.flat_map do |o|
1077
- case o
1078
- when Arel::Nodes::Ordering
1079
- o.reverse
1080
- when String
1081
- o.to_s.split(',').map! do |s|
1082
- s.strip!
1083
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
1381
+ def validate_order_args(args)
1382
+ args.each do |arg|
1383
+ next unless arg.is_a?(Hash)
1384
+ arg.each do |_key, value|
1385
+ unless VALID_DIRECTIONS.include?(value)
1386
+ raise ArgumentError,
1387
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1388
+ end
1084
1389
  end
1085
- else
1086
- o
1087
1390
  end
1088
1391
  end
1089
- end
1090
1392
 
1091
- def array_of_strings?(o)
1092
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
1093
- end
1094
-
1095
- def build_order(arel)
1096
- orders = order_values.uniq
1097
- orders.reject!(&:blank?)
1393
+ def preprocess_order_args(order_args)
1394
+ @klass.disallow_raw_sql!(
1395
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1396
+ permit: connection.column_name_with_order_matcher
1397
+ )
1398
+
1399
+ validate_order_args(order_args)
1400
+
1401
+ references = column_references(order_args)
1402
+ self.references_values |= references unless references.empty?
1403
+
1404
+ # if a symbol is given we prepend the quoted table name
1405
+ order_args.map! do |arg|
1406
+ case arg
1407
+ when Symbol
1408
+ order_column(arg.to_s).asc
1409
+ when Hash
1410
+ arg.map { |field, dir|
1411
+ case field
1412
+ when Arel::Nodes::SqlLiteral
1413
+ field.public_send(dir.downcase)
1414
+ else
1415
+ order_column(field.to_s).public_send(dir.downcase)
1416
+ end
1417
+ }
1418
+ else
1419
+ arg
1420
+ end
1421
+ end.flatten!
1422
+ end
1098
1423
 
1099
- arel.order(*orders) unless orders.empty?
1100
- end
1424
+ def sanitize_order_arguments(order_args)
1425
+ order_args.map! do |arg|
1426
+ klass.sanitize_sql_for_order(arg)
1427
+ end
1428
+ order_args.flatten!
1429
+ order_args.compact_blank!
1430
+ end
1101
1431
 
1102
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1103
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
1432
+ def column_references(order_args)
1433
+ references = order_args.grep(String)
1434
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1435
+ references
1436
+ end
1104
1437
 
1105
- def validate_order_args(args)
1106
- args.each do |arg|
1107
- next unless arg.is_a?(Hash)
1108
- arg.each do |_key, value|
1109
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
1110
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
1438
+ def order_column(field)
1439
+ arel_column(field) do |attr_name|
1440
+ if attr_name == "count" && !group_values.empty?
1441
+ table[attr_name]
1442
+ else
1443
+ Arel.sql(connection.quote_table_name(attr_name))
1444
+ end
1111
1445
  end
1112
1446
  end
1113
- end
1114
1447
 
1115
- def preprocess_order_args(order_args)
1116
- order_args.flatten!
1117
- validate_order_args(order_args)
1118
-
1119
- references = order_args.grep(String)
1120
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1121
- references!(references) if references.any?
1448
+ def resolve_arel_attributes(attrs)
1449
+ attrs.flat_map do |attr|
1450
+ case attr
1451
+ when Arel::Predications
1452
+ attr
1453
+ when Hash
1454
+ attr.flat_map do |table, columns|
1455
+ table = table.to_s
1456
+ Array(columns).map do |column|
1457
+ predicate_builder.resolve_arel_attribute(table, column)
1458
+ end
1459
+ end
1460
+ else
1461
+ attr = attr.to_s
1462
+ if attr.include?(".")
1463
+ table, column = attr.split(".", 2)
1464
+ predicate_builder.resolve_arel_attribute(table, column)
1465
+ else
1466
+ attr
1467
+ end
1468
+ end
1469
+ end
1470
+ end
1122
1471
 
1123
- # if a symbol is given we prepend the quoted table name
1124
- order_args.map! do |arg|
1125
- case arg
1126
- when Symbol
1127
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
1128
- table[arg].asc
1129
- when Hash
1130
- arg.map { |field, dir|
1131
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1132
- table[field].send(dir.downcase)
1133
- }
1472
+ # Checks to make sure that the arguments are not blank. Note that if some
1473
+ # blank-like object were initially passed into the query method, then this
1474
+ # method will not raise an error.
1475
+ #
1476
+ # Example:
1477
+ #
1478
+ # Post.references() # raises an error
1479
+ # Post.references([]) # does not raise an error
1480
+ #
1481
+ # This particular method should be called with a method_name and the args
1482
+ # passed into that method as an input. For example:
1483
+ #
1484
+ # def references(*args)
1485
+ # check_if_method_has_arguments!("references", args)
1486
+ # ...
1487
+ # end
1488
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1489
+ if args.blank?
1490
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1491
+ elsif block_given?
1492
+ yield args
1134
1493
  else
1135
- arg
1494
+ args.flatten!
1495
+ args.compact_blank!
1136
1496
  end
1137
- end.flatten!
1138
- end
1139
-
1140
- # Checks to make sure that the arguments are not blank. Note that if some
1141
- # blank-like object were initially passed into the query method, then this
1142
- # method will not raise an error.
1143
- #
1144
- # Example:
1145
- #
1146
- # Post.references() # => raises an error
1147
- # Post.references([]) # => does not raise an error
1148
- #
1149
- # This particular method should be called with a method_name and the args
1150
- # passed into that method as an input. For example:
1151
- #
1152
- # def references(*args)
1153
- # check_if_method_has_arguments!("references", args)
1154
- # ...
1155
- # end
1156
- def check_if_method_has_arguments!(method_name, args)
1157
- if args.blank?
1158
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1159
1497
  end
1160
- end
1161
1498
 
1162
- # This function is recursive just for better readablity.
1163
- # #where argument doesn't support more than one level nested hash in real world.
1164
- def add_relations_to_bind_values(attributes)
1165
- if attributes.is_a?(Hash)
1166
- attributes.each_value do |value|
1167
- if value.is_a?(ActiveRecord::Relation)
1168
- self.bind_values += value.bind_values
1169
- else
1170
- add_relations_to_bind_values(value)
1499
+ STRUCTURAL_VALUE_METHODS = (
1500
+ Relation::VALUE_METHODS -
1501
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1502
+ ).freeze # :nodoc:
1503
+
1504
+ def structurally_incompatible_values_for(other)
1505
+ values = other.values
1506
+ STRUCTURAL_VALUE_METHODS.reject do |method|
1507
+ v1, v2 = @values[method], values[method]
1508
+ if v1.is_a?(Array)
1509
+ next true unless v2.is_a?(Array)
1510
+ v1 = v1.uniq
1511
+ v2 = v2.uniq
1171
1512
  end
1513
+ v1 == v2
1172
1514
  end
1173
1515
  end
1516
+ end
1517
+
1518
+ class Relation # :nodoc:
1519
+ # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1520
+ # TODO: Remove the class once Rails 6.1 has released.
1521
+ class WhereClauseFactory # :nodoc:
1174
1522
  end
1175
1523
  end
1176
1524
  end