activerecord 4.2.0 → 6.0.5.1

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

Potentially problematic release.


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

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