activerecord 4.2.11.2 → 6.0.2.2

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 +4 -4
  2. data/CHANGELOG.md +675 -1582
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +13 -12
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record.rb +41 -22
  8. data/lib/active_record/aggregations.rb +267 -251
  9. data/lib/active_record/association_relation.rb +26 -12
  10. data/lib/active_record/associations.rb +1737 -1597
  11. data/lib/active_record/associations/alias_tracker.rb +29 -35
  12. data/lib/active_record/associations/association.rb +133 -58
  13. data/lib/active_record/associations/association_scope.rb +103 -132
  14. data/lib/active_record/associations/belongs_to_association.rb +65 -60
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -40
  17. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  18. data/lib/active_record/associations/builder/collection_association.rb +10 -33
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +52 -66
  20. data/lib/active_record/associations/builder/has_many.rb +8 -4
  21. data/lib/active_record/associations/builder/has_one.rb +46 -5
  22. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  23. data/lib/active_record/associations/collection_association.rb +136 -288
  24. data/lib/active_record/associations/collection_proxy.rb +241 -146
  25. data/lib/active_record/associations/foreign_association.rb +10 -1
  26. data/lib/active_record/associations/has_many_association.rb +34 -97
  27. data/lib/active_record/associations/has_many_through_association.rb +60 -87
  28. data/lib/active_record/associations/has_one_association.rb +61 -49
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +141 -167
  31. data/lib/active_record/associations/join_dependency/join_association.rb +38 -86
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +90 -92
  35. data/lib/active_record/associations/preloader/association.rb +90 -123
  36. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  37. data/lib/active_record/associations/singular_association.rb +18 -39
  38. data/lib/active_record/associations/through_association.rb +38 -18
  39. data/lib/active_record/attribute_assignment.rb +56 -183
  40. data/lib/active_record/attribute_decorators.rb +39 -15
  41. data/lib/active_record/attribute_methods.rb +120 -135
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -8
  43. data/lib/active_record/attribute_methods/dirty.rb +174 -144
  44. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  45. data/lib/active_record/attribute_methods/query.rb +6 -5
  46. data/lib/active_record/attribute_methods/read.rb +20 -76
  47. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  49. data/lib/active_record/attribute_methods/write.rb +32 -54
  50. data/lib/active_record/attributes.rb +214 -82
  51. data/lib/active_record/autosave_association.rb +96 -38
  52. data/lib/active_record/base.rb +57 -45
  53. data/lib/active_record/callbacks.rb +100 -74
  54. data/lib/active_record/coders/json.rb +3 -1
  55. data/lib/active_record/coders/yaml_column.rb +24 -12
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +806 -296
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +238 -115
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +83 -23
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +356 -227
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +664 -243
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -83
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +469 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +517 -633
  69. data/lib/active_record/connection_adapters/column.rb +56 -43
  70. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +202 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +66 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  100. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +470 -290
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +555 -356
  117. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -345
  127. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  128. data/lib/active_record/connection_handling.rb +183 -41
  129. data/lib/active_record/core.rb +253 -229
  130. data/lib/active_record/counter_cache.rb +67 -49
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +87 -105
  137. data/lib/active_record/enum.rb +163 -86
  138. data/lib/active_record/errors.rb +188 -53
  139. data/lib/active_record/explain.rb +23 -11
  140. data/lib/active_record/explain_registry.rb +4 -2
  141. data/lib/active_record/explain_subscriber.rb +10 -5
  142. data/lib/active_record/fixture_set/file.rb +35 -9
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +228 -499
  148. data/lib/active_record/gem_version.rb +5 -3
  149. data/lib/active_record/inheritance.rb +158 -112
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +123 -29
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  154. data/lib/active_record/locale/en.yml +3 -2
  155. data/lib/active_record/locking/optimistic.rb +87 -96
  156. data/lib/active_record/locking/pessimistic.rb +18 -6
  157. data/lib/active_record/log_subscriber.rb +76 -33
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +621 -303
  162. data/lib/active_record/migration/command_recorder.rb +177 -90
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +8 -6
  165. data/lib/active_record/model_schema.rb +315 -112
  166. data/lib/active_record/nested_attributes.rb +264 -222
  167. data/lib/active_record/no_touching.rb +14 -1
  168. data/lib/active_record/null_relation.rb +24 -37
  169. data/lib/active_record/persistence.rb +557 -125
  170. data/lib/active_record/query_cache.rb +19 -23
  171. data/lib/active_record/querying.rb +43 -29
  172. data/lib/active_record/railtie.rb +143 -44
  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 +331 -185
  177. data/lib/active_record/readonly_attributes.rb +5 -4
  178. data/lib/active_record/reflection.rb +428 -279
  179. data/lib/active_record/relation.rb +519 -341
  180. data/lib/active_record/relation/batches.rb +207 -55
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +267 -253
  183. data/lib/active_record/relation/delegation.rb +70 -80
  184. data/lib/active_record/relation/finder_methods.rb +286 -241
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +78 -87
  187. data/lib/active_record/relation/predicate_builder.rb +114 -119
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +597 -393
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +11 -13
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +79 -42
  202. data/lib/active_record/runtime_registry.rb +6 -4
  203. data/lib/active_record/sanitization.rb +144 -121
  204. data/lib/active_record/schema.rb +21 -24
  205. data/lib/active_record/schema_dumper.rb +112 -93
  206. data/lib/active_record/schema_migration.rb +24 -17
  207. data/lib/active_record/scoping.rb +45 -26
  208. data/lib/active_record/scoping/default.rb +101 -85
  209. data/lib/active_record/scoping/named.rb +87 -33
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +5 -5
  212. data/lib/active_record/statement_cache.rb +73 -36
  213. data/lib/active_record/store.rb +127 -42
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +307 -100
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +55 -99
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -41
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +225 -0
  222. data/lib/active_record/timestamp.rb +86 -40
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +217 -151
  225. data/lib/active_record/translation.rb +3 -1
  226. data/lib/active_record/type.rb +78 -23
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +4 -45
  229. data/lib/active_record/type/date_time.rb +4 -49
  230. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  231. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +24 -15
  235. data/lib/active_record/type/text.rb +2 -2
  236. data/lib/active_record/type/time.rb +11 -16
  237. data/lib/active_record/type/type_map.rb +15 -17
  238. data/lib/active_record/type/unsigned_integer.rb +9 -7
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +39 -35
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +13 -4
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +14 -13
  247. data/lib/active_record/validations/uniqueness.rb +42 -55
  248. data/lib/active_record/version.rb +3 -1
  249. data/lib/arel.rb +58 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +7 -5
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +31 -1
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
  337. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  340. metadata +166 -58
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_set.rb +0 -81
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  352. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  353. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  354. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  355. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  356. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  359. data/lib/active_record/type/big_integer.rb +0 -13
  360. data/lib/active_record/type/binary.rb +0 -50
  361. data/lib/active_record/type/boolean.rb +0 -31
  362. data/lib/active_record/type/decimal.rb +0 -64
  363. data/lib/active_record/type/decorator.rb +0 -14
  364. data/lib/active_record/type/float.rb +0 -19
  365. data/lib/active_record/type/integer.rb +0 -59
  366. data/lib/active_record/type/mutable.rb +0 -16
  367. data/lib/active_record/type/numeric.rb +0 -36
  368. data/lib/active_record/type/string.rb +0 -40
  369. data/lib/active_record/type/time_value.rb +0 -38
  370. data/lib/active_record/type/value.rb +0 -110
  371. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  372. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,48 +1,49 @@
1
- require 'active_support/core_ext/string/filters'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract"
2
4
 
3
5
  module ActiveRecord
4
6
  class PredicateBuilder
5
7
  class ArrayHandler # :nodoc:
6
- def call(attribute, value)
7
- values = value.map { |x| x.is_a?(Base) ? x.id : x }
8
- nils, values = values.partition(&:nil?)
9
-
10
- if values.any? { |val| val.is_a?(Array) }
11
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
12
- Passing a nested array to Active Record finder methods is
13
- deprecated and will be removed. Flatten your array before using
14
- it for 'IN' conditions.
15
- MSG
16
-
17
- flat_values = values.flatten
18
- values = flat_values unless flat_values.include?(nil)
19
- end
8
+ def initialize(predicate_builder)
9
+ @predicate_builder = predicate_builder
10
+ end
20
11
 
21
- return attribute.in([]) if values.empty? && nils.empty?
12
+ def call(attribute, value)
13
+ return attribute.in([]) if value.empty?
22
14
 
23
- ranges, values = values.partition { |v| v.is_a?(Range) }
15
+ values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
+ nils = values.extract!(&:nil?)
17
+ ranges = values.extract! { |v| v.is_a?(Range) }
24
18
 
25
19
  values_predicate =
26
20
  case values.length
27
21
  when 0 then NullPredicate
28
- when 1 then attribute.eq(values.first)
29
- else attribute.in(values)
22
+ when 1 then predicate_builder.build(attribute, values.first)
23
+ else
24
+ values.map! do |v|
25
+ predicate_builder.build_bind_attribute(attribute.name, v)
26
+ end
27
+ values.empty? ? NullPredicate : attribute.in(values)
30
28
  end
31
29
 
32
30
  unless nils.empty?
33
- values_predicate = values_predicate.or(attribute.eq(nil))
31
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
34
32
  end
35
33
 
36
- array_predicates = ranges.map { |range| attribute.between(range) }
34
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
37
35
  array_predicates.unshift(values_predicate)
38
- array_predicates.inject { |composite, predicate| composite.or(predicate) }
36
+ array_predicates.inject(&:or)
39
37
  end
40
38
 
41
- module NullPredicate # :nodoc:
42
- def self.or(other)
43
- other
39
+ private
40
+ attr_reader :predicate_builder
41
+
42
+ module NullPredicate # :nodoc:
43
+ def self.or(other)
44
+ other
45
+ end
44
46
  end
45
- end
46
47
  end
47
48
  end
48
49
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class AssociationQueryValue # :nodoc:
6
+ def initialize(associated_table, value)
7
+ @associated_table = associated_table
8
+ @value = value
9
+ end
10
+
11
+ def queries
12
+ [associated_table.association_join_foreign_key.to_s => ids]
13
+ end
14
+
15
+ private
16
+ attr_reader :associated_table, :value
17
+
18
+ def ids
19
+ case value
20
+ when Relation
21
+ value.select_values.empty? ? value.select(primary_key) : value
22
+ when Array
23
+ value.map { |v| convert_to_id(v) }
24
+ else
25
+ convert_to_id(value)
26
+ end
27
+ end
28
+
29
+ def primary_key
30
+ associated_table.association_join_primary_key
31
+ end
32
+
33
+ def convert_to_id(value)
34
+ case value
35
+ when Base
36
+ value._read_attribute(primary_key)
37
+ else
38
+ value
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class BaseHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ predicate_builder.build(attribute, value.id)
12
+ end
13
+
14
+ private
15
+ attr_reader :predicate_builder
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class BasicObjectHandler # :nodoc:
6
+ def initialize(predicate_builder)
7
+ @predicate_builder = predicate_builder
8
+ end
9
+
10
+ def call(attribute, value)
11
+ bind = predicate_builder.build_bind_attribute(attribute.name, value)
12
+ attribute.eq(bind)
13
+ end
14
+
15
+ private
16
+ attr_reader :predicate_builder
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class PolymorphicArrayValue # :nodoc:
6
+ def initialize(associated_table, values)
7
+ @associated_table = associated_table
8
+ @values = values
9
+ end
10
+
11
+ def queries
12
+ type_to_ids_mapping.map do |type, ids|
13
+ {
14
+ associated_table.association_foreign_type.to_s => type,
15
+ associated_table.association_foreign_key.to_s => ids
16
+ }
17
+ end
18
+ end
19
+
20
+ private
21
+ attr_reader :associated_table, :values
22
+
23
+ def type_to_ids_mapping
24
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
25
+ values.each_with_object(default_hash) do |value, hash|
26
+ hash[klass(value).polymorphic_name] << convert_to_id(value)
27
+ end
28
+ end
29
+
30
+ def primary_key(value)
31
+ associated_table.association_join_primary_key(klass(value))
32
+ end
33
+
34
+ def klass(value)
35
+ case value
36
+ when Base
37
+ value.class
38
+ when Relation
39
+ value.klass
40
+ end
41
+ end
42
+
43
+ def convert_to_id(value)
44
+ case value
45
+ when Base
46
+ value._read_attribute(primary_key(value))
47
+ when Relation
48
+ value.select(primary_key(value))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PredicateBuilder
5
+ class RangeHandler # :nodoc:
6
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
7
+
8
+ def initialize(predicate_builder)
9
+ @predicate_builder = predicate_builder
10
+ end
11
+
12
+ def call(attribute, value)
13
+ begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
14
+ end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
15
+ attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
16
+ end
17
+
18
+ private
19
+ attr_reader :predicate_builder
20
+ end
21
+ end
22
+ end
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder
3
5
  class RelationHandler # :nodoc:
4
6
  def call(attribute, value)
7
+ if value.eager_loading?
8
+ value = value.send(:apply_join_dependency)
9
+ end
10
+
5
11
  if value.select_values.empty?
6
- value = value.select(value.klass.arel_table[value.klass.primary_key])
12
+ value = value.select(value.arel_attribute(value.klass.primary_key))
7
13
  end
8
14
 
9
15
  attribute.in(value.arel)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute"
4
+
5
+ module ActiveRecord
6
+ class Relation
7
+ class QueryAttribute < ActiveModel::Attribute # :nodoc:
8
+ def type_cast(value)
9
+ value
10
+ end
11
+
12
+ def value_for_database
13
+ @value_for_database ||= super
14
+ end
15
+
16
+ def with_cast_value(value)
17
+ QueryAttribute.new(name, value, type)
18
+ end
19
+
20
+ def nil?
21
+ unless value_before_type_cast.is_a?(StatementCache::Substitute)
22
+ value_before_type_cast.nil? ||
23
+ type.respond_to?(:subtype, true) && value_for_database.nil?
24
+ end
25
+ rescue ::RangeError
26
+ end
27
+
28
+ def infinite?
29
+ infinity?(value_before_type_cast) || infinity?(value_for_database)
30
+ rescue ::RangeError
31
+ end
32
+
33
+ def unboundable?
34
+ if defined?(@_unboundable)
35
+ @_unboundable
36
+ else
37
+ value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
38
+ @_unboundable = nil
39
+ end
40
+ rescue ::RangeError
41
+ @_unboundable = type.cast(value_before_type_cast) <=> 0
42
+ end
43
+
44
+ private
45
+ def infinity?(value)
46
+ value.respond_to?(:infinite?) && value.infinite?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,10 @@
1
- require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_model/forbidden_attributes_protection'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/from_clause"
4
+ require "active_record/relation/query_attribute"
5
+ require "active_record/relation/where_clause"
6
+ require "active_record/relation/where_clause_factory"
7
+ require "active_model/forbidden_attributes_protection"
4
8
 
5
9
  module ActiveRecord
6
10
  module QueryMethods
@@ -11,6 +15,8 @@ module ActiveRecord
11
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
12
16
  # In this case, #where must be chained with #not to return a new relation.
13
17
  class WhereChain
18
+ include ActiveModel::ForbiddenAttributesProtection
19
+
14
20
  def initialize(scope)
15
21
  @scope = scope
16
22
  end
@@ -18,7 +24,7 @@ module ActiveRecord
18
24
  # Returns a new relation expressing WHERE + NOT condition according to
19
25
  # the conditions in the arguments.
20
26
  #
21
- # +not+ accepts conditions as a string, array, or hash. See #where for
27
+ # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
22
28
  # more details on each format.
23
29
  #
24
30
  # User.where.not("name = 'Jon'")
@@ -35,77 +41,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 conditions manually
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,9 +159,9 @@ 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)
@@ -170,10 +172,10 @@ module ActiveRecord
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)
@@ -184,16 +186,29 @@ module ActiveRecord
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).to_sym : 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)
@@ -437,12 +503,21 @@ module ActiveRecord
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,49 +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]
760
- if value.is_a? Relation
761
- self.bind_values = value.arel.bind_values + value.bind_values + bind_values
762
- end
869
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
763
870
  self
764
871
  end
765
872
 
766
873
  # Specifies whether the records should be unique or not. For example:
767
874
  #
768
875
  # User.select(:name)
769
- # # => Might return two records with the same name
876
+ # # Might return two records with the same name
770
877
  #
771
878
  # User.select(:name).distinct
772
- # # => Returns 1 record per distinct name
879
+ # # Returns 1 record per distinct name
773
880
  #
774
881
  # User.select(:name).distinct.distinct(false)
775
- # # => You can also remove the uniqueness
882
+ # # You can also remove the uniqueness
776
883
  def distinct(value = true)
777
884
  spawn.distinct!(value)
778
885
  end
779
- alias uniq distinct
780
886
 
781
887
  # Like #distinct, but modifies relation in place.
782
888
  def distinct!(value = true) # :nodoc:
783
889
  self.distinct_value = value
784
890
  self
785
891
  end
786
- alias uniq! distinct!
787
892
 
788
893
  # Used to extend a scope with additional methods, either through
789
894
  # a module or through a block provided.
@@ -839,6 +944,29 @@ module ActiveRecord
839
944
  self
840
945
  end
841
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
+
842
970
  # Reverse the existing order clause on the relation.
843
971
  #
844
972
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -853,326 +981,402 @@ module ActiveRecord
853
981
  self
854
982
  end
855
983
 
856
- # Returns the Arel object associated with the relation.
857
- def arel # :nodoc:
858
- @arel ||= build_arel
984
+ def skip_query_cache!(value = true) # :nodoc:
985
+ self.skip_query_cache_value = value
986
+ self
859
987
  end
860
988
 
861
- private
989
+ def skip_preloading! # :nodoc:
990
+ self.skip_preloading_value = true
991
+ self
992
+ end
862
993
 
863
- def build_arel
864
- arel = Arel::SelectManager.new(table.engine, table)
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
865
1007
 
866
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
1008
+ # Like #annotate, but modifies relation in place.
1009
+ def annotate!(*args) # :nodoc:
1010
+ self.annotate_values += args
1011
+ self
1012
+ end
867
1013
 
868
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
1014
+ # Returns the Arel object associated with the relation.
1015
+ def arel(aliases = nil) # :nodoc:
1016
+ @arel ||= build_arel(aliases)
1017
+ end
869
1018
 
870
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
1019
+ def construct_join_dependency(associations, join_type) # :nodoc:
1020
+ ActiveRecord::Associations::JoinDependency.new(
1021
+ klass, table, associations, join_type
1022
+ )
1023
+ end
871
1024
 
872
- arel.take(connection.sanitize_limit(limit_value)) if limit_value
873
- arel.skip(offset_value.to_i) if offset_value
874
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1025
+ protected
1026
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1027
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
875
1028
 
876
- build_order(arel)
1029
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1030
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1031
+ end
1032
+ end
877
1033
 
878
- build_select(arel)
1034
+ private
1035
+ def assert_mutability!
1036
+ raise ImmutableRelation if @loaded
1037
+ raise ImmutableRelation if defined?(@arel) && @arel
1038
+ end
879
1039
 
880
- arel.distinct(distinct_value)
881
- arel.from(build_from) if from_value
882
- arel.lock(lock_value) if lock_value
1040
+ def build_arel(aliases)
1041
+ arel = Arel::SelectManager.new(table)
883
1042
 
884
- arel
885
- end
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)
1047
+ end
886
1048
 
887
- def symbol_unscoping(scope)
888
- if !VALID_UNSCOPING_VALUES.include?(scope)
889
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
890
- end
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?
891
1068
 
892
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
893
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
1069
+ build_order(arel)
894
1070
 
895
- case scope
896
- when :order
897
- result = []
898
- when :where
899
- self.bind_values = []
900
- else
901
- result = [] unless single_val_method
902
- end
1071
+ build_select(arel)
903
1072
 
904
- self.send(unscope_code, result)
905
- end
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?
906
1078
 
907
- def where_unscoping(target_value)
908
- target_value = target_value.to_s
1079
+ arel
1080
+ end
909
1081
 
910
- self.where_values = where_values.reject do |rel|
911
- case rel
912
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
913
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
914
- subrelation.name == target_value
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)
1092
+ else
1093
+ opts
915
1094
  end
916
1095
  end
917
1096
 
918
- bind_values.reject! { |col,_| col.name == target_value }
919
- end
920
-
921
- def custom_join_ast(table, joins)
922
- joins = joins.reject(&:blank?)
923
-
924
- return [] if joins.empty?
1097
+ def select_association_list(associations)
1098
+ result = []
1099
+ associations.each do |association|
1100
+ case association
1101
+ when Hash, Symbol, Array
1102
+ result << association
1103
+ else
1104
+ yield if block_given?
1105
+ end
1106
+ end
1107
+ result
1108
+ end
925
1109
 
926
- joins.map! do |join|
927
- case join
928
- when Array
929
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
930
- when String
931
- join = Arel.sql(join)
1110
+ def valid_association_list(associations)
1111
+ select_association_list(associations) do
1112
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
932
1113
  end
933
- table.create_string_join(join)
934
1114
  end
935
- end
936
1115
 
937
- def collapse_wheres(arel, wheres)
938
- predicates = wheres.map do |where|
939
- next where if ::Arel::Nodes::Equality === where
940
- where = Arel.sql(where) if String === where
941
- Arel::Nodes::Grouping.new(where)
1116
+ def build_left_outer_joins(manager, outer_joins, aliases)
1117
+ buckets = Hash.new { |h, k| h[k] = [] }
1118
+ buckets[:association_join] = valid_association_list(outer_joins)
1119
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
942
1120
  end
943
1121
 
944
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
945
- end
1122
+ def build_joins(manager, joins, aliases)
1123
+ buckets = Hash.new { |h, k| h[k] = [] }
946
1124
 
947
- def build_where(opts, other = [])
948
- case opts
949
- when String, Array
950
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
951
- when Hash
952
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
1125
+ unless left_outer_joins_values.empty?
1126
+ left_joins = valid_association_list(left_outer_joins_values.flatten)
1127
+ buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1128
+ end
953
1129
 
954
- tmp_opts, bind_values = create_binds(opts)
955
- self.bind_values += bind_values
1130
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1131
+ buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
1132
+ end
956
1133
 
957
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
958
- add_relations_to_bind_values(attributes)
1134
+ joins.map! do |join|
1135
+ if join.is_a?(String)
1136
+ table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1137
+ else
1138
+ join
1139
+ end
1140
+ end.delete_if(&:blank?).uniq!
959
1141
 
960
- PredicateBuilder.build_from_hash(klass, attributes, table)
961
- else
962
- [opts]
963
- end
964
- end
1142
+ while joins.first.is_a?(Arel::Nodes::Join)
1143
+ join_node = joins.shift
1144
+ if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
1145
+ buckets[:join_node] << join_node
1146
+ else
1147
+ buckets[:leading_join] << join_node
1148
+ end
1149
+ end
965
1150
 
966
- def create_binds(opts)
967
- bindable, non_binds = opts.partition do |column, value|
968
- PredicateBuilder.can_be_bound?(value) &&
969
- @klass.columns_hash.include?(column.to_s) &&
970
- !@klass.reflect_on_aggregation(column)
971
- end
1151
+ joins.each do |join|
1152
+ case join
1153
+ when Hash, Symbol, Array
1154
+ buckets[:association_join] << join
1155
+ when ActiveRecord::Associations::JoinDependency
1156
+ buckets[:stashed_join] << join
1157
+ when Arel::Nodes::Join
1158
+ buckets[:join_node] << join
1159
+ else
1160
+ raise "unknown class: %s" % join.class.name
1161
+ end
1162
+ end
972
1163
 
973
- association_binds, non_binds = non_binds.partition do |column, value|
974
- value.is_a?(Hash) && association_for_table(column)
1164
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
975
1165
  end
976
1166
 
977
- new_opts = {}
978
- binds = []
1167
+ def build_join_query(manager, buckets, join_type, aliases)
1168
+ association_joins = buckets[:association_join]
1169
+ stashed_joins = buckets[:stashed_join]
1170
+ leading_joins = buckets[:leading_join]
1171
+ join_nodes = buckets[:join_node]
979
1172
 
980
- connection = self.connection
1173
+ join_sources = manager.join_sources
1174
+ join_sources.concat(leading_joins) unless leading_joins.empty?
981
1175
 
982
- bindable.each do |(column,value)|
983
- binds.push [@klass.columns_hash[column.to_s], value]
984
- new_opts[column] = connection.substitute_at(column)
985
- end
1176
+ unless association_joins.empty? && stashed_joins.empty?
1177
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1178
+ join_dependency = construct_join_dependency(association_joins, join_type)
1179
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
1180
+ end
986
1181
 
987
- association_binds.each do |(column, value)|
988
- association_relation = association_for_table(column).klass.send(:relation)
989
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
990
- new_opts[column] = association_new_opts
991
- binds += association_bind
1182
+ join_sources.concat(join_nodes) unless join_nodes.empty?
992
1183
  end
993
1184
 
994
- non_binds.each { |column,value| new_opts[column] = value }
995
-
996
- [new_opts, binds]
997
- end
998
-
999
- def association_for_table(table_name)
1000
- table_name = table_name.to_s
1001
- @klass._reflect_on_association(table_name) ||
1002
- @klass._reflect_on_association(table_name.singularize)
1003
- end
1185
+ def build_select(arel)
1186
+ if select_values.any?
1187
+ arel.project(*arel_columns(select_values.uniq))
1188
+ elsif klass.ignored_columns.any?
1189
+ arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1190
+ else
1191
+ arel.project(table[Arel.star])
1192
+ end
1193
+ end
1004
1194
 
1005
- def build_from
1006
- opts, name = from_value
1007
- case opts
1008
- when Relation
1009
- name ||= 'subquery'
1010
- opts.arel.as(name.to_s)
1011
- else
1012
- opts
1195
+ def arel_columns(columns)
1196
+ columns.flat_map do |field|
1197
+ case field
1198
+ when Symbol
1199
+ arel_column(field.to_s) do |attr_name|
1200
+ connection.quote_table_name(attr_name)
1201
+ end
1202
+ when String
1203
+ arel_column(field, &:itself)
1204
+ when Proc
1205
+ field.call
1206
+ else
1207
+ field
1208
+ end
1209
+ end
1013
1210
  end
1014
- end
1015
1211
 
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
1212
+ def arel_column(field)
1213
+ field = klass.attribute_aliases[field] || field
1214
+ from = from_clause.name || from_clause.value
1215
+
1216
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1217
+ arel_attribute(field)
1027
1218
  else
1028
- raise 'unknown class: %s' % join.class.name
1219
+ yield field
1029
1220
  end
1030
1221
  end
1031
1222
 
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
1223
+ def table_name_matches?(from)
1224
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1225
+ end
1036
1226
 
1037
- join_list = join_nodes + custom_join_ast(manager, string_joins)
1227
+ def reverse_sql_order(order_query)
1228
+ if order_query.empty?
1229
+ return [arel_attribute(primary_key).desc] if primary_key
1230
+ raise IrreversibleOrderError,
1231
+ "Relation has no current order and table has no primary key to be used as default order"
1232
+ end
1038
1233
 
1039
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1040
- @klass,
1041
- association_joins,
1042
- join_list
1043
- )
1234
+ order_query.flat_map do |o|
1235
+ case o
1236
+ when Arel::Attribute
1237
+ o.desc
1238
+ when Arel::Nodes::Ordering
1239
+ o.reverse
1240
+ when String
1241
+ if does_not_support_reverse?(o)
1242
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1243
+ end
1244
+ o.split(",").map! do |s|
1245
+ s.strip!
1246
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1247
+ end
1248
+ else
1249
+ o
1250
+ end
1251
+ end
1252
+ end
1044
1253
 
1045
- join_infos = join_dependency.join_constraints stashed_association_joins
1254
+ def does_not_support_reverse?(order)
1255
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
1256
+ # override methods like #count.
1257
+ order = String.new(order) unless order.instance_of?(String)
1046
1258
 
1047
- join_infos.each do |info|
1048
- info.joins.each { |join| manager.from(join) }
1049
- manager.bind_values.concat info.binds
1259
+ # Uses SQL function with multiple arguments.
1260
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1261
+ # Uses "nulls first" like construction.
1262
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1050
1263
  end
1051
1264
 
1052
- manager.join_sources.concat(join_list)
1265
+ def build_order(arel)
1266
+ orders = order_values.uniq
1267
+ orders.reject!(&:blank?)
1053
1268
 
1054
- manager
1055
- end
1269
+ arel.order(*orders) unless orders.empty?
1270
+ end
1056
1271
 
1057
- def build_select(arel)
1058
- if select_values.any?
1059
- arel.project(*arel_columns(select_values.uniq))
1060
- else
1061
- arel.project(@klass.arel_table[Arel.star])
1272
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1273
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1274
+
1275
+ def validate_order_args(args)
1276
+ args.each do |arg|
1277
+ next unless arg.is_a?(Hash)
1278
+ arg.each do |_key, value|
1279
+ unless VALID_DIRECTIONS.include?(value)
1280
+ raise ArgumentError,
1281
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1282
+ end
1283
+ end
1284
+ end
1062
1285
  end
1063
- end
1064
1286
 
1065
- def arel_columns(columns)
1066
- columns.map do |field|
1067
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value
1068
- arel_table[field]
1069
- elsif Symbol === field
1070
- connection.quote_table_name(field.to_s)
1071
- else
1072
- field
1287
+ def preprocess_order_args(order_args)
1288
+ order_args.reject!(&:blank?)
1289
+ order_args.map! do |arg|
1290
+ klass.sanitize_sql_for_order(arg)
1073
1291
  end
1292
+ order_args.flatten!
1293
+
1294
+ @klass.disallow_raw_sql!(
1295
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1296
+ permit: connection.column_name_with_order_matcher
1297
+ )
1298
+
1299
+ validate_order_args(order_args)
1300
+
1301
+ references = order_args.grep(String)
1302
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1303
+ references!(references) if references.any?
1304
+
1305
+ # if a symbol is given we prepend the quoted table name
1306
+ order_args.map! do |arg|
1307
+ case arg
1308
+ when Symbol
1309
+ order_column(arg.to_s).asc
1310
+ when Hash
1311
+ arg.map { |field, dir|
1312
+ case field
1313
+ when Arel::Nodes::SqlLiteral
1314
+ field.send(dir.downcase)
1315
+ else
1316
+ order_column(field.to_s).send(dir.downcase)
1317
+ end
1318
+ }
1319
+ else
1320
+ arg
1321
+ end
1322
+ end.flatten!
1074
1323
  end
1075
- end
1076
1324
 
1077
- def reverse_sql_order(order_query)
1078
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
1079
-
1080
- order_query.flat_map do |o|
1081
- case o
1082
- when Arel::Nodes::Ordering
1083
- o.reverse
1084
- when String
1085
- o.to_s.split(',').map! do |s|
1086
- s.strip!
1087
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
1325
+ def order_column(field)
1326
+ arel_column(field) do |attr_name|
1327
+ if attr_name == "count" && !group_values.empty?
1328
+ arel_attribute(attr_name)
1329
+ else
1330
+ Arel.sql(connection.quote_table_name(attr_name))
1088
1331
  end
1089
- else
1090
- o
1091
1332
  end
1092
1333
  end
1093
- end
1094
1334
 
1095
- def array_of_strings?(o)
1096
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
1097
- end
1098
-
1099
- def build_order(arel)
1100
- orders = order_values.uniq
1101
- orders.reject!(&:blank?)
1102
-
1103
- arel.order(*orders) unless orders.empty?
1104
- end
1105
-
1106
- VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1107
- 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
1108
-
1109
- def validate_order_args(args)
1110
- args.each do |arg|
1111
- next unless arg.is_a?(Hash)
1112
- arg.each do |_key, value|
1113
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
1114
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
1335
+ # Checks to make sure that the arguments are not blank. Note that if some
1336
+ # blank-like object were initially passed into the query method, then this
1337
+ # method will not raise an error.
1338
+ #
1339
+ # Example:
1340
+ #
1341
+ # Post.references() # raises an error
1342
+ # Post.references([]) # does not raise an error
1343
+ #
1344
+ # This particular method should be called with a method_name and the args
1345
+ # passed into that method as an input. For example:
1346
+ #
1347
+ # def references(*args)
1348
+ # check_if_method_has_arguments!("references", args)
1349
+ # ...
1350
+ # end
1351
+ def check_if_method_has_arguments!(method_name, args)
1352
+ if args.blank?
1353
+ raise ArgumentError, "The method .#{method_name}() must contain arguments."
1115
1354
  end
1116
1355
  end
1117
- end
1118
-
1119
- def preprocess_order_args(order_args)
1120
- order_args.flatten!
1121
- validate_order_args(order_args)
1122
1356
 
1123
- references = order_args.grep(String)
1124
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
1125
- references!(references) if references.any?
1126
-
1127
- # if a symbol is given we prepend the quoted table name
1128
- order_args.map! do |arg|
1129
- case arg
1130
- when Symbol
1131
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
1132
- table[arg].asc
1133
- when Hash
1134
- arg.map { |field, dir|
1135
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1136
- table[field].send(dir.downcase)
1137
- }
1138
- else
1139
- arg
1357
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1358
+ def structurally_incompatible_values_for_or(other)
1359
+ values = other.values
1360
+ STRUCTURAL_OR_METHODS.reject do |method|
1361
+ default = DEFAULT_VALUES[method]
1362
+ @values.fetch(method, default) == values.fetch(method, default)
1140
1363
  end
1141
- end.flatten!
1142
- end
1364
+ end
1143
1365
 
1144
- # Checks to make sure that the arguments are not blank. Note that if some
1145
- # blank-like object were initially passed into the query method, then this
1146
- # method will not raise an error.
1147
- #
1148
- # Example:
1149
- #
1150
- # Post.references() # => raises an error
1151
- # Post.references([]) # => does not raise an error
1152
- #
1153
- # This particular method should be called with a method_name and the args
1154
- # passed into that method as an input. For example:
1155
- #
1156
- # def references(*args)
1157
- # check_if_method_has_arguments!("references", args)
1158
- # ...
1159
- # end
1160
- def check_if_method_has_arguments!(method_name, args)
1161
- if args.blank?
1162
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1366
+ def where_clause_factory
1367
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1163
1368
  end
1164
- end
1369
+ alias having_clause_factory where_clause_factory
1165
1370
 
1166
- def add_relations_to_bind_values(attributes)
1167
- if attributes.is_a?(Hash)
1168
- attributes.each_value do |value|
1169
- if value.is_a?(ActiveRecord::Relation)
1170
- self.bind_values += value.arel.bind_values + value.bind_values
1171
- else
1172
- add_relations_to_bind_values(value)
1173
- end
1174
- end
1371
+ DEFAULT_VALUES = {
1372
+ create_with: FROZEN_EMPTY_HASH,
1373
+ where: Relation::WhereClause.empty,
1374
+ having: Relation::WhereClause.empty,
1375
+ from: Relation::FromClause.empty
1376
+ }
1377
+
1378
+ Relation::MULTI_VALUE_METHODS.each do |value|
1379
+ DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1175
1380
  end
1176
- end
1177
1381
  end
1178
1382
  end