activerecord 4.2.8 → 6.0.0

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

Potentially problematic release.


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

Files changed (372) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +612 -1583
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +13 -12
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record.rb +41 -22
  8. data/lib/active_record/aggregations.rb +267 -251
  9. data/lib/active_record/association_relation.rb +11 -6
  10. data/lib/active_record/associations.rb +1737 -1597
  11. data/lib/active_record/associations/alias_tracker.rb +29 -35
  12. data/lib/active_record/associations/association.rb +125 -58
  13. data/lib/active_record/associations/association_scope.rb +103 -132
  14. data/lib/active_record/associations/belongs_to_association.rb +65 -60
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -40
  17. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  18. data/lib/active_record/associations/builder/collection_association.rb +10 -33
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +52 -66
  20. data/lib/active_record/associations/builder/has_many.rb +8 -4
  21. data/lib/active_record/associations/builder/has_one.rb +46 -5
  22. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  23. data/lib/active_record/associations/collection_association.rb +134 -286
  24. data/lib/active_record/associations/collection_proxy.rb +241 -146
  25. data/lib/active_record/associations/foreign_association.rb +10 -1
  26. data/lib/active_record/associations/has_many_association.rb +34 -97
  27. data/lib/active_record/associations/has_many_through_association.rb +60 -87
  28. data/lib/active_record/associations/has_one_association.rb +61 -49
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +137 -167
  31. data/lib/active_record/associations/join_dependency/join_association.rb +38 -88
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +90 -92
  35. data/lib/active_record/associations/preloader/association.rb +90 -123
  36. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  37. data/lib/active_record/associations/singular_association.rb +18 -39
  38. data/lib/active_record/associations/through_association.rb +38 -18
  39. data/lib/active_record/attribute_assignment.rb +56 -183
  40. data/lib/active_record/attribute_decorators.rb +39 -15
  41. data/lib/active_record/attribute_methods.rb +120 -135
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -8
  43. data/lib/active_record/attribute_methods/dirty.rb +174 -144
  44. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  45. data/lib/active_record/attribute_methods/query.rb +6 -5
  46. data/lib/active_record/attribute_methods/read.rb +20 -76
  47. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  49. data/lib/active_record/attribute_methods/write.rb +32 -54
  50. data/lib/active_record/attributes.rb +214 -82
  51. data/lib/active_record/autosave_association.rb +91 -37
  52. data/lib/active_record/base.rb +57 -45
  53. data/lib/active_record/callbacks.rb +100 -74
  54. data/lib/active_record/coders/json.rb +3 -1
  55. data/lib/active_record/coders/yaml_column.rb +24 -12
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -296
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -115
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +356 -227
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +664 -244
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -83
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +460 -204
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +510 -627
  69. data/lib/active_record/connection_adapters/column.rb +56 -43
  70. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -188
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -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 +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  100. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +470 -290
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +551 -356
  117. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -345
  127. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  128. data/lib/active_record/connection_handling.rb +176 -41
  129. data/lib/active_record/core.rb +251 -231
  130. data/lib/active_record/counter_cache.rb +67 -49
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +87 -105
  137. data/lib/active_record/enum.rb +163 -86
  138. data/lib/active_record/errors.rb +188 -53
  139. data/lib/active_record/explain.rb +23 -11
  140. data/lib/active_record/explain_registry.rb +4 -2
  141. data/lib/active_record/explain_subscriber.rb +10 -5
  142. data/lib/active_record/fixture_set/file.rb +35 -9
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +228 -499
  148. data/lib/active_record/gem_version.rb +5 -3
  149. data/lib/active_record/inheritance.rb +158 -112
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +123 -29
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  154. data/lib/active_record/locale/en.yml +3 -2
  155. data/lib/active_record/locking/optimistic.rb +87 -96
  156. data/lib/active_record/locking/pessimistic.rb +18 -6
  157. data/lib/active_record/log_subscriber.rb +76 -33
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +626 -283
  162. data/lib/active_record/migration/command_recorder.rb +177 -90
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +8 -6
  165. data/lib/active_record/model_schema.rb +314 -112
  166. data/lib/active_record/nested_attributes.rb +264 -222
  167. data/lib/active_record/no_touching.rb +14 -1
  168. data/lib/active_record/null_relation.rb +24 -37
  169. data/lib/active_record/persistence.rb +557 -125
  170. data/lib/active_record/query_cache.rb +19 -23
  171. data/lib/active_record/querying.rb +43 -29
  172. data/lib/active_record/railtie.rb +147 -46
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +2 -0
  175. data/lib/active_record/railties/controller_runtime.rb +34 -33
  176. data/lib/active_record/railties/databases.rake +330 -197
  177. data/lib/active_record/readonly_attributes.rb +5 -4
  178. data/lib/active_record/reflection.rb +428 -279
  179. data/lib/active_record/relation.rb +518 -341
  180. data/lib/active_record/relation/batches.rb +207 -55
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +267 -253
  183. data/lib/active_record/relation/delegation.rb +70 -80
  184. data/lib/active_record/relation/finder_methods.rb +277 -241
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +78 -87
  187. data/lib/active_record/relation/predicate_builder.rb +114 -119
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +575 -394
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +11 -13
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +79 -42
  202. data/lib/active_record/runtime_registry.rb +6 -4
  203. data/lib/active_record/sanitization.rb +144 -121
  204. data/lib/active_record/schema.rb +21 -24
  205. data/lib/active_record/schema_dumper.rb +112 -93
  206. data/lib/active_record/schema_migration.rb +24 -17
  207. data/lib/active_record/scoping.rb +45 -26
  208. data/lib/active_record/scoping/default.rb +101 -85
  209. data/lib/active_record/scoping/named.rb +86 -33
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +5 -5
  212. data/lib/active_record/statement_cache.rb +73 -36
  213. data/lib/active_record/store.rb +127 -42
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +308 -99
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +55 -99
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -41
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +224 -0
  222. data/lib/active_record/timestamp.rb +86 -40
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +216 -150
  225. data/lib/active_record/translation.rb +3 -1
  226. data/lib/active_record/type.rb +78 -23
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +4 -45
  229. data/lib/active_record/type/date_time.rb +4 -49
  230. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  231. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +24 -15
  235. data/lib/active_record/type/text.rb +2 -2
  236. data/lib/active_record/type/time.rb +11 -16
  237. data/lib/active_record/type/type_map.rb +15 -17
  238. data/lib/active_record/type/unsigned_integer.rb +9 -7
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +39 -35
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +13 -4
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +14 -13
  247. data/lib/active_record/validations/uniqueness.rb +42 -55
  248. data/lib/active_record/version.rb +3 -1
  249. data/lib/arel.rb +51 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +7 -5
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +31 -1
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
  337. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  340. metadata +164 -60
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_set.rb +0 -81
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  352. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  353. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  354. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  355. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  356. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  359. data/lib/active_record/type/big_integer.rb +0 -13
  360. data/lib/active_record/type/binary.rb +0 -50
  361. data/lib/active_record/type/boolean.rb +0 -31
  362. data/lib/active_record/type/decimal.rb +0 -58
  363. data/lib/active_record/type/decorator.rb +0 -14
  364. data/lib/active_record/type/float.rb +0 -19
  365. data/lib/active_record/type/integer.rb +0 -59
  366. data/lib/active_record/type/mutable.rb +0 -16
  367. data/lib/active_record/type/numeric.rb +0 -36
  368. data/lib/active_record/type/string.rb +0 -40
  369. data/lib/active_record/type/time_value.rb +0 -38
  370. data/lib/active_record/type/value.rb +0 -110
  371. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  372. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,48 +1,49 @@
1
- require 'active_support/core_ext/string/filters'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract"
2
4
 
3
5
  module ActiveRecord
4
6
  class PredicateBuilder
5
7
  class ArrayHandler # :nodoc:
6
- def call(attribute, value)
7
- values = value.map { |x| x.is_a?(Base) ? x.id : x }
8
- nils, values = values.partition(&:nil?)
9
-
10
- if values.any? { |val| val.is_a?(Array) }
11
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
12
- Passing a nested array to Active Record finder methods is
13
- deprecated and will be removed. Flatten your array before using
14
- it for 'IN' conditions.
15
- MSG
16
-
17
- flat_values = values.flatten
18
- values = flat_values unless flat_values.include?(nil)
19
- end
8
+ def initialize(predicate_builder)
9
+ @predicate_builder = predicate_builder
10
+ end
20
11
 
21
- return attribute.in([]) if values.empty? && nils.empty?
12
+ def call(attribute, value)
13
+ return attribute.in([]) if value.empty?
22
14
 
23
- ranges, values = values.partition { |v| v.is_a?(Range) }
15
+ values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
+ nils = values.extract!(&:nil?)
17
+ ranges = values.extract! { |v| v.is_a?(Range) }
24
18
 
25
19
  values_predicate =
26
20
  case values.length
27
21
  when 0 then NullPredicate
28
- when 1 then attribute.eq(values.first)
29
- else attribute.in(values)
22
+ when 1 then predicate_builder.build(attribute, values.first)
23
+ else
24
+ values.map! do |v|
25
+ predicate_builder.build_bind_attribute(attribute.name, v)
26
+ end
27
+ values.empty? ? NullPredicate : attribute.in(values)
30
28
  end
31
29
 
32
30
  unless nils.empty?
33
- values_predicate = values_predicate.or(attribute.eq(nil))
31
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
34
32
  end
35
33
 
36
- array_predicates = ranges.map { |range| attribute.between(range) }
34
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
37
35
  array_predicates.unshift(values_predicate)
38
- array_predicates.inject { |composite, predicate| composite.or(predicate) }
36
+ array_predicates.inject(&:or)
39
37
  end
40
38
 
41
- module NullPredicate # :nodoc:
42
- def self.or(other)
43
- other
39
+ private
40
+ attr_reader :predicate_builder
41
+
42
+ module NullPredicate # :nodoc:
43
+ def self.or(other)
44
+ other
45
+ end
44
46
  end
45
- end
46
47
  end
47
48
  end
48
49
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class AssociationQueryValue # :nodoc:
6
+ def initialize(associated_table, value)
7
+ @associated_table = associated_table
8
+ @value = value
9
+ end
10
+
11
+ def queries
12
+ [associated_table.association_join_foreign_key.to_s => ids]
13
+ end
14
+
15
+ private
16
+ attr_reader :associated_table, :value
17
+
18
+ def ids
19
+ case value
20
+ when Relation
21
+ value.select_values.empty? ? value.select(primary_key) : value
22
+ when Array
23
+ value.map { |v| convert_to_id(v) }
24
+ else
25
+ convert_to_id(value)
26
+ end
27
+ end
28
+
29
+ def primary_key
30
+ associated_table.association_join_primary_key
31
+ end
32
+
33
+ def convert_to_id(value)
34
+ case value
35
+ when Base
36
+ value._read_attribute(primary_key)
37
+ else
38
+ value
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class BaseHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ predicate_builder.build(attribute, value.id)
12
+ end
13
+
14
+ private
15
+ attr_reader :predicate_builder
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class BasicObjectHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ bind = predicate_builder.build_bind_attribute(attribute.name, value)
12
+ attribute.eq(bind)
13
+ end
14
+
15
+ private
16
+ attr_reader :predicate_builder
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class PolymorphicArrayValue # :nodoc:
6
+ def initialize(associated_table, values)
7
+ @associated_table = associated_table
8
+ @values = values
9
+ end
10
+
11
+ def queries
12
+ type_to_ids_mapping.map do |type, ids|
13
+ {
14
+ associated_table.association_foreign_type.to_s => type,
15
+ associated_table.association_foreign_key.to_s => ids
16
+ }
17
+ end
18
+ end
19
+
20
+ private
21
+ attr_reader :associated_table, :values
22
+
23
+ def type_to_ids_mapping
24
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
25
+ values.each_with_object(default_hash) do |value, hash|
26
+ hash[klass(value).polymorphic_name] << convert_to_id(value)
27
+ end
28
+ end
29
+
30
+ def primary_key(value)
31
+ associated_table.association_join_primary_key(klass(value))
32
+ end
33
+
34
+ def klass(value)
35
+ case value
36
+ when Base
37
+ value.class
38
+ when Relation
39
+ value.klass
40
+ end
41
+ end
42
+
43
+ def convert_to_id(value)
44
+ case value
45
+ when Base
46
+ value._read_attribute(primary_key(value))
47
+ when Relation
48
+ value.select(primary_key(value))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class RangeHandler # :nodoc:
6
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
7
+
8
+ def initialize(predicate_builder)
9
+ @predicate_builder = predicate_builder
10
+ end
11
+
12
+ def call(attribute, value)
13
+ begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
14
+ end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
15
+ attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
16
+ end
17
+
18
+ private
19
+ attr_reader :predicate_builder
20
+ end
21
+ end
22
+ end
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder
3
5
  class RelationHandler # :nodoc:
4
6
  def call(attribute, value)
7
+ if value.eager_loading?
8
+ value = value.send(:apply_join_dependency)
9
+ end
10
+
5
11
  if value.select_values.empty?
6
- value = value.select(value.klass.arel_table[value.klass.primary_key])
12
+ value = value.select(value.arel_attribute(value.klass.primary_key))
7
13
  end
8
14
 
9
15
  attribute.in(value.arel)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute"
4
+
5
+ module ActiveRecord
6
+ class Relation
7
+ class QueryAttribute < ActiveModel::Attribute # :nodoc:
8
+ def type_cast(value)
9
+ value
10
+ end
11
+
12
+ def value_for_database
13
+ @value_for_database ||= super
14
+ end
15
+
16
+ def with_cast_value(value)
17
+ QueryAttribute.new(name, value, type)
18
+ end
19
+
20
+ def nil?
21
+ unless value_before_type_cast.is_a?(StatementCache::Substitute)
22
+ value_before_type_cast.nil? ||
23
+ type.respond_to?(:subtype, true) && value_for_database.nil?
24
+ end
25
+ rescue ::RangeError
26
+ end
27
+
28
+ def infinite?
29
+ infinity?(value_before_type_cast) || infinity?(value_for_database)
30
+ rescue ::RangeError
31
+ end
32
+
33
+ def unboundable?
34
+ if defined?(@_unboundable)
35
+ @_unboundable
36
+ else
37
+ value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
38
+ @_unboundable = nil
39
+ end
40
+ rescue ::RangeError
41
+ @_unboundable = type.cast(value_before_type_cast) <=> 0
42
+ end
43
+
44
+ private
45
+ def infinity?(value)
46
+ value.respond_to?(:infinite?) && value.infinite?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -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_record/relation/where_clause_factory"
7
+ require "active_model/forbidden_attributes_protection"
4
8
 
5
9
  module ActiveRecord
6
10
  module QueryMethods
@@ -11,6 +15,8 @@ module ActiveRecord
11
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
12
16
  # In this case, #where must be chained with #not to return a new relation.
13
17
  class WhereChain
18
+ include ActiveModel::ForbiddenAttributesProtection
19
+
14
20
  def initialize(scope)
15
21
  @scope = scope
16
22
  end
@@ -18,7 +24,7 @@ module ActiveRecord
18
24
  # Returns a new relation expressing WHERE + NOT condition according to
19
25
  # the conditions in the arguments.
20
26
  #
21
- # +not+ accepts conditions as a string, array, or hash. See #where for
27
+ # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
22
28
  # more details on each format.
23
29
  #
24
30
  # User.where.not("name = 'Jon'")
@@ -35,77 +41,56 @@ module ActiveRecord
35
41
  #
36
42
  # User.where.not(name: %w(Ko1 Nobu))
37
43
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
38
- #
39
- # User.where.not(name: "Jon", role: "admin")
40
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
41
44
  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
55
- end
45
+ opts = sanitize_forbidden_attributes(opts)
46
+
47
+ where_clause = @scope.send(:where_clause_factory).build(opts, rest)
56
48
 
57
49
  @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
58
- @scope.where_values += where_value
50
+
51
+ if not_behaves_as_nor?(opts)
52
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
53
+ NOT conditions will no longer behave as NOR in Rails 6.1.
54
+ To continue using NOR conditions, NOT each conditions manually
55
+ (`#{ opts.keys.map { |key| ".where.not(#{key.inspect} => ...)" }.join }`).
56
+ MSG
57
+ @scope.where_clause += where_clause.invert(:nor)
58
+ else
59
+ @scope.where_clause += where_clause.invert
60
+ end
61
+
59
62
  @scope
60
63
  end
61
- end
62
64
 
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
65
+ private
66
+ def not_behaves_as_nor?(opts)
67
+ opts.is_a?(Hash) && opts.size > 1
68
+ end
75
69
  end
76
70
 
77
- (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
71
+ FROZEN_EMPTY_ARRAY = [].freeze
72
+ FROZEN_EMPTY_HASH = {}.freeze
73
+
74
+ Relation::VALUE_METHODS.each do |name|
75
+ method_name = \
76
+ case name
77
+ when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
78
+ when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
79
+ when *Relation::CLAUSE_METHODS then "#{name}_clause"
80
+ end
78
81
  class_eval <<-CODE, __FILE__, __LINE__ + 1
79
- def #{name}_value # def readonly_value
80
- @values[:#{name}] # @values[:readonly]
82
+ def #{method_name} # def includes_values
83
+ default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
84
+ @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
81
85
  end # end
82
- CODE
83
- end
84
86
 
85
- Relation::SINGLE_VALUE_METHODS.each do |name|
86
- 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
87
+ def #{method_name}=(value) # def includes_values=(value)
88
+ assert_mutability! # assert_mutability!
89
+ @values[:#{name}] = value # @values[:includes] = value
91
90
  end # end
92
91
  CODE
93
92
  end
94
93
 
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
94
  alias extensions extending_values
110
95
 
111
96
  # Specify relationships to be included in the result set. For
@@ -118,7 +103,7 @@ module ActiveRecord
118
103
  #
119
104
  # allows you to access the +address+ attribute of the +User+ model without
120
105
  # firing an additional query. This will often result in a
121
- # performance improvement over a simple +join+.
106
+ # performance improvement over a simple join.
122
107
  #
123
108
  # You can also specify multiple relationships, like this:
124
109
  #
@@ -130,7 +115,7 @@ module ActiveRecord
130
115
  #
131
116
  # === conditions
132
117
  #
133
- # If you want to add conditions to your included models you'll have
118
+ # If you want to add string conditions to your included models, you'll have
134
119
  # to explicitly reference them. For example:
135
120
  #
136
121
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -139,8 +124,14 @@ module ActiveRecord
139
124
  #
140
125
  # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
141
126
  #
142
- # Note that +includes+ works with association names while +references+ needs
127
+ # Note that #includes works with association names while #references needs
143
128
  # the actual table name.
129
+ #
130
+ # If you pass the conditions via hash, you don't need to call #references
131
+ # explicitly, as #where references the tables for you. For example, this
132
+ # will work correctly:
133
+ #
134
+ # User.includes(:posts).where(posts: { name: 'example' })
144
135
  def includes(*args)
145
136
  check_if_method_has_arguments!(:includes, args)
146
137
  spawn.includes!(*args)
@@ -157,9 +148,9 @@ module ActiveRecord
157
148
  # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
158
149
  #
159
150
  # 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"
151
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
152
+ # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
153
+ # # "users"."id"
163
154
  def eager_load(*args)
164
155
  check_if_method_has_arguments!(:eager_load, args)
165
156
  spawn.eager_load!(*args)
@@ -170,10 +161,10 @@ module ActiveRecord
170
161
  self
171
162
  end
172
163
 
173
- # Allows preloading of +args+, in the same way that +includes+ does:
164
+ # Allows preloading of +args+, in the same way that #includes does:
174
165
  #
175
166
  # User.preload(:posts)
176
- # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
167
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
177
168
  def preload(*args)
178
169
  check_if_method_has_arguments!(:preload, args)
179
170
  spawn.preload!(*args)
@@ -184,16 +175,29 @@ module ActiveRecord
184
175
  self
185
176
  end
186
177
 
178
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
179
+ # then the individual association records are collected from the relation. Like so:
180
+ #
181
+ # account.memberships.extract_associated(:user)
182
+ # # => Returns collection of User records
183
+ #
184
+ # This is short-hand for:
185
+ #
186
+ # account.memberships.preload(:user).collect(&:user)
187
+ def extract_associated(association)
188
+ preload(association).collect(&association)
189
+ end
190
+
187
191
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
188
192
  # and should therefore be JOINed in any query rather than loaded separately.
189
- # This method only works in conjunction with +includes+.
193
+ # This method only works in conjunction with #includes.
190
194
  # See #includes for more details.
191
195
  #
192
196
  # User.includes(:posts).where("posts.name = 'foo'")
193
- # # => Doesn't JOIN the posts table, resulting in an error.
197
+ # # Doesn't JOIN the posts table, resulting in an error.
194
198
  #
195
199
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
196
- # # => Query now knows the string references posts, so adds a JOIN
200
+ # # Query now knows the string references posts, so adds a JOIN
197
201
  def references(*table_names)
198
202
  check_if_method_has_arguments!(:references, table_names)
199
203
  spawn.references!(*table_names)
@@ -209,12 +213,13 @@ module ActiveRecord
209
213
 
210
214
  # Works in two unique ways.
211
215
  #
212
- # First: takes a block so it can be used just like Array#select.
216
+ # First: takes a block so it can be used just like <tt>Array#select</tt>.
213
217
  #
214
218
  # Model.all.select { |m| m.field == value }
215
219
  #
216
220
  # 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.
221
+ # converting them into an array and iterating through them using
222
+ # <tt>Array#select</tt>.
218
223
  #
219
224
  # Second: Modifies the SELECT statement for the query so that only certain
220
225
  # fields are retrieved:
@@ -242,47 +247,71 @@ module ActiveRecord
242
247
  # # => "value"
243
248
  #
244
249
  # Accessing attributes of an object that do not have fields retrieved by a select
245
- # except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
250
+ # except +id+ will throw ActiveModel::MissingAttributeError:
246
251
  #
247
252
  # Model.select(:field).first.other_field
248
253
  # # => ActiveModel::MissingAttributeError: missing attribute: other_field
249
254
  def select(*fields)
250
255
  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)
256
+ if fields.any?
257
+ raise ArgumentError, "`select' with block doesn't take arguments."
258
+ end
259
+
260
+ return super()
255
261
  end
262
+
263
+ raise ArgumentError, "Call `select' with at least one field" if fields.empty?
264
+ spawn._select!(*fields)
256
265
  end
257
266
 
258
267
  def _select!(*fields) # :nodoc:
268
+ fields.reject!(&:blank?)
259
269
  fields.flatten!
260
- fields.map! do |field|
261
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
262
- end
263
270
  self.select_values += fields
264
271
  self
265
272
  end
266
273
 
274
+ # Allows you to change a previously set select statement.
275
+ #
276
+ # Post.select(:title, :body)
277
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
278
+ #
279
+ # Post.select(:title, :body).reselect(:created_at)
280
+ # # SELECT `posts`.`created_at` FROM `posts`
281
+ #
282
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
283
+ # Note that we're unscoping the entire select statement.
284
+ def reselect(*args)
285
+ check_if_method_has_arguments!(:reselect, args)
286
+ spawn.reselect!(*args)
287
+ end
288
+
289
+ # Same as #reselect but operates on relation in-place instead of copying.
290
+ def reselect!(*args) # :nodoc:
291
+ self.select_values = args
292
+ self
293
+ end
294
+
267
295
  # Allows to specify a group attribute:
268
296
  #
269
297
  # User.group(:name)
270
- # => SELECT "users".* FROM "users" GROUP BY name
298
+ # # SELECT "users".* FROM "users" GROUP BY name
271
299
  #
272
300
  # Returns an array with distinct records based on the +group+ attribute:
273
301
  #
274
302
  # User.select([:id, :name])
275
- # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
303
+ # # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
276
304
  #
277
305
  # User.group(:name)
278
- # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
306
+ # # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
279
307
  #
280
308
  # 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, ...>]
309
+ # # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
282
310
  #
283
311
  # Passing in an array of attributes to group by is also supported.
312
+ #
284
313
  # 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">]
314
+ # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
286
315
  def group(*args)
287
316
  check_if_method_has_arguments!(:group, args)
288
317
  spawn.group!(*args)
@@ -298,27 +327,28 @@ module ActiveRecord
298
327
  # Allows to specify an order attribute:
299
328
  #
300
329
  # User.order(:name)
301
- # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
330
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
302
331
  #
303
332
  # User.order(email: :desc)
304
- # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
333
+ # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
305
334
  #
306
335
  # User.order(:name, email: :desc)
307
- # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
336
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
308
337
  #
309
338
  # User.order('name')
310
- # => SELECT "users".* FROM "users" ORDER BY name
339
+ # # SELECT "users".* FROM "users" ORDER BY name
311
340
  #
312
341
  # User.order('name DESC')
313
- # => SELECT "users".* FROM "users" ORDER BY name DESC
342
+ # # SELECT "users".* FROM "users" ORDER BY name DESC
314
343
  #
315
344
  # User.order('name DESC, email')
316
- # => SELECT "users".* FROM "users" ORDER BY name DESC, email
345
+ # # SELECT "users".* FROM "users" ORDER BY name DESC, email
317
346
  def order(*args)
318
347
  check_if_method_has_arguments!(:order, args)
319
348
  spawn.order!(*args)
320
349
  end
321
350
 
351
+ # Same as #order but operates on relation in-place instead of copying.
322
352
  def order!(*args) # :nodoc:
323
353
  preprocess_order_args(args)
324
354
 
@@ -340,8 +370,9 @@ module ActiveRecord
340
370
  spawn.reorder!(*args)
341
371
  end
342
372
 
373
+ # Same as #reorder but operates on relation in-place instead of copying.
343
374
  def reorder!(*args) # :nodoc:
344
- preprocess_order_args(args)
375
+ preprocess_order_args(args) unless args.all?(&:blank?)
345
376
 
346
377
  self.reordering_value = true
347
378
  self.order_values = args
@@ -349,8 +380,8 @@ module ActiveRecord
349
380
  end
350
381
 
351
382
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
352
- :limit, :offset, :joins, :includes, :from,
353
- :readonly, :having])
383
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
384
+ :includes, :from, :readonly, :having, :optimizer_hints])
354
385
 
355
386
  # Removes an unwanted relation that is already defined on a chain of relations.
356
387
  # This is useful when passing around chains of relations and would like to
@@ -365,15 +396,15 @@ module ActiveRecord
365
396
  # User.order('email DESC').select('id').where(name: "John")
366
397
  # .unscope(:order, :select, :where) == User.all
367
398
  #
368
- # One can additionally pass a hash as an argument to unscope specific :where values.
399
+ # One can additionally pass a hash as an argument to unscope specific +:where+ values.
369
400
  # 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:
401
+ # +:where+ and the value should be the where value to unscope. For example:
371
402
  #
372
403
  # User.where(name: "John", active: true).unscope(where: :name)
373
404
  # == User.where(active: true)
374
405
  #
375
- # This method is similar to <tt>except</tt>, but unlike
376
- # <tt>except</tt>, it persists across merges:
406
+ # This method is similar to #except, but unlike
407
+ # #except, it persists across merges:
377
408
  #
378
409
  # User.order('email').merge(User.except(:order))
379
410
  # == User.order('email')
@@ -383,7 +414,7 @@ module ActiveRecord
383
414
  #
384
415
  # This means it can be used in association definitions:
385
416
  #
386
- # has_many :comments, -> { unscope where: :trashed }
417
+ # has_many :comments, -> { unscope(where: :trashed) }
387
418
  #
388
419
  def unscope(*args)
389
420
  check_if_method_has_arguments!(:unscope, args)
@@ -397,16 +428,20 @@ module ActiveRecord
397
428
  args.each do |scope|
398
429
  case scope
399
430
  when Symbol
400
- symbol_unscoping(scope)
431
+ scope = :left_outer_joins if scope == :left_joins
432
+ if !VALID_UNSCOPING_VALUES.include?(scope)
433
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
434
+ end
435
+ assert_mutability!
436
+ @values[scope] = DEFAULT_VALUES[scope]
401
437
  when Hash
402
438
  scope.each do |key, target_value|
403
439
  if key != :where
404
440
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
405
441
  end
406
442
 
407
- Array(target_value).each do |val|
408
- where_unscoping(val)
409
- end
443
+ target_values = Array(target_value).map(&:to_s)
444
+ self.where_clause = where_clause.except(*target_values)
410
445
  end
411
446
  else
412
447
  raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
@@ -416,15 +451,35 @@ module ActiveRecord
416
451
  self
417
452
  end
418
453
 
419
- # Performs a joins on +args+:
454
+ # Performs a joins on +args+. The given symbol(s) should match the name of
455
+ # the association(s).
420
456
  #
421
457
  # User.joins(:posts)
422
- # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
458
+ # # SELECT "users".*
459
+ # # FROM "users"
460
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
461
+ #
462
+ # Multiple joins:
463
+ #
464
+ # User.joins(:posts, :account)
465
+ # # SELECT "users".*
466
+ # # FROM "users"
467
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
468
+ # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
469
+ #
470
+ # Nested joins:
471
+ #
472
+ # User.joins(posts: [:comments])
473
+ # # SELECT "users".*
474
+ # # FROM "users"
475
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
476
+ # # INNER JOIN "comments" "comments_posts"
477
+ # # ON "comments_posts"."post_id" = "posts"."id"
423
478
  #
424
479
  # You can use strings in order to customize your joins:
425
480
  #
426
481
  # 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
482
+ # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
428
483
  def joins(*args)
429
484
  check_if_method_has_arguments!(:joins, args)
430
485
  spawn.joins!(*args)
@@ -437,12 +492,21 @@ module ActiveRecord
437
492
  self
438
493
  end
439
494
 
440
- def bind(value) # :nodoc:
441
- spawn.bind!(value)
495
+ # Performs a left outer joins on +args+:
496
+ #
497
+ # User.left_outer_joins(:posts)
498
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
499
+ #
500
+ def left_outer_joins(*args)
501
+ check_if_method_has_arguments!(__callee__, args)
502
+ spawn.left_outer_joins!(*args)
442
503
  end
504
+ alias :left_joins :left_outer_joins
443
505
 
444
- def bind!(value) # :nodoc:
445
- self.bind_values += [value]
506
+ def left_outer_joins!(*args) # :nodoc:
507
+ args.compact!
508
+ args.flatten!
509
+ self.left_outer_joins_values += args
446
510
  self
447
511
  end
448
512
 
@@ -489,7 +553,7 @@ module ActiveRecord
489
553
  # than the previous methods; you are responsible for ensuring that the values in the template
490
554
  # are properly quoted. The values are passed to the connector for quoting, but the caller
491
555
  # 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>.
556
+ # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
493
557
  #
494
558
  # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
495
559
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
@@ -566,7 +630,7 @@ module ActiveRecord
566
630
  # If the condition is any blank-ish object, then #where is a no-op and returns
567
631
  # the current relation.
568
632
  def where(opts = :chain, *rest)
569
- if opts == :chain
633
+ if :chain == opts
570
634
  WhereChain.new(spawn)
571
635
  elsif opts.blank?
572
636
  self
@@ -576,27 +640,61 @@ module ActiveRecord
576
640
  end
577
641
 
578
642
  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)
643
+ opts = sanitize_forbidden_attributes(opts)
644
+ references!(PredicateBuilder.references(opts)) if Hash === opts
645
+ self.where_clause += where_clause_factory.build(opts, rest)
585
646
  self
586
647
  end
587
648
 
588
649
  # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
589
650
  #
590
- # Post.where(trashed: true).where(trashed: false) # => WHERE `trashed` = 1 AND `trashed` = 0
591
- # Post.where(trashed: true).rewhere(trashed: false) # => WHERE `trashed` = 0
592
- # Post.where(active: true).where(trashed: true).rewhere(trashed: false) # => WHERE `active` = 1 AND `trashed` = 0
651
+ # Post.where(trashed: true).where(trashed: false)
652
+ # # WHERE `trashed` = 1 AND `trashed` = 0
593
653
  #
594
- # This is short-hand for unscope(where: conditions.keys).where(conditions). Note that unlike reorder, we're only unscoping
595
- # the named conditions -- not the entire where statement.
654
+ # Post.where(trashed: true).rewhere(trashed: false)
655
+ # # WHERE `trashed` = 0
656
+ #
657
+ # Post.where(active: true).where(trashed: true).rewhere(trashed: false)
658
+ # # WHERE `active` = 1 AND `trashed` = 0
659
+ #
660
+ # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
661
+ # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
596
662
  def rewhere(conditions)
597
663
  unscope(where: conditions.keys).where(conditions)
598
664
  end
599
665
 
666
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
667
+ # argument.
668
+ #
669
+ # The two relations must be structurally compatible: they must be scoping the same model, and
670
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
671
+ # present). Neither relation may have a #limit, #offset, or #distinct set.
672
+ #
673
+ # Post.where("id = 1").or(Post.where("author_id = 3"))
674
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
675
+ #
676
+ def or(other)
677
+ unless other.is_a? Relation
678
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
679
+ end
680
+
681
+ spawn.or!(other)
682
+ end
683
+
684
+ def or!(other) # :nodoc:
685
+ incompatible_values = structurally_incompatible_values_for_or(other)
686
+
687
+ unless incompatible_values.empty?
688
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
689
+ end
690
+
691
+ self.where_clause = self.where_clause.or(other.where_clause)
692
+ self.having_clause = having_clause.or(other.having_clause)
693
+ self.references_values += other.references_values
694
+
695
+ self
696
+ end
697
+
600
698
  # Allows to specify a HAVING clause. Note that you can't use HAVING
601
699
  # without also specifying a GROUP clause.
602
700
  #
@@ -606,9 +704,10 @@ module ActiveRecord
606
704
  end
607
705
 
608
706
  def having!(opts, *rest) # :nodoc:
707
+ opts = sanitize_forbidden_attributes(opts)
609
708
  references!(PredicateBuilder.references(opts)) if Hash === opts
610
709
 
611
- self.having_values += build_where(opts, rest)
710
+ self.having_clause += having_clause_factory.build(opts, rest)
612
711
  self
613
712
  end
614
713
 
@@ -643,7 +742,7 @@ module ActiveRecord
643
742
  end
644
743
 
645
744
  # Specifies locking settings (default to +true+). For more information
646
- # on locking, please see +ActiveRecord::Locking+.
745
+ # on locking, please see ActiveRecord::Locking.
647
746
  def lock(locks = true)
648
747
  spawn.lock!(locks)
649
748
  end
@@ -674,7 +773,7 @@ module ActiveRecord
674
773
  # For example:
675
774
  #
676
775
  # @posts = current_user.visible_posts.where(name: params[:name])
677
- # # => the visible_posts method is expected to return a chainable Relation
776
+ # # the visible_posts method is expected to return a chainable Relation
678
777
  #
679
778
  # def visible_posts
680
779
  # case role
@@ -688,7 +787,7 @@ module ActiveRecord
688
787
  # end
689
788
  #
690
789
  def none
691
- where("1=0").extending!(NullRelation)
790
+ spawn.none!
692
791
  end
693
792
 
694
793
  def none! # :nodoc:
@@ -700,7 +799,7 @@ module ActiveRecord
700
799
  #
701
800
  # users = User.readonly
702
801
  # users.first.save
703
- # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
802
+ # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
704
803
  def readonly(value = true)
705
804
  spawn.readonly!(value)
706
805
  end
@@ -719,7 +818,7 @@ module ActiveRecord
719
818
  # users = users.create_with(name: 'DHH')
720
819
  # users.new.name # => 'DHH'
721
820
  #
722
- # You can pass +nil+ to +create_with+ to reset attributes:
821
+ # You can pass +nil+ to #create_with to reset attributes:
723
822
  #
724
823
  # users = users.create_with(nil)
725
824
  # users.new.name # => 'Oscar'
@@ -732,7 +831,7 @@ module ActiveRecord
732
831
  value = sanitize_forbidden_attributes(value)
733
832
  self.create_with_value = create_with_value.merge(value)
734
833
  else
735
- self.create_with_value = {}
834
+ self.create_with_value = FROZEN_EMPTY_HASH
736
835
  end
737
836
 
738
837
  self
@@ -741,49 +840,44 @@ module ActiveRecord
741
840
  # Specifies table from which the records will be fetched. For example:
742
841
  #
743
842
  # Topic.select('title').from('posts')
744
- # # => SELECT title FROM posts
843
+ # # SELECT title FROM posts
745
844
  #
746
845
  # Can accept other relation objects. For example:
747
846
  #
748
847
  # Topic.select('title').from(Topic.approved)
749
- # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
848
+ # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
750
849
  #
751
850
  # Topic.select('a.title').from(Topic.approved, :a)
752
- # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
851
+ # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
753
852
  #
754
853
  def from(value, subquery_name = nil)
755
854
  spawn.from!(value, subquery_name)
756
855
  end
757
856
 
758
857
  def from!(value, subquery_name = nil) # :nodoc:
759
- self.from_value = [value, subquery_name]
760
- if value.is_a? Relation
761
- self.bind_values = value.arel.bind_values + value.bind_values + bind_values
762
- end
858
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
763
859
  self
764
860
  end
765
861
 
766
862
  # Specifies whether the records should be unique or not. For example:
767
863
  #
768
864
  # User.select(:name)
769
- # # => Might return two records with the same name
865
+ # # Might return two records with the same name
770
866
  #
771
867
  # User.select(:name).distinct
772
- # # => Returns 1 record per distinct name
868
+ # # Returns 1 record per distinct name
773
869
  #
774
870
  # User.select(:name).distinct.distinct(false)
775
- # # => You can also remove the uniqueness
871
+ # # You can also remove the uniqueness
776
872
  def distinct(value = true)
777
873
  spawn.distinct!(value)
778
874
  end
779
- alias uniq distinct
780
875
 
781
876
  # Like #distinct, but modifies relation in place.
782
877
  def distinct!(value = true) # :nodoc:
783
878
  self.distinct_value = value
784
879
  self
785
880
  end
786
- alias uniq! distinct!
787
881
 
788
882
  # Used to extend a scope with additional methods, either through
789
883
  # a module or through a block provided.
@@ -839,6 +933,29 @@ module ActiveRecord
839
933
  self
840
934
  end
841
935
 
936
+ # Specify optimizer hints to be used in the SELECT statement.
937
+ #
938
+ # Example (for MySQL):
939
+ #
940
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
941
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
942
+ #
943
+ # Example (for PostgreSQL with pg_hint_plan):
944
+ #
945
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
946
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
947
+ def optimizer_hints(*args)
948
+ check_if_method_has_arguments!(:optimizer_hints, args)
949
+ spawn.optimizer_hints!(*args)
950
+ end
951
+
952
+ def optimizer_hints!(*args) # :nodoc:
953
+ args.flatten!
954
+
955
+ self.optimizer_hints_values |= args
956
+ self
957
+ end
958
+
842
959
  # Reverse the existing order clause on the relation.
843
960
  #
844
961
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -853,326 +970,390 @@ module ActiveRecord
853
970
  self
854
971
  end
855
972
 
856
- # Returns the Arel object associated with the relation.
857
- def arel # :nodoc:
858
- @arel ||= build_arel
973
+ def skip_query_cache!(value = true) # :nodoc:
974
+ self.skip_query_cache_value = value
975
+ self
859
976
  end
860
977
 
861
- private
978
+ def skip_preloading! # :nodoc:
979
+ self.skip_preloading_value = true
980
+ self
981
+ end
862
982
 
863
- def build_arel
864
- arel = Arel::SelectManager.new(table.engine, table)
983
+ # Adds an SQL comment to queries generated from this relation. For example:
984
+ #
985
+ # User.annotate("selecting user names").select(:name)
986
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
987
+ #
988
+ # User.annotate("selecting", "user", "names").select(:name)
989
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
990
+ #
991
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
992
+ def annotate(*args)
993
+ check_if_method_has_arguments!(:annotate, args)
994
+ spawn.annotate!(*args)
995
+ end
865
996
 
866
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
997
+ # Like #annotate, but modifies relation in place.
998
+ def annotate!(*args) # :nodoc:
999
+ self.annotate_values += args
1000
+ self
1001
+ end
867
1002
 
868
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
1003
+ # Returns the Arel object associated with the relation.
1004
+ def arel(aliases = nil) # :nodoc:
1005
+ @arel ||= build_arel(aliases)
1006
+ end
869
1007
 
870
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
1008
+ def construct_join_dependency(associations, join_type) # :nodoc:
1009
+ ActiveRecord::Associations::JoinDependency.new(
1010
+ klass, table, associations, join_type
1011
+ )
1012
+ end
871
1013
 
872
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
873
- arel.skip(offset_value.to_i) if offset_value
874
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1014
+ protected
1015
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1016
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
875
1017
 
876
- build_order(arel)
1018
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1019
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1020
+ end
1021
+ end
877
1022
 
878
- build_select(arel)
1023
+ private
1024
+ def assert_mutability!
1025
+ raise ImmutableRelation if @loaded
1026
+ raise ImmutableRelation if defined?(@arel) && @arel
1027
+ end
879
1028
 
880
- arel.distinct(distinct_value)
881
- arel.from(build_from) if from_value
882
- arel.lock(lock_value) if lock_value
1029
+ def build_arel(aliases)
1030
+ arel = Arel::SelectManager.new(table)
883
1031
 
884
- arel
885
- end
1032
+ if !joins_values.empty?
1033
+ build_joins(arel, joins_values.flatten, aliases)
1034
+ elsif !left_outer_joins_values.empty?
1035
+ build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
1036
+ end
886
1037
 
887
- def symbol_unscoping(scope)
888
- if !VALID_UNSCOPING_VALUES.include?(scope)
889
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
890
- end
1038
+ arel.where(where_clause.ast) unless where_clause.empty?
1039
+ arel.having(having_clause.ast) unless having_clause.empty?
1040
+ if limit_value
1041
+ limit_attribute = ActiveModel::Attribute.with_cast_value(
1042
+ "LIMIT",
1043
+ connection.sanitize_limit(limit_value),
1044
+ Type.default_value,
1045
+ )
1046
+ arel.take(Arel::Nodes::BindParam.new(limit_attribute))
1047
+ end
1048
+ if offset_value
1049
+ offset_attribute = ActiveModel::Attribute.with_cast_value(
1050
+ "OFFSET",
1051
+ offset_value.to_i,
1052
+ Type.default_value,
1053
+ )
1054
+ arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
1055
+ end
1056
+ arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
891
1057
 
892
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
893
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
1058
+ build_order(arel)
894
1059
 
895
- case scope
896
- when :order
897
- result = []
898
- when :where
899
- self.bind_values = []
900
- else
901
- result = [] unless single_val_method
902
- end
1060
+ build_select(arel)
903
1061
 
904
- self.send(unscope_code, result)
905
- end
1062
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1063
+ arel.distinct(distinct_value)
1064
+ arel.from(build_from) unless from_clause.empty?
1065
+ arel.lock(lock_value) if lock_value
1066
+ arel.comment(*annotate_values) unless annotate_values.empty?
906
1067
 
907
- def where_unscoping(target_value)
908
- target_value = target_value.to_s
1068
+ arel
1069
+ end
909
1070
 
910
- self.where_values = where_values.reject do |rel|
911
- case rel
912
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
913
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
914
- subrelation.name == target_value
1071
+ def build_from
1072
+ opts = from_clause.value
1073
+ name = from_clause.name
1074
+ case opts
1075
+ when Relation
1076
+ if opts.eager_loading?
1077
+ opts = opts.send(:apply_join_dependency)
1078
+ end
1079
+ name ||= "subquery"
1080
+ opts.arel.as(name.to_s)
1081
+ else
1082
+ opts
915
1083
  end
916
1084
  end
917
1085
 
918
- bind_values.reject! { |col,_| col.name == target_value }
919
- end
920
-
921
- def custom_join_ast(table, joins)
922
- joins = joins.reject(&:blank?)
923
-
924
- return [] if joins.empty?
925
-
926
- joins.map! do |join|
927
- case join
928
- when Array
929
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
930
- when String
931
- join = Arel.sql(join)
1086
+ def valid_association_list(associations)
1087
+ associations.each do |association|
1088
+ case association
1089
+ when Hash, Symbol, Array
1090
+ # valid
1091
+ else
1092
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1093
+ end
932
1094
  end
933
- table.create_string_join(join)
934
1095
  end
935
- end
936
1096
 
937
- def collapse_wheres(arel, wheres)
938
- predicates = wheres.map do |where|
939
- next where if ::Arel::Nodes::Equality === where
940
- where = Arel.sql(where) if String === where
941
- Arel::Nodes::Grouping.new(where)
1097
+ def build_left_outer_joins(manager, outer_joins, aliases)
1098
+ buckets = Hash.new { |h, k| h[k] = [] }
1099
+ buckets[:association_join] = valid_association_list(outer_joins)
1100
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
942
1101
  end
943
1102
 
944
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
945
- end
1103
+ def build_joins(manager, joins, aliases)
1104
+ buckets = Hash.new { |h, k| h[k] = [] }
946
1105
 
947
- def build_where(opts, other = [])
948
- case opts
949
- when String, Array
950
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
951
- when Hash
952
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
1106
+ unless left_outer_joins_values.empty?
1107
+ left_joins = valid_association_list(left_outer_joins_values.flatten)
1108
+ buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1109
+ end
953
1110
 
954
- tmp_opts, bind_values = create_binds(opts)
955
- self.bind_values += bind_values
1111
+ joins.map! do |join|
1112
+ if join.is_a?(String)
1113
+ table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1114
+ else
1115
+ join
1116
+ end
1117
+ end.delete_if(&:blank?).uniq!
956
1118
 
957
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
958
- add_relations_to_bind_values(attributes)
1119
+ while joins.first.is_a?(Arel::Nodes::Join)
1120
+ join_node = joins.shift
1121
+ if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
1122
+ buckets[:join_node] << join_node
1123
+ else
1124
+ buckets[:leading_join] << join_node
1125
+ end
1126
+ end
959
1127
 
960
- PredicateBuilder.build_from_hash(klass, attributes, table)
961
- else
962
- [opts]
963
- end
964
- end
1128
+ joins.each do |join|
1129
+ case join
1130
+ when Hash, Symbol, Array
1131
+ buckets[:association_join] << join
1132
+ when ActiveRecord::Associations::JoinDependency
1133
+ buckets[:stashed_join] << join
1134
+ when Arel::Nodes::Join
1135
+ buckets[:join_node] << join
1136
+ else
1137
+ raise "unknown class: %s" % join.class.name
1138
+ end
1139
+ end
965
1140
 
966
- def create_binds(opts)
967
- bindable, non_binds = opts.partition do |column, value|
968
- PredicateBuilder.can_be_bound?(value) &&
969
- @klass.columns_hash.include?(column.to_s) &&
970
- !@klass.reflect_on_aggregation(column)
1141
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
971
1142
  end
972
1143
 
973
- association_binds, non_binds = non_binds.partition do |column, value|
974
- value.is_a?(Hash) && association_for_table(column)
975
- end
1144
+ def build_join_query(manager, buckets, join_type, aliases)
1145
+ association_joins = buckets[:association_join]
1146
+ stashed_joins = buckets[:stashed_join]
1147
+ leading_joins = buckets[:leading_join]
1148
+ join_nodes = buckets[:join_node]
976
1149
 
977
- new_opts = {}
978
- binds = []
1150
+ join_sources = manager.join_sources
1151
+ join_sources.concat(leading_joins) unless leading_joins.empty?
979
1152
 
980
- connection = self.connection
1153
+ unless association_joins.empty? && stashed_joins.empty?
1154
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1155
+ join_dependency = construct_join_dependency(association_joins, join_type)
1156
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
1157
+ end
981
1158
 
982
- bindable.each do |(column,value)|
983
- binds.push [@klass.columns_hash[column.to_s], value]
984
- new_opts[column] = connection.substitute_at(column)
1159
+ join_sources.concat(join_nodes) unless join_nodes.empty?
985
1160
  end
986
1161
 
987
- association_binds.each do |(column, value)|
988
- association_relation = association_for_table(column).klass.send(:relation)
989
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
990
- new_opts[column] = association_new_opts
991
- binds += association_bind
1162
+ def build_select(arel)
1163
+ if select_values.any?
1164
+ arel.project(*arel_columns(select_values.uniq))
1165
+ elsif klass.ignored_columns.any?
1166
+ arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1167
+ else
1168
+ arel.project(table[Arel.star])
1169
+ end
992
1170
  end
993
1171
 
994
- non_binds.each { |column,value| new_opts[column] = value }
995
-
996
- [new_opts, binds]
997
- end
998
-
999
- def association_for_table(table_name)
1000
- table_name = table_name.to_s
1001
- @klass._reflect_on_association(table_name) ||
1002
- @klass._reflect_on_association(table_name.singularize)
1003
- end
1004
-
1005
- def build_from
1006
- opts, name = from_value
1007
- case opts
1008
- when Relation
1009
- name ||= 'subquery'
1010
- opts.arel.as(name.to_s)
1011
- else
1012
- opts
1172
+ def arel_columns(columns)
1173
+ columns.flat_map do |field|
1174
+ case field
1175
+ when Symbol
1176
+ arel_column(field.to_s) do |attr_name|
1177
+ connection.quote_table_name(attr_name)
1178
+ end
1179
+ when String
1180
+ arel_column(field, &:itself)
1181
+ when Proc
1182
+ field.call
1183
+ else
1184
+ field
1185
+ end
1186
+ end
1013
1187
  end
1014
- end
1015
1188
 
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
1189
+ def arel_column(field)
1190
+ field = klass.attribute_aliases[field] || field
1191
+ from = from_clause.name || from_clause.value
1192
+
1193
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1194
+ arel_attribute(field)
1027
1195
  else
1028
- raise 'unknown class: %s' % join.class.name
1196
+ yield field
1029
1197
  end
1030
1198
  end
1031
1199
 
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
1200
+ def table_name_matches?(from)
1201
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1202
+ end
1036
1203
 
1037
- join_list = join_nodes + custom_join_ast(manager, string_joins)
1204
+ def reverse_sql_order(order_query)
1205
+ if order_query.empty?
1206
+ return [arel_attribute(primary_key).desc] if primary_key
1207
+ raise IrreversibleOrderError,
1208
+ "Relation has no current order and table has no primary key to be used as default order"
1209
+ end
1038
1210
 
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
1211
+ order_query.flat_map do |o|
1212
+ case o
1213
+ when Arel::Attribute
1214
+ o.desc
1215
+ when Arel::Nodes::Ordering
1216
+ o.reverse
1217
+ when String
1218
+ if does_not_support_reverse?(o)
1219
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1220
+ end
1221
+ o.split(",").map! do |s|
1222
+ s.strip!
1223
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1224
+ end
1225
+ else
1226
+ o
1227
+ end
1228
+ end
1229
+ end
1044
1230
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
1231
+ def does_not_support_reverse?(order)
1232
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
1233
+ # override methods like #count.
1234
+ order = String.new(order) unless order.instance_of?(String)
1046
1235
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
1236
+ # Uses SQL function with multiple arguments.
1237
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1238
+ # Uses "nulls first" like construction.
1239
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1050
1240
  end
1051
1241
 
1052
- manager.join_sources.concat(join_list)
1242
+ def build_order(arel)
1243
+ orders = order_values.uniq
1244
+ orders.reject!(&:blank?)
1053
1245
 
1054
- manager
1055
- end
1246
+ arel.order(*orders) unless orders.empty?
1247
+ end
1056
1248
 
1057
- def build_select(arel)
1058
- if select_values.any?
1059
- arel.project(*arel_columns(select_values.uniq))
1060
- else
1061
- arel.project(@klass.arel_table[Arel.star])
1249
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1250
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1251
+
1252
+ def validate_order_args(args)
1253
+ args.each do |arg|
1254
+ next unless arg.is_a?(Hash)
1255
+ arg.each do |_key, value|
1256
+ unless VALID_DIRECTIONS.include?(value)
1257
+ raise ArgumentError,
1258
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1259
+ end
1260
+ end
1261
+ end
1062
1262
  end
1063
- end
1064
1263
 
1065
- def arel_columns(columns)
1066
- columns.map do |field|
1067
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value
1068
- arel_table[field]
1069
- elsif Symbol === field
1070
- connection.quote_table_name(field.to_s)
1071
- else
1072
- field
1264
+ def preprocess_order_args(order_args)
1265
+ order_args.reject!(&:blank?)
1266
+ order_args.map! do |arg|
1267
+ klass.sanitize_sql_for_order(arg)
1073
1268
  end
1269
+ order_args.flatten!
1270
+
1271
+ @klass.disallow_raw_sql!(
1272
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1273
+ permit: connection.column_name_with_order_matcher
1274
+ )
1275
+
1276
+ validate_order_args(order_args)
1277
+
1278
+ references = order_args.grep(String)
1279
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1280
+ references!(references) if references.any?
1281
+
1282
+ # if a symbol is given we prepend the quoted table name
1283
+ order_args.map! do |arg|
1284
+ case arg
1285
+ when Symbol
1286
+ order_column(arg.to_s).asc
1287
+ when Hash
1288
+ arg.map { |field, dir|
1289
+ case field
1290
+ when Arel::Nodes::SqlLiteral
1291
+ field.send(dir.downcase)
1292
+ else
1293
+ order_column(field.to_s).send(dir.downcase)
1294
+ end
1295
+ }
1296
+ else
1297
+ arg
1298
+ end
1299
+ end.flatten!
1074
1300
  end
1075
- end
1076
1301
 
1077
- def reverse_sql_order(order_query)
1078
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1079
-
1080
- order_query.flat_map do |o|
1081
- case o
1082
- when Arel::Nodes::Ordering
1083
- o.reverse
1084
- when String
1085
- o.to_s.split(',').map! do |s|
1086
- s.strip!
1087
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
1302
+ def order_column(field)
1303
+ arel_column(field) do |attr_name|
1304
+ if attr_name == "count" && !group_values.empty?
1305
+ arel_attribute(attr_name)
1306
+ else
1307
+ Arel.sql(connection.quote_table_name(attr_name))
1088
1308
  end
1089
- else
1090
- o
1091
1309
  end
1092
1310
  end
1093
- end
1094
-
1095
- def array_of_strings?(o)
1096
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
1097
- end
1098
1311
 
1099
- def build_order(arel)
1100
- orders = order_values.uniq
1101
- orders.reject!(&:blank?)
1102
-
1103
- arel.order(*orders) unless orders.empty?
1104
- end
1105
-
1106
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1107
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
1108
-
1109
- def validate_order_args(args)
1110
- args.each do |arg|
1111
- next unless arg.is_a?(Hash)
1112
- arg.each do |_key, value|
1113
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
1114
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
1312
+ # Checks to make sure that the arguments are not blank. Note that if some
1313
+ # blank-like object were initially passed into the query method, then this
1314
+ # method will not raise an error.
1315
+ #
1316
+ # Example:
1317
+ #
1318
+ # Post.references() # raises an error
1319
+ # Post.references([]) # does not raise an error
1320
+ #
1321
+ # This particular method should be called with a method_name and the args
1322
+ # passed into that method as an input. For example:
1323
+ #
1324
+ # def references(*args)
1325
+ # check_if_method_has_arguments!("references", args)
1326
+ # ...
1327
+ # end
1328
+ def check_if_method_has_arguments!(method_name, args)
1329
+ if args.blank?
1330
+ raise ArgumentError, "The method .#{method_name}() must contain arguments."
1115
1331
  end
1116
1332
  end
1117
- end
1118
-
1119
- def preprocess_order_args(order_args)
1120
- order_args.flatten!
1121
- validate_order_args(order_args)
1122
1333
 
1123
- references = order_args.grep(String)
1124
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1125
- references!(references) if references.any?
1126
-
1127
- # if a symbol is given we prepend the quoted table name
1128
- order_args.map! do |arg|
1129
- case arg
1130
- when Symbol
1131
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
1132
- table[arg].asc
1133
- when Hash
1134
- arg.map { |field, dir|
1135
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1136
- table[field].send(dir.downcase)
1137
- }
1138
- else
1139
- arg
1334
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1335
+ def structurally_incompatible_values_for_or(other)
1336
+ values = other.values
1337
+ STRUCTURAL_OR_METHODS.reject do |method|
1338
+ default = DEFAULT_VALUES[method]
1339
+ @values.fetch(method, default) == values.fetch(method, default)
1140
1340
  end
1141
- end.flatten!
1142
- end
1341
+ end
1143
1342
 
1144
- # Checks to make sure that the arguments are not blank. Note that if some
1145
- # blank-like object were initially passed into the query method, then this
1146
- # method will not raise an error.
1147
- #
1148
- # Example:
1149
- #
1150
- # Post.references() # => raises an error
1151
- # Post.references([]) # => does not raise an error
1152
- #
1153
- # This particular method should be called with a method_name and the args
1154
- # passed into that method as an input. For example:
1155
- #
1156
- # def references(*args)
1157
- # check_if_method_has_arguments!("references", args)
1158
- # ...
1159
- # end
1160
- def check_if_method_has_arguments!(method_name, args)
1161
- if args.blank?
1162
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1343
+ def where_clause_factory
1344
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1163
1345
  end
1164
- end
1346
+ alias having_clause_factory where_clause_factory
1165
1347
 
1166
- def add_relations_to_bind_values(attributes)
1167
- if attributes.is_a?(Hash)
1168
- attributes.each_value do |value|
1169
- if value.is_a?(ActiveRecord::Relation)
1170
- self.bind_values += value.arel.bind_values + value.bind_values
1171
- else
1172
- add_relations_to_bind_values(value)
1173
- end
1174
- end
1348
+ DEFAULT_VALUES = {
1349
+ create_with: FROZEN_EMPTY_HASH,
1350
+ where: Relation::WhereClause.empty,
1351
+ having: Relation::WhereClause.empty,
1352
+ from: Relation::FromClause.empty
1353
+ }
1354
+
1355
+ Relation::MULTI_VALUE_METHODS.each do |value|
1356
+ DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1175
1357
  end
1176
- end
1177
1358
  end
1178
1359
  end