activerecord 4.2.0 → 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 -971
  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/aggregations.rb +267 -248
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +135 -56
  11. data/lib/active_record/associations/association_scope.rb +103 -131
  12. data/lib/active_record/associations/belongs_to_association.rb +67 -54
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  14. data/lib/active_record/associations/builder/association.rb +27 -40
  15. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  16. data/lib/active_record/associations/builder/collection_association.rb +10 -29
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +60 -70
  18. data/lib/active_record/associations/builder/has_many.rb +8 -4
  19. data/lib/active_record/associations/builder/has_one.rb +46 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +138 -274
  22. data/lib/active_record/associations/collection_proxy.rb +252 -151
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -83
  25. data/lib/active_record/associations/has_many_through_association.rb +62 -80
  26. data/lib/active_record/associations/has_one_association.rb +62 -49
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +38 -80
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +138 -162
  32. data/lib/active_record/associations/preloader/association.rb +90 -119
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +92 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -45
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1737 -1596
  38. data/lib/active_record/attribute_assignment.rb +56 -183
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +15 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +174 -134
  42. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  43. data/lib/active_record/attribute_methods/query.rb +6 -5
  44. data/lib/active_record/attribute_methods/read.rb +20 -76
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +33 -55
  48. data/lib/active_record/attribute_methods.rb +124 -143
  49. data/lib/active_record/attributes.rb +214 -74
  50. data/lib/active_record/autosave_association.rb +115 -46
  51. data/lib/active_record/base.rb +60 -49
  52. data/lib/active_record/callbacks.rb +100 -74
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -290
  56. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  57. data/lib/active_record/connection_adapters/abstract/database_statements.rb +247 -108
  58. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
  59. data/lib/active_record/connection_adapters/abstract/quoting.rb +171 -53
  60. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  61. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  62. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +366 -227
  63. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  64. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +706 -222
  65. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -87
  66. data/lib/active_record/connection_adapters/abstract_adapter.rb +468 -194
  67. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +535 -597
  68. data/lib/active_record/connection_adapters/column.rb +56 -43
  69. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  70. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  73. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  74. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  79. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -195
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +65 -115
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  91. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  94. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  98. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +474 -286
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -363
  116. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +288 -359
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +176 -41
  128. data/lib/active_record/core.rb +266 -233
  129. data/lib/active_record/counter_cache.rb +68 -50
  130. data/lib/active_record/database_configurations/database_config.rb +37 -0
  131. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  132. data/lib/active_record/database_configurations/url_config.rb +79 -0
  133. data/lib/active_record/database_configurations.rb +233 -0
  134. data/lib/active_record/define_callbacks.rb +22 -0
  135. data/lib/active_record/dynamic_matchers.rb +87 -105
  136. data/lib/active_record/enum.rb +164 -88
  137. data/lib/active_record/errors.rb +189 -53
  138. data/lib/active_record/explain.rb +23 -11
  139. data/lib/active_record/explain_registry.rb +4 -2
  140. data/lib/active_record/explain_subscriber.rb +11 -6
  141. data/lib/active_record/fixture_set/file.rb +35 -9
  142. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  143. data/lib/active_record/fixture_set/render_context.rb +17 -0
  144. data/lib/active_record/fixture_set/table_row.rb +153 -0
  145. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  146. data/lib/active_record/fixtures.rb +226 -495
  147. data/lib/active_record/gem_version.rb +4 -2
  148. data/lib/active_record/inheritance.rb +158 -112
  149. data/lib/active_record/insert_all.rb +179 -0
  150. data/lib/active_record/integration.rb +123 -29
  151. data/lib/active_record/internal_metadata.rb +53 -0
  152. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  153. data/lib/active_record/locale/en.yml +3 -2
  154. data/lib/active_record/locking/optimistic.rb +91 -98
  155. data/lib/active_record/locking/pessimistic.rb +18 -6
  156. data/lib/active_record/log_subscriber.rb +76 -33
  157. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  158. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  159. data/lib/active_record/middleware/database_selector.rb +75 -0
  160. data/lib/active_record/migration/command_recorder.rb +177 -90
  161. data/lib/active_record/migration/compatibility.rb +244 -0
  162. data/lib/active_record/migration/join_table.rb +8 -6
  163. data/lib/active_record/migration.rb +634 -288
  164. data/lib/active_record/model_schema.rb +314 -112
  165. data/lib/active_record/nested_attributes.rb +266 -214
  166. data/lib/active_record/no_touching.rb +15 -2
  167. data/lib/active_record/null_relation.rb +24 -37
  168. data/lib/active_record/persistence.rb +559 -124
  169. data/lib/active_record/query_cache.rb +19 -23
  170. data/lib/active_record/querying.rb +43 -29
  171. data/lib/active_record/railtie.rb +148 -47
  172. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  173. data/lib/active_record/railties/console_sandbox.rb +2 -0
  174. data/lib/active_record/railties/controller_runtime.rb +34 -33
  175. data/lib/active_record/railties/databases.rake +338 -202
  176. data/lib/active_record/readonly_attributes.rb +5 -4
  177. data/lib/active_record/reflection.rb +460 -299
  178. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  179. data/lib/active_record/relation/batches.rb +207 -55
  180. data/lib/active_record/relation/calculations.rb +269 -248
  181. data/lib/active_record/relation/delegation.rb +70 -80
  182. data/lib/active_record/relation/finder_methods.rb +279 -255
  183. data/lib/active_record/relation/from_clause.rb +26 -0
  184. data/lib/active_record/relation/merger.rb +83 -69
  185. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -25
  186. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  187. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  188. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  189. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  190. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  191. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  192. data/lib/active_record/relation/predicate_builder.rb +116 -92
  193. data/lib/active_record/relation/query_attribute.rb +50 -0
  194. data/lib/active_record/relation/query_methods.rb +574 -391
  195. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  196. data/lib/active_record/relation/spawn_methods.rb +18 -16
  197. data/lib/active_record/relation/where_clause.rb +190 -0
  198. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  199. data/lib/active_record/relation.rb +518 -340
  200. data/lib/active_record/result.rb +79 -42
  201. data/lib/active_record/runtime_registry.rb +6 -4
  202. data/lib/active_record/sanitization.rb +144 -121
  203. data/lib/active_record/schema.rb +21 -24
  204. data/lib/active_record/schema_dumper.rb +112 -93
  205. data/lib/active_record/schema_migration.rb +24 -20
  206. data/lib/active_record/scoping/default.rb +101 -84
  207. data/lib/active_record/scoping/named.rb +86 -33
  208. data/lib/active_record/scoping.rb +45 -26
  209. data/lib/active_record/secure_token.rb +40 -0
  210. data/lib/active_record/serialization.rb +5 -5
  211. data/lib/active_record/statement_cache.rb +73 -36
  212. data/lib/active_record/store.rb +127 -42
  213. data/lib/active_record/suppressor.rb +61 -0
  214. data/lib/active_record/table_metadata.rb +75 -0
  215. data/lib/active_record/tasks/database_tasks.rb +309 -99
  216. data/lib/active_record/tasks/mysql_database_tasks.rb +58 -88
  217. data/lib/active_record/tasks/postgresql_database_tasks.rb +82 -31
  218. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  219. data/lib/active_record/test_databases.rb +23 -0
  220. data/lib/active_record/test_fixtures.rb +224 -0
  221. data/lib/active_record/timestamp.rb +86 -40
  222. data/lib/active_record/touch_later.rb +66 -0
  223. data/lib/active_record/transactions.rb +215 -139
  224. data/lib/active_record/translation.rb +3 -1
  225. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  226. data/lib/active_record/type/date.rb +4 -41
  227. data/lib/active_record/type/date_time.rb +4 -38
  228. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  229. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  230. data/lib/active_record/type/internal/timezone.rb +17 -0
  231. data/lib/active_record/type/json.rb +30 -0
  232. data/lib/active_record/type/serialized.rb +30 -15
  233. data/lib/active_record/type/text.rb +2 -2
  234. data/lib/active_record/type/time.rb +11 -16
  235. data/lib/active_record/type/type_map.rb +15 -17
  236. data/lib/active_record/type/unsigned_integer.rb +9 -7
  237. data/lib/active_record/type.rb +78 -23
  238. data/lib/active_record/type_caster/connection.rb +34 -0
  239. data/lib/active_record/type_caster/map.rb +20 -0
  240. data/lib/active_record/type_caster.rb +9 -0
  241. data/lib/active_record/validations/absence.rb +25 -0
  242. data/lib/active_record/validations/associated.rb +13 -4
  243. data/lib/active_record/validations/length.rb +26 -0
  244. data/lib/active_record/validations/presence.rb +14 -13
  245. data/lib/active_record/validations/uniqueness.rb +43 -46
  246. data/lib/active_record/validations.rb +39 -35
  247. data/lib/active_record/version.rb +3 -1
  248. data/lib/active_record.rb +43 -21
  249. data/lib/arel/alias_predication.rb +9 -0
  250. data/lib/arel/attributes/attribute.rb +37 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/collectors/bind.rb +24 -0
  253. data/lib/arel/collectors/composite.rb +31 -0
  254. data/lib/arel/collectors/plain_string.rb +20 -0
  255. data/lib/arel/collectors/sql_string.rb +20 -0
  256. data/lib/arel/collectors/substitute_binds.rb +28 -0
  257. data/lib/arel/crud.rb +42 -0
  258. data/lib/arel/delete_manager.rb +18 -0
  259. data/lib/arel/errors.rb +9 -0
  260. data/lib/arel/expressions.rb +29 -0
  261. data/lib/arel/factory_methods.rb +49 -0
  262. data/lib/arel/insert_manager.rb +49 -0
  263. data/lib/arel/math.rb +45 -0
  264. data/lib/arel/nodes/and.rb +32 -0
  265. data/lib/arel/nodes/ascending.rb +23 -0
  266. data/lib/arel/nodes/binary.rb +52 -0
  267. data/lib/arel/nodes/bind_param.rb +36 -0
  268. data/lib/arel/nodes/case.rb +55 -0
  269. data/lib/arel/nodes/casted.rb +50 -0
  270. data/lib/arel/nodes/comment.rb +29 -0
  271. data/lib/arel/nodes/count.rb +12 -0
  272. data/lib/arel/nodes/delete_statement.rb +45 -0
  273. data/lib/arel/nodes/descending.rb +23 -0
  274. data/lib/arel/nodes/equality.rb +18 -0
  275. data/lib/arel/nodes/extract.rb +24 -0
  276. data/lib/arel/nodes/false.rb +16 -0
  277. data/lib/arel/nodes/full_outer_join.rb +8 -0
  278. data/lib/arel/nodes/function.rb +44 -0
  279. data/lib/arel/nodes/grouping.rb +8 -0
  280. data/lib/arel/nodes/in.rb +8 -0
  281. data/lib/arel/nodes/infix_operation.rb +80 -0
  282. data/lib/arel/nodes/inner_join.rb +8 -0
  283. data/lib/arel/nodes/insert_statement.rb +37 -0
  284. data/lib/arel/nodes/join_source.rb +20 -0
  285. data/lib/arel/nodes/matches.rb +18 -0
  286. data/lib/arel/nodes/named_function.rb +23 -0
  287. data/lib/arel/nodes/node.rb +50 -0
  288. data/lib/arel/nodes/node_expression.rb +13 -0
  289. data/lib/arel/nodes/outer_join.rb +8 -0
  290. data/lib/arel/nodes/over.rb +15 -0
  291. data/lib/arel/nodes/regexp.rb +16 -0
  292. data/lib/arel/nodes/right_outer_join.rb +8 -0
  293. data/lib/arel/nodes/select_core.rb +67 -0
  294. data/lib/arel/nodes/select_statement.rb +41 -0
  295. data/lib/arel/nodes/sql_literal.rb +16 -0
  296. data/lib/arel/nodes/string_join.rb +11 -0
  297. data/lib/arel/nodes/table_alias.rb +27 -0
  298. data/lib/arel/nodes/terminal.rb +16 -0
  299. data/lib/arel/nodes/true.rb +16 -0
  300. data/lib/arel/nodes/unary.rb +45 -0
  301. data/lib/arel/nodes/unary_operation.rb +20 -0
  302. data/lib/arel/nodes/unqualified_column.rb +22 -0
  303. data/lib/arel/nodes/update_statement.rb +41 -0
  304. data/lib/arel/nodes/values_list.rb +9 -0
  305. data/lib/arel/nodes/window.rb +126 -0
  306. data/lib/arel/nodes/with.rb +11 -0
  307. data/lib/arel/nodes.rb +68 -0
  308. data/lib/arel/order_predications.rb +13 -0
  309. data/lib/arel/predications.rb +257 -0
  310. data/lib/arel/select_manager.rb +271 -0
  311. data/lib/arel/table.rb +110 -0
  312. data/lib/arel/tree_manager.rb +72 -0
  313. data/lib/arel/update_manager.rb +34 -0
  314. data/lib/arel/visitors/depth_first.rb +204 -0
  315. data/lib/arel/visitors/dot.rb +297 -0
  316. data/lib/arel/visitors/ibm_db.rb +34 -0
  317. data/lib/arel/visitors/informix.rb +62 -0
  318. data/lib/arel/visitors/mssql.rb +157 -0
  319. data/lib/arel/visitors/mysql.rb +83 -0
  320. data/lib/arel/visitors/oracle.rb +159 -0
  321. data/lib/arel/visitors/oracle12.rb +66 -0
  322. data/lib/arel/visitors/postgresql.rb +110 -0
  323. data/lib/arel/visitors/sqlite.rb +39 -0
  324. data/lib/arel/visitors/to_sql.rb +889 -0
  325. data/lib/arel/visitors/visitor.rb +46 -0
  326. data/lib/arel/visitors/where_sql.rb +23 -0
  327. data/lib/arel/visitors.rb +20 -0
  328. data/lib/arel/window_predications.rb +9 -0
  329. data/lib/arel.rb +51 -0
  330. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  331. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
  333. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -8
  335. data/lib/rails/generators/active_record/migration.rb +31 -1
  336. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record.rb +7 -5
  339. metadata +166 -60
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  347. data/lib/active_record/attribute.rb +0 -149
  348. data/lib/active_record/attribute_set/builder.rb +0 -86
  349. data/lib/active_record/attribute_set.rb +0 -77
  350. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  351. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  352. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  353. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  354. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  355. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  356. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  357. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  358. data/lib/active_record/type/big_integer.rb +0 -13
  359. data/lib/active_record/type/binary.rb +0 -50
  360. data/lib/active_record/type/boolean.rb +0 -30
  361. data/lib/active_record/type/decimal.rb +0 -40
  362. data/lib/active_record/type/decorator.rb +0 -14
  363. data/lib/active_record/type/float.rb +0 -19
  364. data/lib/active_record/type/integer.rb +0 -55
  365. data/lib/active_record/type/mutable.rb +0 -16
  366. data/lib/active_record/type/numeric.rb +0 -36
  367. data/lib/active_record/type/string.rb +0 -36
  368. data/lib/active_record/type/time_value.rb +0 -38
  369. data/lib/active_record/type/value.rb +0 -101
  370. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
  371. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
  372. /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,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) : 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,46 +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]
858
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
760
859
  self
761
860
  end
762
861
 
763
862
  # Specifies whether the records should be unique or not. For example:
764
863
  #
765
864
  # User.select(:name)
766
- # # => Might return two records with the same name
865
+ # # Might return two records with the same name
767
866
  #
768
867
  # User.select(:name).distinct
769
- # # => Returns 1 record per distinct name
868
+ # # Returns 1 record per distinct name
770
869
  #
771
870
  # User.select(:name).distinct.distinct(false)
772
- # # => You can also remove the uniqueness
871
+ # # You can also remove the uniqueness
773
872
  def distinct(value = true)
774
873
  spawn.distinct!(value)
775
874
  end
776
- alias uniq distinct
777
875
 
778
876
  # Like #distinct, but modifies relation in place.
779
877
  def distinct!(value = true) # :nodoc:
780
878
  self.distinct_value = value
781
879
  self
782
880
  end
783
- alias uniq! distinct!
784
881
 
785
882
  # Used to extend a scope with additional methods, either through
786
883
  # a module or through a block provided.
@@ -836,6 +933,29 @@ module ActiveRecord
836
933
  self
837
934
  end
838
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
+
839
959
  # Reverse the existing order clause on the relation.
840
960
  #
841
961
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -850,327 +970,390 @@ module ActiveRecord
850
970
  self
851
971
  end
852
972
 
853
- # Returns the Arel object associated with the relation.
854
- def arel # :nodoc:
855
- @arel ||= build_arel
973
+ def skip_query_cache!(value = true) # :nodoc:
974
+ self.skip_query_cache_value = value
975
+ self
856
976
  end
857
977
 
858
- private
978
+ def skip_preloading! # :nodoc:
979
+ self.skip_preloading_value = true
980
+ self
981
+ end
859
982
 
860
- def build_arel
861
- 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
862
996
 
863
- 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
864
1002
 
865
- 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
866
1007
 
867
- 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
868
1013
 
869
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
870
- arel.skip(offset_value.to_i) if offset_value
1014
+ protected
1015
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1016
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
871
1017
 
872
- arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
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
873
1022
 
874
- build_order(arel)
1023
+ private
1024
+ def assert_mutability!
1025
+ raise ImmutableRelation if @loaded
1026
+ raise ImmutableRelation if defined?(@arel) && @arel
1027
+ end
875
1028
 
876
- build_select(arel, select_values.uniq)
1029
+ def build_arel(aliases)
1030
+ arel = Arel::SelectManager.new(table)
877
1031
 
878
- arel.distinct(distinct_value)
879
- arel.from(build_from) if from_value
880
- arel.lock(lock_value) if lock_value
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
881
1037
 
882
- arel
883
- 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?
884
1057
 
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
1058
+ build_order(arel)
889
1059
 
890
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
891
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
1060
+ build_select(arel)
892
1061
 
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
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?
901
1067
 
902
- self.send(unscope_code, result)
903
- end
1068
+ arel
1069
+ end
904
1070
 
905
- def where_unscoping(target_value)
906
- target_value = target_value.to_s
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
1083
+ end
1084
+ end
907
1085
 
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
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
913
1094
  end
914
1095
  end
915
1096
 
916
- bind_values.reject! { |col,_| col.name == target_value }
917
- end
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)
1101
+ end
918
1102
 
919
- def custom_join_ast(table, joins)
920
- joins = joins.reject(&:blank?)
1103
+ def build_joins(manager, joins, aliases)
1104
+ buckets = Hash.new { |h, k| h[k] = [] }
921
1105
 
922
- return [] if joins.empty?
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
923
1110
 
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)
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!
1118
+
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
930
1126
  end
931
- table.create_string_join(join)
932
- end
933
- end
934
1127
 
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
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
941
1140
 
942
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
943
- end
1141
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1142
+ end
944
1143
 
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)
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]
951
1149
 
952
- tmp_opts, bind_values = create_binds(opts)
953
- self.bind_values += bind_values
1150
+ join_sources = manager.join_sources
1151
+ join_sources.concat(leading_joins) unless leading_joins.empty?
954
1152
 
955
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
956
- add_relations_to_bind_values(attributes)
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
957
1158
 
958
- PredicateBuilder.build_from_hash(klass, attributes, table)
959
- else
960
- [opts]
1159
+ join_sources.concat(join_nodes) unless join_nodes.empty?
961
1160
  end
962
- end
963
1161
 
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
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) })
969
1167
  else
970
- false
1168
+ arel.project(table[Arel.star])
971
1169
  end
972
1170
  end
973
1171
 
974
- association_binds, non_binds = non_binds.partition do |column, value|
975
- value.is_a?(Hash) && association_for_table(column)
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
976
1187
  end
977
1188
 
978
- new_opts = {}
979
- binds = []
1189
+ def arel_column(field)
1190
+ field = klass.attribute_aliases[field] || field
1191
+ from = from_clause.name || from_clause.value
980
1192
 
981
- bindable.each do |(column,value)|
982
- binds.push [@klass.columns_hash[column.to_s], value]
983
- new_opts[column] = connection.substitute_at(column)
1193
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1194
+ arel_attribute(field)
1195
+ else
1196
+ yield field
1197
+ end
984
1198
  end
985
1199
 
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
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)
991
1202
  end
992
1203
 
993
- non_binds.each { |column,value| new_opts[column] = value }
994
-
995
- [new_opts, binds]
996
- end
997
-
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
1003
-
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
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
1015
1210
 
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
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
1029
1228
  end
1030
1229
  end
1031
1230
 
1032
- association_joins = buckets[:association_join] || []
1033
- stashed_association_joins = buckets[:stashed_join] || []
1034
- join_nodes = (buckets[:join_node] || []).uniq
1035
- string_joins = (buckets[:string_join] || []).map(&:strip).uniq
1036
-
1037
- join_list = join_nodes + custom_join_ast(manager, string_joins)
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)
1038
1235
 
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
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)
1240
+ end
1044
1241
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
1242
+ def build_order(arel)
1243
+ orders = order_values.uniq
1244
+ orders.reject!(&:blank?)
1046
1245
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
1246
+ arel.order(*orders) unless orders.empty?
1050
1247
  end
1051
1248
 
1052
- manager.join_sources.concat(join_list)
1053
-
1054
- manager
1055
- end
1249
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1250
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1056
1251
 
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]
1062
- else
1063
- field
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
1064
1260
  end
1065
1261
  end
1262
+ end
1066
1263
 
1067
- arel.project(*expanded_select)
1068
- else
1069
- arel.project(@klass.arel_table[Arel.star])
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)
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!
1070
1300
  end
1071
- end
1072
1301
 
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')
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))
1084
1308
  end
1085
- else
1086
- o
1087
1309
  end
1088
1310
  end
1089
- end
1090
-
1091
- def array_of_strings?(o)
1092
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
1093
- end
1094
1311
 
1095
- def build_order(arel)
1096
- orders = order_values.uniq
1097
- orders.reject!(&:blank?)
1098
-
1099
- arel.order(*orders) unless orders.empty?
1100
- end
1101
-
1102
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1103
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
1104
-
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)
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."
1111
1331
  end
1112
1332
  end
1113
- end
1114
-
1115
- def preprocess_order_args(order_args)
1116
- order_args.flatten!
1117
- validate_order_args(order_args)
1118
1333
 
1119
- references = order_args.grep(String)
1120
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1121
- references!(references) if references.any?
1122
-
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
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)
1136
1340
  end
1137
- end.flatten!
1138
- end
1341
+ end
1139
1342
 
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."
1343
+ def where_clause_factory
1344
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1159
1345
  end
1160
- end
1346
+ alias having_clause_factory where_clause_factory
1161
1347
 
1162
- # This function is recursive just for better readablity.
1163
- # #where argument doesn't support more than one level nested hash in real world.
1164
- def add_relations_to_bind_values(attributes)
1165
- if attributes.is_a?(Hash)
1166
- attributes.each_value do |value|
1167
- if value.is_a?(ActiveRecord::Relation)
1168
- self.bind_values += value.bind_values
1169
- else
1170
- add_relations_to_bind_values(value)
1171
- end
1172
- 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
1173
1357
  end
1174
- end
1175
1358
  end
1176
1359
  end