activerecord 5.2.8 → 7.0.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 (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +155 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +42 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -3,20 +3,16 @@
3
3
  require "active_record/relation/from_clause"
4
4
  require "active_record/relation/query_attribute"
5
5
  require "active_record/relation/where_clause"
6
- require "active_record/relation/where_clause_factory"
7
6
  require "active_model/forbidden_attributes_protection"
7
+ require "active_support/core_ext/array/wrap"
8
8
 
9
9
  module ActiveRecord
10
10
  module QueryMethods
11
- extend ActiveSupport::Concern
12
-
13
11
  include ActiveModel::ForbiddenAttributesProtection
14
12
 
15
13
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
14
  # In this case, #where must be chained with #not to return a new relation.
17
15
  class WhereChain
18
- include ActiveModel::ForbiddenAttributesProtection
19
-
20
16
  def initialize(scope)
21
17
  @scope = scope
22
18
  end
@@ -43,36 +39,104 @@ module ActiveRecord
43
39
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
44
40
  #
45
41
  # User.where.not(name: "Jon", role: "admin")
46
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
42
+ # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
47
43
  def not(opts, *rest)
48
- opts = sanitize_forbidden_attributes(opts)
44
+ where_clause = @scope.send(:build_where_clause, opts, rest)
49
45
 
50
- where_clause = @scope.send(:where_clause_factory).build(opts, rest)
51
-
52
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
53
46
  @scope.where_clause += where_clause.invert
47
+
48
+ @scope
49
+ end
50
+
51
+ # Returns a new relation with joins and where clause to identify
52
+ # associated relations.
53
+ #
54
+ # For example, posts that are associated to a related author:
55
+ #
56
+ # Post.where.associated(:author)
57
+ # # SELECT "posts".* FROM "posts"
58
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
59
+ # # WHERE "authors"."id" IS NOT NULL
60
+ #
61
+ # Additionally, multiple relations can be combined. This will return posts
62
+ # associated to both an author and any comments:
63
+ #
64
+ # Post.where.associated(:author, :comments)
65
+ # # SELECT "posts".* FROM "posts"
66
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
67
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
68
+ # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
69
+ def associated(*associations)
70
+ associations.each do |association|
71
+ reflection = scope_association_reflection(association)
72
+ @scope.joins!(association)
73
+ self.not(reflection.table_name => { reflection.association_primary_key => nil })
74
+ end
75
+
76
+ @scope
77
+ end
78
+
79
+ # Returns a new relation with left outer joins and where clause to identify
80
+ # missing relations.
81
+ #
82
+ # For example, posts that are missing a related author:
83
+ #
84
+ # Post.where.missing(:author)
85
+ # # SELECT "posts".* FROM "posts"
86
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
87
+ # # WHERE "authors"."id" IS NULL
88
+ #
89
+ # Additionally, multiple relations can be combined. This will return posts
90
+ # that are missing both an author and any comments:
91
+ #
92
+ # Post.where.missing(:author, :comments)
93
+ # # SELECT "posts".* FROM "posts"
94
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
95
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
96
+ # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
97
+ def missing(*associations)
98
+ associations.each do |association|
99
+ reflection = scope_association_reflection(association)
100
+ @scope.left_outer_joins!(association)
101
+ @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
102
+ end
103
+
54
104
  @scope
55
105
  end
106
+
107
+ private
108
+ def scope_association_reflection(association)
109
+ reflection = @scope.klass._reflect_on_association(association)
110
+ unless reflection
111
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
112
+ end
113
+ reflection
114
+ end
56
115
  end
57
116
 
58
117
  FROZEN_EMPTY_ARRAY = [].freeze
59
118
  FROZEN_EMPTY_HASH = {}.freeze
60
119
 
61
120
  Relation::VALUE_METHODS.each do |name|
62
- method_name = \
121
+ method_name, default =
63
122
  case name
64
- when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
65
- when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
66
- when *Relation::CLAUSE_METHODS then "#{name}_clause"
123
+ when *Relation::MULTI_VALUE_METHODS
124
+ ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
125
+ when *Relation::SINGLE_VALUE_METHODS
126
+ ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
127
+ when *Relation::CLAUSE_METHODS
128
+ ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
67
129
  end
68
- class_eval <<-CODE, __FILE__, __LINE__ + 1
69
- def #{method_name} # def includes_values
70
- get_value(#{name.inspect}) # get_value(:includes)
71
- end # end
72
130
 
73
- def #{method_name}=(value) # def includes_values=(value)
74
- set_value(#{name.inspect}, value) # set_value(:includes, value)
75
- end # end
131
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
132
+ def #{method_name} # def includes_values
133
+ @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
134
+ end # end
135
+
136
+ def #{method_name}=(value) # def includes_values=(value)
137
+ assert_mutability! # assert_mutability!
138
+ @values[:#{name}] = value # @values[:includes] = value
139
+ end # end
76
140
  CODE
77
141
  end
78
142
 
@@ -100,7 +164,7 @@ module ActiveRecord
100
164
  #
101
165
  # === conditions
102
166
  #
103
- # If you want to add conditions to your included models you'll have
167
+ # If you want to add string conditions to your included models, you'll have
104
168
  # to explicitly reference them. For example:
105
169
  #
106
170
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -111,15 +175,18 @@ module ActiveRecord
111
175
  #
112
176
  # Note that #includes works with association names while #references needs
113
177
  # the actual table name.
178
+ #
179
+ # If you pass the conditions via hash, you don't need to call #references
180
+ # explicitly, as #where references the tables for you. For example, this
181
+ # will work correctly:
182
+ #
183
+ # User.includes(:posts).where(posts: { name: 'example' })
114
184
  def includes(*args)
115
- check_if_method_has_arguments!(:includes, args)
185
+ check_if_method_has_arguments!(__callee__, args)
116
186
  spawn.includes!(*args)
117
187
  end
118
188
 
119
189
  def includes!(*args) # :nodoc:
120
- args.reject!(&:blank?)
121
- args.flatten!
122
-
123
190
  self.includes_values |= args
124
191
  self
125
192
  end
@@ -131,12 +198,12 @@ module ActiveRecord
131
198
  # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
132
199
  # # "users"."id"
133
200
  def eager_load(*args)
134
- check_if_method_has_arguments!(:eager_load, args)
201
+ check_if_method_has_arguments!(__callee__, args)
135
202
  spawn.eager_load!(*args)
136
203
  end
137
204
 
138
205
  def eager_load!(*args) # :nodoc:
139
- self.eager_load_values += args
206
+ self.eager_load_values |= args
140
207
  self
141
208
  end
142
209
 
@@ -145,15 +212,28 @@ module ActiveRecord
145
212
  # User.preload(:posts)
146
213
  # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
147
214
  def preload(*args)
148
- check_if_method_has_arguments!(:preload, args)
215
+ check_if_method_has_arguments!(__callee__, args)
149
216
  spawn.preload!(*args)
150
217
  end
151
218
 
152
219
  def preload!(*args) # :nodoc:
153
- self.preload_values += args
220
+ self.preload_values |= args
154
221
  self
155
222
  end
156
223
 
224
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
225
+ # then the individual association records are collected from the relation. Like so:
226
+ #
227
+ # account.memberships.extract_associated(:user)
228
+ # # => Returns collection of User records
229
+ #
230
+ # This is short-hand for:
231
+ #
232
+ # account.memberships.preload(:user).collect(&:user)
233
+ def extract_associated(association)
234
+ preload(association).collect(&association)
235
+ end
236
+
157
237
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
158
238
  # and should therefore be JOINed in any query rather than loaded separately.
159
239
  # This method only works in conjunction with #includes.
@@ -165,14 +245,11 @@ module ActiveRecord
165
245
  # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
166
246
  # # Query now knows the string references posts, so adds a JOIN
167
247
  def references(*table_names)
168
- check_if_method_has_arguments!(:references, table_names)
248
+ check_if_method_has_arguments!(__callee__, table_names)
169
249
  spawn.references!(*table_names)
170
250
  end
171
251
 
172
252
  def references!(*table_names) # :nodoc:
173
- table_names.flatten!
174
- table_names.map!(&:to_s)
175
-
176
253
  self.references_values |= table_names
177
254
  self
178
255
  end
@@ -226,13 +303,33 @@ module ActiveRecord
226
303
  return super()
227
304
  end
228
305
 
229
- raise ArgumentError, "Call `select' with at least one field" if fields.empty?
306
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
230
307
  spawn._select!(*fields)
231
308
  end
232
309
 
233
310
  def _select!(*fields) # :nodoc:
234
- fields.flatten!
235
- self.select_values += fields
311
+ self.select_values |= fields
312
+ self
313
+ end
314
+
315
+ # Allows you to change a previously set select statement.
316
+ #
317
+ # Post.select(:title, :body)
318
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
319
+ #
320
+ # Post.select(:title, :body).reselect(:created_at)
321
+ # # SELECT `posts`.`created_at` FROM `posts`
322
+ #
323
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
324
+ # Note that we're unscoping the entire select statement.
325
+ def reselect(*args)
326
+ check_if_method_has_arguments!(__callee__, args)
327
+ spawn.reselect!(*args)
328
+ end
329
+
330
+ # Same as #reselect but operates on relation in-place instead of copying.
331
+ def reselect!(*args) # :nodoc:
332
+ self.select_values = args
236
333
  self
237
334
  end
238
335
 
@@ -257,28 +354,46 @@ module ActiveRecord
257
354
  # User.select([:id, :first_name]).group(:id, :first_name).first(3)
258
355
  # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
259
356
  def group(*args)
260
- check_if_method_has_arguments!(:group, args)
357
+ check_if_method_has_arguments!(__callee__, args)
261
358
  spawn.group!(*args)
262
359
  end
263
360
 
264
361
  def group!(*args) # :nodoc:
265
- args.flatten!
266
-
267
362
  self.group_values += args
268
363
  self
269
364
  end
270
365
 
271
- # Allows to specify an order attribute:
366
+ # Applies an <code>ORDER BY</code> clause to a query.
367
+ #
368
+ # #order accepts arguments in one of several formats.
369
+ #
370
+ # === symbols
371
+ #
372
+ # The symbol represents the name of the column you want to order the results by.
272
373
  #
273
374
  # User.order(:name)
274
375
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
275
376
  #
377
+ # By default, the order is ascending. If you want descending order, you can
378
+ # map the column name symbol to +:desc+.
379
+ #
276
380
  # User.order(email: :desc)
277
381
  # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
278
382
  #
383
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
384
+ #
279
385
  # User.order(:name, email: :desc)
280
386
  # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
281
387
  #
388
+ # === strings
389
+ #
390
+ # Strings are passed directly to the database, allowing you to specify
391
+ # simple SQL expressions.
392
+ #
393
+ # This could be a source of SQL injection, so only strings composed of plain
394
+ # column names and simple <code>function(column_name)</code> expressions
395
+ # with optional +ASC+/+DESC+ modifiers are allowed.
396
+ #
282
397
  # User.order('name')
283
398
  # # SELECT "users".* FROM "users" ORDER BY name
284
399
  #
@@ -287,19 +402,56 @@ module ActiveRecord
287
402
  #
288
403
  # User.order('name DESC, email')
289
404
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
405
+ #
406
+ # === Arel
407
+ #
408
+ # If you need to pass in complicated expressions that you have verified
409
+ # are safe for the database, you can use Arel.
410
+ #
411
+ # User.order(Arel.sql('end_date - start_date'))
412
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
413
+ #
414
+ # Custom query syntax, like JSON columns for Postgres, is supported in this way.
415
+ #
416
+ # User.order(Arel.sql("payload->>'kind'"))
417
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
290
418
  def order(*args)
291
- check_if_method_has_arguments!(:order, args)
419
+ check_if_method_has_arguments!(__callee__, args) do
420
+ sanitize_order_arguments(args)
421
+ end
292
422
  spawn.order!(*args)
293
423
  end
294
424
 
295
425
  # Same as #order but operates on relation in-place instead of copying.
296
426
  def order!(*args) # :nodoc:
297
- preprocess_order_args(args)
298
-
299
- self.order_values += args
427
+ preprocess_order_args(args) unless args.empty?
428
+ self.order_values |= args
300
429
  self
301
430
  end
302
431
 
432
+ # Allows to specify an order by a specific set of values. Depending on your
433
+ # adapter this will either use a CASE statement or a built-in function.
434
+ #
435
+ # User.in_order_of(:id, [1, 5, 3])
436
+ # # SELECT "users".* FROM "users"
437
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
438
+ # # WHERE "users"."id" IN (1, 5, 3)
439
+ #
440
+ def in_order_of(column, values)
441
+ klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
442
+ return spawn.none! if values.empty?
443
+
444
+ references = column_references([column])
445
+ self.references_values |= references unless references.empty?
446
+
447
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
448
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
449
+
450
+ spawn
451
+ .order!(connection.field_ordered_value(arel_column, values))
452
+ .where!(arel_column.in(values))
453
+ end
454
+
303
455
  # Replaces any existing order defined on the relation with the specified order.
304
456
  #
305
457
  # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
@@ -310,22 +462,24 @@ module ActiveRecord
310
462
  #
311
463
  # generates a query with 'ORDER BY id ASC, name ASC'.
312
464
  def reorder(*args)
313
- check_if_method_has_arguments!(:reorder, args)
465
+ check_if_method_has_arguments!(__callee__, args) do
466
+ sanitize_order_arguments(args)
467
+ end
314
468
  spawn.reorder!(*args)
315
469
  end
316
470
 
317
471
  # Same as #reorder but operates on relation in-place instead of copying.
318
472
  def reorder!(*args) # :nodoc:
319
473
  preprocess_order_args(args)
320
-
474
+ args.uniq!
321
475
  self.reordering_value = true
322
476
  self.order_values = args
323
477
  self
324
478
  end
325
479
 
326
480
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
327
- :limit, :offset, :joins, :left_outer_joins,
328
- :includes, :from, :readonly, :having])
481
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
482
+ :includes, :from, :readonly, :having, :optimizer_hints])
329
483
 
330
484
  # Removes an unwanted relation that is already defined on a chain of relations.
331
485
  # This is useful when passing around chains of relations and would like to
@@ -361,12 +515,11 @@ module ActiveRecord
361
515
  # has_many :comments, -> { unscope(where: :trashed) }
362
516
  #
363
517
  def unscope(*args)
364
- check_if_method_has_arguments!(:unscope, args)
518
+ check_if_method_has_arguments!(__callee__, args)
365
519
  spawn.unscope!(*args)
366
520
  end
367
521
 
368
522
  def unscope!(*args) # :nodoc:
369
- args.flatten!
370
523
  self.unscope_values += args
371
524
 
372
525
  args.each do |scope|
@@ -376,14 +529,15 @@ module ActiveRecord
376
529
  if !VALID_UNSCOPING_VALUES.include?(scope)
377
530
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
378
531
  end
379
- set_value(scope, DEFAULT_VALUES[scope])
532
+ assert_mutability!
533
+ @values.delete(scope)
380
534
  when Hash
381
535
  scope.each do |key, target_value|
382
536
  if key != :where
383
537
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
384
538
  end
385
539
 
386
- target_values = Array(target_value).map(&:to_s)
540
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
387
541
  self.where_clause = where_clause.except(*target_values)
388
542
  end
389
543
  else
@@ -394,7 +548,7 @@ module ActiveRecord
394
548
  self
395
549
  end
396
550
 
397
- # Performs a joins on +args+. The given symbol(s) should match the name of
551
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
398
552
  # the association(s).
399
553
  #
400
554
  # User.joins(:posts)
@@ -416,26 +570,23 @@ module ActiveRecord
416
570
  # # SELECT "users".*
417
571
  # # FROM "users"
418
572
  # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
419
- # # INNER JOIN "comments" "comments_posts"
420
- # # ON "comments_posts"."post_id" = "posts"."id"
573
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
421
574
  #
422
575
  # You can use strings in order to customize your joins:
423
576
  #
424
577
  # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
425
578
  # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
426
579
  def joins(*args)
427
- check_if_method_has_arguments!(:joins, args)
580
+ check_if_method_has_arguments!(__callee__, args)
428
581
  spawn.joins!(*args)
429
582
  end
430
583
 
431
584
  def joins!(*args) # :nodoc:
432
- args.compact!
433
- args.flatten!
434
- self.joins_values += args
585
+ self.joins_values |= args
435
586
  self
436
587
  end
437
588
 
438
- # Performs a left outer joins on +args+:
589
+ # Performs LEFT OUTER JOINs on +args+:
439
590
  #
440
591
  # User.left_outer_joins(:posts)
441
592
  # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@@ -447,9 +598,7 @@ module ActiveRecord
447
598
  alias :left_joins :left_outer_joins
448
599
 
449
600
  def left_outer_joins!(*args) # :nodoc:
450
- args.compact!
451
- args.flatten!
452
- self.left_outer_joins_values += args
601
+ self.left_outer_joins_values |= args
453
602
  self
454
603
  end
455
604
 
@@ -519,13 +668,13 @@ module ActiveRecord
519
668
  #
520
669
  # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
521
670
  #
522
- # User.where({ name: "Joe", email: "joe@example.com" })
671
+ # User.where(name: "Joe", email: "joe@example.com")
523
672
  # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
524
673
  #
525
- # User.where({ name: ["Alice", "Bob"]})
674
+ # User.where(name: ["Alice", "Bob"])
526
675
  # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
527
676
  #
528
- # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
677
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
529
678
  # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
530
679
  #
531
680
  # In the case of a belongs_to relationship, an association key can be used
@@ -555,8 +704,8 @@ module ActiveRecord
555
704
  #
556
705
  # For hash conditions, you can either use the table name in the key, or use a sub-hash.
557
706
  #
558
- # User.joins(:posts).where({ "posts.published" => true })
559
- # User.joins(:posts).where({ posts: { published: true } })
707
+ # User.joins(:posts).where("posts.published" => true)
708
+ # User.joins(:posts).where(posts: { published: true })
560
709
  #
561
710
  # === no argument
562
711
  #
@@ -572,20 +721,18 @@ module ActiveRecord
572
721
  #
573
722
  # If the condition is any blank-ish object, then #where is a no-op and returns
574
723
  # the current relation.
575
- def where(opts = :chain, *rest)
576
- if :chain == opts
724
+ def where(*args)
725
+ if args.empty?
577
726
  WhereChain.new(spawn)
578
- elsif opts.blank?
727
+ elsif args.length == 1 && args.first.blank?
579
728
  self
580
729
  else
581
- spawn.where!(opts, *rest)
730
+ spawn.where!(*args)
582
731
  end
583
732
  end
584
733
 
585
734
  def where!(opts, *rest) # :nodoc:
586
- opts = sanitize_forbidden_attributes(opts)
587
- references!(PredicateBuilder.references(opts)) if Hash === opts
588
- self.where_clause += where_clause_factory.build(opts, rest)
735
+ self.where_clause += build_where_clause(opts, rest)
589
736
  self
590
737
  end
591
738
 
@@ -603,7 +750,97 @@ module ActiveRecord
603
750
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
604
751
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
605
752
  def rewhere(conditions)
606
- unscope(where: conditions.keys).where(conditions)
753
+ scope = spawn
754
+ where_clause = scope.build_where_clause(conditions)
755
+
756
+ scope.unscope!(where: where_clause.extract_attributes)
757
+ scope.where_clause += where_clause
758
+ scope
759
+ end
760
+
761
+ # Allows you to invert an entire where clause instead of manually applying conditions.
762
+ #
763
+ # class User
764
+ # scope :active, -> { where(accepted: true, locked: false) }
765
+ # end
766
+ #
767
+ # User.where(accepted: true)
768
+ # # WHERE `accepted` = 1
769
+ #
770
+ # User.where(accepted: true).invert_where
771
+ # # WHERE `accepted` != 1
772
+ #
773
+ # User.active
774
+ # # WHERE `accepted` = 1 AND `locked` = 0
775
+ #
776
+ # User.active.invert_where
777
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
778
+ #
779
+ # Be careful because this inverts all conditions before +invert_where+ call.
780
+ #
781
+ # class User
782
+ # scope :active, -> { where(accepted: true, locked: false) }
783
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
784
+ # end
785
+ #
786
+ # # It also inverts `where(role: 'admin')` unexpectedly.
787
+ # User.where(role: 'admin').inactive
788
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
789
+ #
790
+ def invert_where
791
+ spawn.invert_where!
792
+ end
793
+
794
+ def invert_where! # :nodoc:
795
+ self.where_clause = where_clause.invert
796
+ self
797
+ end
798
+
799
+ # Checks whether the given relation is structurally compatible with this relation, to determine
800
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
801
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
802
+ # #where (if no #group has been defined) or #having (if a #group is present).
803
+ #
804
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
805
+ # # => true
806
+ #
807
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
808
+ # # => false
809
+ #
810
+ def structurally_compatible?(other)
811
+ structurally_incompatible_values_for(other).empty?
812
+ end
813
+
814
+ # Returns a new relation, which is the logical intersection of this relation and the one passed
815
+ # as an argument.
816
+ #
817
+ # The two relations must be structurally compatible: they must be scoping the same model, and
818
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
819
+ # present).
820
+ #
821
+ # Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
822
+ # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
823
+ #
824
+ def and(other)
825
+ if other.is_a?(Relation)
826
+ spawn.and!(other)
827
+ else
828
+ raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
829
+ end
830
+ end
831
+
832
+ def and!(other) # :nodoc:
833
+ incompatible_values = structurally_incompatible_values_for(other)
834
+
835
+ unless incompatible_values.empty?
836
+ raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
837
+ end
838
+
839
+ self.where_clause |= other.where_clause
840
+ self.having_clause |= other.having_clause
841
+ self.references_values |= other.references_values
842
+
843
+ self
607
844
  end
608
845
 
609
846
  # Returns a new relation, which is the logical union of this relation and the one passed as an
@@ -611,21 +848,21 @@ module ActiveRecord
611
848
  #
612
849
  # The two relations must be structurally compatible: they must be scoping the same model, and
613
850
  # they must differ only by #where (if no #group has been defined) or #having (if a #group is
614
- # present). Neither relation may have a #limit, #offset, or #distinct set.
851
+ # present).
615
852
  #
616
853
  # Post.where("id = 1").or(Post.where("author_id = 3"))
617
854
  # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
618
855
  #
619
856
  def or(other)
620
- unless other.is_a? Relation
857
+ if other.is_a?(Relation)
858
+ spawn.or!(other)
859
+ else
621
860
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
622
861
  end
623
-
624
- spawn.or!(other)
625
862
  end
626
863
 
627
864
  def or!(other) # :nodoc:
628
- incompatible_values = structurally_incompatible_values_for_or(other)
865
+ incompatible_values = structurally_incompatible_values_for(other)
629
866
 
630
867
  unless incompatible_values.empty?
631
868
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
@@ -633,7 +870,7 @@ module ActiveRecord
633
870
 
634
871
  self.where_clause = self.where_clause.or(other.where_clause)
635
872
  self.having_clause = having_clause.or(other.having_clause)
636
- self.references_values += other.references_values
873
+ self.references_values |= other.references_values
637
874
 
638
875
  self
639
876
  end
@@ -647,10 +884,7 @@ module ActiveRecord
647
884
  end
648
885
 
649
886
  def having!(opts, *rest) # :nodoc:
650
- opts = sanitize_forbidden_attributes(opts)
651
- references!(PredicateBuilder.references(opts)) if Hash === opts
652
-
653
- self.having_clause += having_clause_factory.build(opts, rest)
887
+ self.having_clause += build_having_clause(opts, rest)
654
888
  self
655
889
  end
656
890
 
@@ -752,6 +986,21 @@ module ActiveRecord
752
986
  self
753
987
  end
754
988
 
989
+ # Sets the returned relation to strict_loading mode. This will raise an error
990
+ # if the record tries to lazily load an association.
991
+ #
992
+ # user = User.strict_loading.first
993
+ # user.comments.to_a
994
+ # => ActiveRecord::StrictLoadingViolationError
995
+ def strict_loading(value = true)
996
+ spawn.strict_loading!(value)
997
+ end
998
+
999
+ def strict_loading!(value = true) # :nodoc:
1000
+ self.strict_loading_value = value
1001
+ self
1002
+ end
1003
+
755
1004
  # Sets attributes to be used when creating new records from a
756
1005
  # relation object.
757
1006
  #
@@ -780,7 +1029,7 @@ module ActiveRecord
780
1029
  self
781
1030
  end
782
1031
 
783
- # Specifies table from which the records will be fetched. For example:
1032
+ # Specifies the table from which the records will be fetched. For example:
784
1033
  #
785
1034
  # Topic.select('title').from('posts')
786
1035
  # # SELECT title FROM posts
@@ -790,9 +1039,26 @@ module ActiveRecord
790
1039
  # Topic.select('title').from(Topic.approved)
791
1040
  # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
792
1041
  #
1042
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1043
+ #
793
1044
  # Topic.select('a.title').from(Topic.approved, :a)
794
1045
  # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
795
1046
  #
1047
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1048
+ #
1049
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1050
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1051
+ #
1052
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1053
+ #
1054
+ # color = "red"
1055
+ # Color
1056
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1057
+ # .where("colorvalue->>'color' = ?", color)
1058
+ # .select("c.*").to_a
1059
+ # # SELECT c.*
1060
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1061
+ # # WHERE (colorvalue->>'color' = 'red')
796
1062
  def from(value, subquery_name = nil)
797
1063
  spawn.from!(value, subquery_name)
798
1064
  end
@@ -876,6 +1142,27 @@ module ActiveRecord
876
1142
  self
877
1143
  end
878
1144
 
1145
+ # Specify optimizer hints to be used in the SELECT statement.
1146
+ #
1147
+ # Example (for MySQL):
1148
+ #
1149
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
1150
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
1151
+ #
1152
+ # Example (for PostgreSQL with pg_hint_plan):
1153
+ #
1154
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
1155
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
1156
+ def optimizer_hints(*args)
1157
+ check_if_method_has_arguments!(__callee__, args)
1158
+ spawn.optimizer_hints!(*args)
1159
+ end
1160
+
1161
+ def optimizer_hints!(*args) # :nodoc:
1162
+ self.optimizer_hints_values |= args
1163
+ self
1164
+ end
1165
+
879
1166
  # Reverse the existing order clause on the relation.
880
1167
  #
881
1168
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -884,8 +1171,7 @@ module ActiveRecord
884
1171
  end
885
1172
 
886
1173
  def reverse_order! # :nodoc:
887
- orders = order_values.uniq
888
- orders.reject!(&:blank?)
1174
+ orders = order_values.compact_blank
889
1175
  self.order_values = reverse_sql_order(orders)
890
1176
  self
891
1177
  end
@@ -895,68 +1181,189 @@ module ActiveRecord
895
1181
  self
896
1182
  end
897
1183
 
1184
+ def skip_preloading! # :nodoc:
1185
+ self.skip_preloading_value = true
1186
+ self
1187
+ end
1188
+
1189
+ # Adds an SQL comment to queries generated from this relation. For example:
1190
+ #
1191
+ # User.annotate("selecting user names").select(:name)
1192
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
1193
+ #
1194
+ # User.annotate("selecting", "user", "names").select(:name)
1195
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1196
+ #
1197
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1198
+ def annotate(*args)
1199
+ check_if_method_has_arguments!(__callee__, args)
1200
+ spawn.annotate!(*args)
1201
+ end
1202
+
1203
+ # Like #annotate, but modifies relation in place.
1204
+ def annotate!(*args) # :nodoc:
1205
+ self.annotate_values += args
1206
+ self
1207
+ end
1208
+
1209
+ # Deduplicate multiple values.
1210
+ def uniq!(name)
1211
+ if values = @values[name]
1212
+ values.uniq! if values.is_a?(Array) && !values.empty?
1213
+ end
1214
+ self
1215
+ end
1216
+
1217
+ # Excludes the specified record (or collection of records) from the resulting
1218
+ # relation. For example:
1219
+ #
1220
+ # Post.excluding(post)
1221
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1222
+ #
1223
+ # Post.excluding(post_one, post_two)
1224
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1225
+ #
1226
+ # This can also be called on associations. As with the above example, either
1227
+ # a single record of collection thereof may be specified:
1228
+ #
1229
+ # post = Post.find(1)
1230
+ # comment = Comment.find(2)
1231
+ # post.comments.excluding(comment)
1232
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1233
+ #
1234
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1235
+ #
1236
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1237
+ # specified, or if any of the records in the collection (if a collection
1238
+ # is passed in) are not instances of the same model that the relation is
1239
+ # scoping.
1240
+ def excluding(*records)
1241
+ records.flatten!(1)
1242
+ records.compact!
1243
+
1244
+ unless records.all?(klass)
1245
+ raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1246
+ end
1247
+
1248
+ spawn.excluding!(records)
1249
+ end
1250
+ alias :without :excluding
1251
+
1252
+ def excluding!(records) # :nodoc:
1253
+ predicates = [ predicate_builder[primary_key, records].invert ]
1254
+ self.where_clause += Relation::WhereClause.new(predicates)
1255
+ self
1256
+ end
1257
+
898
1258
  # Returns the Arel object associated with the relation.
899
1259
  def arel(aliases = nil) # :nodoc:
900
1260
  @arel ||= build_arel(aliases)
901
1261
  end
902
1262
 
903
- # Returns a relation value with a given name
904
- def get_value(name) # :nodoc:
905
- @values.fetch(name, DEFAULT_VALUES[name])
1263
+ def construct_join_dependency(associations, join_type) # :nodoc:
1264
+ ActiveRecord::Associations::JoinDependency.new(
1265
+ klass, table, associations, join_type
1266
+ )
906
1267
  end
907
1268
 
908
1269
  protected
1270
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1271
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
909
1272
 
910
- # Sets the relation value with the given name
911
- def set_value(name, value) # :nodoc:
912
- assert_mutability!
913
- @values[name] = value
1273
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1274
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1275
+ end
914
1276
  end
915
1277
 
1278
+ def build_where_clause(opts, rest = []) # :nodoc:
1279
+ opts = sanitize_forbidden_attributes(opts)
1280
+
1281
+ case opts
1282
+ when String, Array
1283
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1284
+ when Hash
1285
+ opts = opts.transform_keys do |key|
1286
+ key = key.to_s
1287
+ klass.attribute_aliases[key] || key
1288
+ end
1289
+ references = PredicateBuilder.references(opts)
1290
+ self.references_values |= references unless references.empty?
1291
+
1292
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1293
+ lookup_table_klass_from_join_dependencies(table_name)
1294
+ end
1295
+ when Arel::Nodes::Node
1296
+ parts = [opts]
1297
+ else
1298
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1299
+ end
1300
+
1301
+ Relation::WhereClause.new(parts)
1302
+ end
1303
+ alias :build_having_clause :build_where_clause
1304
+
916
1305
  private
1306
+ def lookup_table_klass_from_join_dependencies(table_name)
1307
+ each_join_dependencies do |join|
1308
+ return join.base_klass if table_name == join.table_name
1309
+ end
1310
+ nil
1311
+ end
1312
+
1313
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1314
+ join_dependencies.each do |join_dependency|
1315
+ join_dependency.each(&block)
1316
+ end
1317
+ end
1318
+
1319
+ def build_join_dependencies
1320
+ associations = joins_values | left_outer_joins_values
1321
+ associations |= eager_load_values unless eager_load_values.empty?
1322
+ associations |= includes_values unless includes_values.empty?
1323
+
1324
+ join_dependencies = []
1325
+ join_dependencies.unshift construct_join_dependency(
1326
+ select_association_list(associations, join_dependencies), nil
1327
+ )
1328
+ end
917
1329
 
918
1330
  def assert_mutability!
919
1331
  raise ImmutableRelation if @loaded
920
1332
  raise ImmutableRelation if defined?(@arel) && @arel
921
1333
  end
922
1334
 
923
- def build_arel(aliases)
1335
+ def build_arel(aliases = nil)
924
1336
  arel = Arel::SelectManager.new(table)
925
1337
 
926
- aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
927
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
1338
+ build_joins(arel.join_sources, aliases)
928
1339
 
929
1340
  arel.where(where_clause.ast) unless where_clause.empty?
930
1341
  arel.having(having_clause.ast) unless having_clause.empty?
931
- if limit_value
932
- limit_attribute = ActiveModel::Attribute.with_cast_value(
933
- "LIMIT".freeze,
934
- connection.sanitize_limit(limit_value),
935
- Type.default_value,
936
- )
937
- arel.take(Arel::Nodes::BindParam.new(limit_attribute))
938
- end
939
- if offset_value
940
- offset_attribute = ActiveModel::Attribute.with_cast_value(
941
- "OFFSET".freeze,
942
- offset_value.to_i,
943
- Type.default_value,
944
- )
945
- arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
946
- end
947
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1342
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1343
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1344
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
948
1345
 
949
1346
  build_order(arel)
950
-
951
1347
  build_select(arel)
952
1348
 
1349
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
953
1350
  arel.distinct(distinct_value)
954
1351
  arel.from(build_from) unless from_clause.empty?
955
1352
  arel.lock(lock_value) if lock_value
956
1353
 
1354
+ unless annotate_values.empty?
1355
+ annotates = annotate_values
1356
+ annotates = annotates.uniq if annotates.size > 1
1357
+ arel.comment(*annotates)
1358
+ end
1359
+
957
1360
  arel
958
1361
  end
959
1362
 
1363
+ def build_cast_value(name, value)
1364
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1365
+ end
1366
+
960
1367
  def build_from
961
1368
  opts = from_clause.value
962
1369
  name = from_clause.name
@@ -972,75 +1379,101 @@ module ActiveRecord
972
1379
  end
973
1380
  end
974
1381
 
975
- def build_left_outer_joins(manager, outer_joins, aliases)
976
- buckets = outer_joins.group_by do |join|
977
- case join
1382
+ def select_association_list(associations, stashed_joins = nil)
1383
+ result = []
1384
+ associations.each do |association|
1385
+ case association
978
1386
  when Hash, Symbol, Array
979
- :association_join
1387
+ result << association
980
1388
  when ActiveRecord::Associations::JoinDependency
981
- :stashed_join
1389
+ stashed_joins&.<< association
982
1390
  else
983
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1391
+ yield association if block_given?
984
1392
  end
985
1393
  end
1394
+ result
1395
+ end
986
1396
 
987
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1397
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
988
1398
  end
989
1399
 
990
- def build_joins(manager, joins, aliases)
991
- buckets = joins.group_by do |join|
992
- case join
993
- when String
994
- :string_join
995
- when Hash, Symbol, Array
996
- :association_join
997
- when ActiveRecord::Associations::JoinDependency
998
- :stashed_join
999
- when Arel::Nodes::Join
1000
- :join_node
1400
+ def build_join_buckets
1401
+ buckets = Hash.new { |h, k| h[k] = [] }
1402
+
1403
+ unless left_outer_joins_values.empty?
1404
+ stashed_left_joins = []
1405
+ left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1406
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1407
+ end
1408
+
1409
+ if joins_values.empty?
1410
+ buckets[:association_join] = left_joins
1411
+ buckets[:stashed_join] = stashed_left_joins
1412
+ return buckets, Arel::Nodes::OuterJoin
1413
+ else
1414
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1415
+ end
1416
+ end
1417
+
1418
+ joins = joins_values.dup
1419
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1420
+ stashed_eager_load = joins.pop if joins.last.base_klass == klass
1421
+ end
1422
+
1423
+ joins.each_with_index do |join, i|
1424
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
1425
+ end
1426
+
1427
+ while joins.first.is_a?(Arel::Nodes::Join)
1428
+ join_node = joins.shift
1429
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1430
+ buckets[:join_node] << join_node
1431
+ else
1432
+ buckets[:leading_join] << join_node
1433
+ end
1434
+ end
1435
+
1436
+ buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1437
+ if join.is_a?(Arel::Nodes::Join)
1438
+ buckets[:join_node] << join
1001
1439
  else
1002
1440
  raise "unknown class: %s" % join.class.name
1003
1441
  end
1004
1442
  end
1005
1443
 
1006
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1444
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1445
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1446
+
1447
+ return buckets, Arel::Nodes::InnerJoin
1007
1448
  end
1008
1449
 
1009
- def build_join_query(manager, buckets, join_type, aliases)
1010
- buckets.default = []
1450
+ def build_joins(join_sources, aliases = nil)
1451
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1452
+
1453
+ buckets, join_type = build_join_buckets
1011
1454
 
1012
1455
  association_joins = buckets[:association_join]
1013
1456
  stashed_joins = buckets[:stashed_join]
1014
- join_nodes = buckets[:join_node].uniq
1015
- string_joins = buckets[:string_join].map(&:strip).uniq
1016
-
1017
- join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1018
- alias_tracker = alias_tracker(join_list, aliases)
1019
-
1020
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
1021
- klass, table, association_joins
1022
- )
1457
+ leading_joins = buckets[:leading_join]
1458
+ join_nodes = buckets[:join_node]
1023
1459
 
1024
- joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1025
- joins.each { |join| manager.from(join) }
1460
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1026
1461
 
1027
- manager.join_sources.concat(join_list)
1028
-
1029
- alias_tracker.aliases
1030
- end
1462
+ unless association_joins.empty? && stashed_joins.empty?
1463
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1464
+ join_dependency = construct_join_dependency(association_joins, join_type)
1465
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1466
+ end
1031
1467
 
1032
- def convert_join_strings_to_ast(joins)
1033
- joins
1034
- .flatten
1035
- .reject(&:blank?)
1036
- .map { |join| table.create_string_join(Arel.sql(join)) }
1468
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1469
+ join_sources
1037
1470
  end
1038
1471
 
1039
1472
  def build_select(arel)
1040
1473
  if select_values.any?
1041
- arel.project(*arel_columns(select_values.uniq))
1042
- elsif klass.ignored_columns.any?
1043
- arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1474
+ arel.project(*arel_columns(select_values))
1475
+ elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1476
+ arel.project(*klass.column_names.map { |field| table[field] })
1044
1477
  else
1045
1478
  arel.project(table[Arel.star])
1046
1479
  end
@@ -1064,23 +1497,30 @@ module ActiveRecord
1064
1497
  end
1065
1498
 
1066
1499
  def arel_column(field)
1067
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1500
+ field = klass.attribute_aliases[field] || field
1068
1501
  from = from_clause.name || from_clause.value
1069
1502
 
1070
1503
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1071
- arel_attribute(field)
1504
+ table[field]
1505
+ elsif field.match?(/\A\w+\.\w+\z/)
1506
+ table, column = field.split(".")
1507
+ predicate_builder.resolve_arel_attribute(table, column) do
1508
+ lookup_table_klass_from_join_dependencies(table)
1509
+ end
1072
1510
  else
1073
1511
  yield field
1074
1512
  end
1075
1513
  end
1076
1514
 
1077
1515
  def table_name_matches?(from)
1078
- /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1516
+ table_name = Regexp.escape(table.name)
1517
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1518
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1079
1519
  end
1080
1520
 
1081
1521
  def reverse_sql_order(order_query)
1082
1522
  if order_query.empty?
1083
- return [arel_attribute(primary_key).desc] if primary_key
1523
+ return [table[primary_key].desc] if primary_key
1084
1524
  raise IrreversibleOrderError,
1085
1525
  "Relation has no current order and table has no primary key to be used as default order"
1086
1526
  end
@@ -1091,9 +1531,11 @@ module ActiveRecord
1091
1531
  o.desc
1092
1532
  when Arel::Nodes::Ordering
1093
1533
  o.reverse
1534
+ when Arel::Nodes::NodeExpression
1535
+ o.desc
1094
1536
  when String
1095
1537
  if does_not_support_reverse?(o)
1096
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1538
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1097
1539
  end
1098
1540
  o.split(",").map! do |s|
1099
1541
  s.strip!
@@ -1113,13 +1555,11 @@ module ActiveRecord
1113
1555
  # Uses SQL function with multiple arguments.
1114
1556
  (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1115
1557
  # Uses "nulls first" like construction.
1116
- /nulls (first|last)\Z/i.match?(order)
1558
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1117
1559
  end
1118
1560
 
1119
1561
  def build_order(arel)
1120
- orders = order_values.uniq
1121
- orders.reject!(&:blank?)
1122
-
1562
+ orders = order_values.compact_blank
1123
1563
  arel.order(*orders) unless orders.empty?
1124
1564
  end
1125
1565
 
@@ -1139,21 +1579,15 @@ module ActiveRecord
1139
1579
  end
1140
1580
 
1141
1581
  def preprocess_order_args(order_args)
1142
- order_args.map! do |arg|
1143
- klass.sanitize_sql_for_order(arg)
1144
- end
1145
- order_args.flatten!
1146
-
1147
- @klass.enforce_raw_sql_whitelist(
1582
+ @klass.disallow_raw_sql!(
1148
1583
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1149
- whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
1584
+ permit: connection.column_name_with_order_matcher
1150
1585
  )
1151
1586
 
1152
1587
  validate_order_args(order_args)
1153
1588
 
1154
- references = order_args.grep(String)
1155
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1156
- references!(references) if references.any?
1589
+ references = column_references(order_args)
1590
+ self.references_values |= references unless references.empty?
1157
1591
 
1158
1592
  # if a symbol is given we prepend the quoted table name
1159
1593
  order_args.map! do |arg|
@@ -1164,9 +1598,9 @@ module ActiveRecord
1164
1598
  arg.map { |field, dir|
1165
1599
  case field
1166
1600
  when Arel::Nodes::SqlLiteral
1167
- field.send(dir.downcase)
1601
+ field.public_send(dir.downcase)
1168
1602
  else
1169
- order_column(field.to_s).send(dir.downcase)
1603
+ order_column(field.to_s).public_send(dir.downcase)
1170
1604
  end
1171
1605
  }
1172
1606
  else
@@ -1175,16 +1609,59 @@ module ActiveRecord
1175
1609
  end.flatten!
1176
1610
  end
1177
1611
 
1612
+ def sanitize_order_arguments(order_args)
1613
+ order_args.map! do |arg|
1614
+ klass.sanitize_sql_for_order(arg)
1615
+ end
1616
+ end
1617
+
1618
+ def column_references(order_args)
1619
+ references = order_args.flat_map do |arg|
1620
+ case arg
1621
+ when String, Symbol
1622
+ arg
1623
+ when Hash
1624
+ arg.keys
1625
+ end
1626
+ end
1627
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1628
+ references
1629
+ end
1630
+
1178
1631
  def order_column(field)
1179
1632
  arel_column(field) do |attr_name|
1180
1633
  if attr_name == "count" && !group_values.empty?
1181
- arel_attribute(attr_name)
1634
+ table[attr_name]
1182
1635
  else
1183
1636
  Arel.sql(connection.quote_table_name(attr_name))
1184
1637
  end
1185
1638
  end
1186
1639
  end
1187
1640
 
1641
+ def resolve_arel_attributes(attrs)
1642
+ attrs.flat_map do |attr|
1643
+ case attr
1644
+ when Arel::Predications
1645
+ attr
1646
+ when Hash
1647
+ attr.flat_map do |table, columns|
1648
+ table = table.to_s
1649
+ Array(columns).map do |column|
1650
+ predicate_builder.resolve_arel_attribute(table, column)
1651
+ end
1652
+ end
1653
+ else
1654
+ attr = attr.to_s
1655
+ if attr.include?(".")
1656
+ table, column = attr.split(".", 2)
1657
+ predicate_builder.resolve_arel_attribute(table, column)
1658
+ else
1659
+ attr
1660
+ end
1661
+ end
1662
+ end
1663
+ end
1664
+
1188
1665
  # Checks to make sure that the arguments are not blank. Note that if some
1189
1666
  # blank-like object were initially passed into the query method, then this
1190
1667
  # method will not raise an error.
@@ -1194,40 +1671,40 @@ module ActiveRecord
1194
1671
  # Post.references() # raises an error
1195
1672
  # Post.references([]) # does not raise an error
1196
1673
  #
1197
- # This particular method should be called with a method_name and the args
1674
+ # This particular method should be called with a method_name (__callee__) and the args
1198
1675
  # passed into that method as an input. For example:
1199
1676
  #
1200
1677
  # def references(*args)
1201
- # check_if_method_has_arguments!("references", args)
1678
+ # check_if_method_has_arguments!(__callee__, args)
1202
1679
  # ...
1203
1680
  # end
1204
- def check_if_method_has_arguments!(method_name, args)
1681
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1205
1682
  if args.blank?
1206
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1207
- end
1208
- end
1683
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1684
+ else
1685
+ yield args if block_given?
1209
1686
 
1210
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1211
- def structurally_incompatible_values_for_or(other)
1212
- STRUCTURAL_OR_METHODS.reject do |method|
1213
- get_value(method) == other.get_value(method)
1687
+ args.flatten!
1688
+ args.compact_blank!
1214
1689
  end
1215
1690
  end
1216
1691
 
1217
- def where_clause_factory
1218
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1219
- end
1220
- alias having_clause_factory where_clause_factory
1221
-
1222
- DEFAULT_VALUES = {
1223
- create_with: FROZEN_EMPTY_HASH,
1224
- where: Relation::WhereClause.empty,
1225
- having: Relation::WhereClause.empty,
1226
- from: Relation::FromClause.empty
1227
- }
1228
-
1229
- Relation::MULTI_VALUE_METHODS.each do |value|
1230
- DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1692
+ STRUCTURAL_VALUE_METHODS = (
1693
+ Relation::VALUE_METHODS -
1694
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1695
+ ).freeze # :nodoc:
1696
+
1697
+ def structurally_incompatible_values_for(other)
1698
+ values = other.values
1699
+ STRUCTURAL_VALUE_METHODS.reject do |method|
1700
+ v1, v2 = @values[method], values[method]
1701
+ if v1.is_a?(Array)
1702
+ next true unless v2.is_a?(Array)
1703
+ v1 = v1.uniq
1704
+ v2 = v2.uniq
1705
+ end
1706
+ v1 == v2
1707
+ end
1231
1708
  end
1232
1709
  end
1233
1710
  end