activerecord 4.2.0 → 6.1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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