omg-activerecord 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +355 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record/aggregations.rb +287 -0
  8. data/lib/active_record/association_relation.rb +50 -0
  9. data/lib/active_record/associations/alias_tracker.rb +90 -0
  10. data/lib/active_record/associations/association.rb +417 -0
  11. data/lib/active_record/associations/association_scope.rb +175 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +163 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  14. data/lib/active_record/associations/builder/association.rb +170 -0
  15. data/lib/active_record/associations/builder/belongs_to.rb +160 -0
  16. data/lib/active_record/associations/builder/collection_association.rb +80 -0
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
  18. data/lib/active_record/associations/builder/has_many.rb +23 -0
  19. data/lib/active_record/associations/builder/has_one.rb +61 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +48 -0
  21. data/lib/active_record/associations/collection_association.rb +535 -0
  22. data/lib/active_record/associations/collection_proxy.rb +1163 -0
  23. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  24. data/lib/active_record/associations/errors.rb +265 -0
  25. data/lib/active_record/associations/foreign_association.rb +40 -0
  26. data/lib/active_record/associations/has_many_association.rb +167 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +232 -0
  28. data/lib/active_record/associations/has_one_association.rb +142 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/join_dependency.rb +301 -0
  34. data/lib/active_record/associations/nested_error.rb +47 -0
  35. data/lib/active_record/associations/preloader/association.rb +316 -0
  36. data/lib/active_record/associations/preloader/batch.rb +48 -0
  37. data/lib/active_record/associations/preloader/branch.rb +153 -0
  38. data/lib/active_record/associations/preloader/through_association.rb +150 -0
  39. data/lib/active_record/associations/preloader.rb +135 -0
  40. data/lib/active_record/associations/singular_association.rb +76 -0
  41. data/lib/active_record/associations/through_association.rb +132 -0
  42. data/lib/active_record/associations.rb +1897 -0
  43. data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
  44. data/lib/active_record/attribute_assignment.rb +82 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
  46. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  47. data/lib/active_record/attribute_methods/dirty.rb +262 -0
  48. data/lib/active_record/attribute_methods/primary_key.rb +158 -0
  49. data/lib/active_record/attribute_methods/query.rb +50 -0
  50. data/lib/active_record/attribute_methods/read.rb +46 -0
  51. data/lib/active_record/attribute_methods/serialization.rb +232 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
  53. data/lib/active_record/attribute_methods/write.rb +49 -0
  54. data/lib/active_record/attribute_methods.rb +542 -0
  55. data/lib/active_record/attributes.rb +307 -0
  56. data/lib/active_record/autosave_association.rb +586 -0
  57. data/lib/active_record/base.rb +338 -0
  58. data/lib/active_record/callbacks.rb +452 -0
  59. data/lib/active_record/coders/column_serializer.rb +61 -0
  60. data/lib/active_record/coders/json.rb +15 -0
  61. data/lib/active_record/coders/yaml_column.rb +95 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
  64. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
  78. data/lib/active_record/connection_adapters/column.rb +122 -0
  79. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  80. data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
  81. data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
  82. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  83. data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  89. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
  90. data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
  91. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  92. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  93. data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
  94. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
  95. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
  115. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  116. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  117. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  118. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
  119. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
  120. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  121. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  122. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
  123. data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
  124. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
  125. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
  126. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
  127. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
  128. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
  129. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  130. data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
  131. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
  132. data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
  133. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  134. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  135. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
  136. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  137. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
  138. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
  139. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
  140. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
  141. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
  142. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
  143. data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
  144. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
  145. data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
  146. data/lib/active_record/connection_adapters.rb +176 -0
  147. data/lib/active_record/connection_handling.rb +413 -0
  148. data/lib/active_record/core.rb +836 -0
  149. data/lib/active_record/counter_cache.rb +230 -0
  150. data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
  151. data/lib/active_record/database_configurations/database_config.rb +104 -0
  152. data/lib/active_record/database_configurations/hash_config.rb +172 -0
  153. data/lib/active_record/database_configurations/url_config.rb +78 -0
  154. data/lib/active_record/database_configurations.rb +309 -0
  155. data/lib/active_record/delegated_type.rb +289 -0
  156. data/lib/active_record/deprecator.rb +7 -0
  157. data/lib/active_record/destroy_association_async_job.rb +38 -0
  158. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  159. data/lib/active_record/dynamic_matchers.rb +121 -0
  160. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  161. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  162. data/lib/active_record/encryption/cipher.rb +53 -0
  163. data/lib/active_record/encryption/config.rb +70 -0
  164. data/lib/active_record/encryption/configurable.rb +60 -0
  165. data/lib/active_record/encryption/context.rb +42 -0
  166. data/lib/active_record/encryption/contexts.rb +76 -0
  167. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  168. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  169. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  170. data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
  171. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  172. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  173. data/lib/active_record/encryption/encryptor.rb +177 -0
  174. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  175. data/lib/active_record/encryption/errors.rb +15 -0
  176. data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
  177. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  178. data/lib/active_record/encryption/key.rb +28 -0
  179. data/lib/active_record/encryption/key_generator.rb +53 -0
  180. data/lib/active_record/encryption/key_provider.rb +46 -0
  181. data/lib/active_record/encryption/message.rb +33 -0
  182. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  183. data/lib/active_record/encryption/message_serializer.rb +96 -0
  184. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  185. data/lib/active_record/encryption/properties.rb +76 -0
  186. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  187. data/lib/active_record/encryption/scheme.rb +107 -0
  188. data/lib/active_record/encryption.rb +58 -0
  189. data/lib/active_record/enum.rb +424 -0
  190. data/lib/active_record/errors.rb +614 -0
  191. data/lib/active_record/explain.rb +63 -0
  192. data/lib/active_record/explain_registry.rb +37 -0
  193. data/lib/active_record/explain_subscriber.rb +34 -0
  194. data/lib/active_record/fixture_set/file.rb +89 -0
  195. data/lib/active_record/fixture_set/model_metadata.rb +42 -0
  196. data/lib/active_record/fixture_set/render_context.rb +19 -0
  197. data/lib/active_record/fixture_set/table_row.rb +208 -0
  198. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  199. data/lib/active_record/fixtures.rb +850 -0
  200. data/lib/active_record/future_result.rb +182 -0
  201. data/lib/active_record/gem_version.rb +17 -0
  202. data/lib/active_record/inheritance.rb +366 -0
  203. data/lib/active_record/insert_all.rb +328 -0
  204. data/lib/active_record/integration.rb +209 -0
  205. data/lib/active_record/internal_metadata.rb +164 -0
  206. data/lib/active_record/legacy_yaml_adapter.rb +15 -0
  207. data/lib/active_record/locale/en.yml +48 -0
  208. data/lib/active_record/locking/optimistic.rb +228 -0
  209. data/lib/active_record/locking/pessimistic.rb +102 -0
  210. data/lib/active_record/log_subscriber.rb +149 -0
  211. data/lib/active_record/marshalling.rb +56 -0
  212. data/lib/active_record/message_pack.rb +124 -0
  213. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  214. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  215. data/lib/active_record/middleware/database_selector.rb +87 -0
  216. data/lib/active_record/middleware/shard_selector.rb +62 -0
  217. data/lib/active_record/migration/command_recorder.rb +406 -0
  218. data/lib/active_record/migration/compatibility.rb +490 -0
  219. data/lib/active_record/migration/default_strategy.rb +22 -0
  220. data/lib/active_record/migration/execution_strategy.rb +19 -0
  221. data/lib/active_record/migration/join_table.rb +16 -0
  222. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  223. data/lib/active_record/migration.rb +1626 -0
  224. data/lib/active_record/model_schema.rb +635 -0
  225. data/lib/active_record/nested_attributes.rb +633 -0
  226. data/lib/active_record/no_touching.rb +65 -0
  227. data/lib/active_record/normalization.rb +163 -0
  228. data/lib/active_record/persistence.rb +968 -0
  229. data/lib/active_record/promise.rb +84 -0
  230. data/lib/active_record/query_cache.rb +56 -0
  231. data/lib/active_record/query_logs.rb +247 -0
  232. data/lib/active_record/query_logs_formatter.rb +30 -0
  233. data/lib/active_record/querying.rb +122 -0
  234. data/lib/active_record/railtie.rb +440 -0
  235. data/lib/active_record/railties/console_sandbox.rb +5 -0
  236. data/lib/active_record/railties/controller_runtime.rb +65 -0
  237. data/lib/active_record/railties/databases.rake +641 -0
  238. data/lib/active_record/railties/job_runtime.rb +23 -0
  239. data/lib/active_record/readonly_attributes.rb +66 -0
  240. data/lib/active_record/reflection.rb +1287 -0
  241. data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
  242. data/lib/active_record/relation/batches.rb +491 -0
  243. data/lib/active_record/relation/calculations.rb +679 -0
  244. data/lib/active_record/relation/delegation.rb +154 -0
  245. data/lib/active_record/relation/finder_methods.rb +661 -0
  246. data/lib/active_record/relation/from_clause.rb +30 -0
  247. data/lib/active_record/relation/merger.rb +192 -0
  248. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  249. data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
  250. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  251. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
  252. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  253. data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
  254. data/lib/active_record/relation/predicate_builder.rb +181 -0
  255. data/lib/active_record/relation/query_attribute.rb +68 -0
  256. data/lib/active_record/relation/query_methods.rb +2235 -0
  257. data/lib/active_record/relation/record_fetch_warning.rb +52 -0
  258. data/lib/active_record/relation/spawn_methods.rb +78 -0
  259. data/lib/active_record/relation/where_clause.rb +218 -0
  260. data/lib/active_record/relation.rb +1495 -0
  261. data/lib/active_record/result.rb +249 -0
  262. data/lib/active_record/runtime_registry.rb +82 -0
  263. data/lib/active_record/sanitization.rb +254 -0
  264. data/lib/active_record/schema.rb +77 -0
  265. data/lib/active_record/schema_dumper.rb +364 -0
  266. data/lib/active_record/schema_migration.rb +106 -0
  267. data/lib/active_record/scoping/default.rb +205 -0
  268. data/lib/active_record/scoping/named.rb +202 -0
  269. data/lib/active_record/scoping.rb +136 -0
  270. data/lib/active_record/secure_password.rb +60 -0
  271. data/lib/active_record/secure_token.rb +66 -0
  272. data/lib/active_record/serialization.rb +29 -0
  273. data/lib/active_record/signed_id.rb +137 -0
  274. data/lib/active_record/statement_cache.rb +164 -0
  275. data/lib/active_record/store.rb +299 -0
  276. data/lib/active_record/suppressor.rb +59 -0
  277. data/lib/active_record/table_metadata.rb +85 -0
  278. data/lib/active_record/tasks/database_tasks.rb +681 -0
  279. data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
  280. data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
  281. data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
  282. data/lib/active_record/test_databases.rb +24 -0
  283. data/lib/active_record/test_fixtures.rb +321 -0
  284. data/lib/active_record/testing/query_assertions.rb +121 -0
  285. data/lib/active_record/timestamp.rb +177 -0
  286. data/lib/active_record/token_for.rb +123 -0
  287. data/lib/active_record/touch_later.rb +70 -0
  288. data/lib/active_record/transaction.rb +132 -0
  289. data/lib/active_record/transactions.rb +523 -0
  290. data/lib/active_record/translation.rb +22 -0
  291. data/lib/active_record/type/adapter_specific_registry.rb +144 -0
  292. data/lib/active_record/type/date.rb +9 -0
  293. data/lib/active_record/type/date_time.rb +9 -0
  294. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  295. data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
  296. data/lib/active_record/type/internal/timezone.rb +22 -0
  297. data/lib/active_record/type/json.rb +30 -0
  298. data/lib/active_record/type/serialized.rb +76 -0
  299. data/lib/active_record/type/text.rb +11 -0
  300. data/lib/active_record/type/time.rb +35 -0
  301. data/lib/active_record/type/type_map.rb +58 -0
  302. data/lib/active_record/type/unsigned_integer.rb +16 -0
  303. data/lib/active_record/type.rb +83 -0
  304. data/lib/active_record/type_caster/connection.rb +33 -0
  305. data/lib/active_record/type_caster/map.rb +23 -0
  306. data/lib/active_record/type_caster.rb +9 -0
  307. data/lib/active_record/validations/absence.rb +25 -0
  308. data/lib/active_record/validations/associated.rb +65 -0
  309. data/lib/active_record/validations/length.rb +26 -0
  310. data/lib/active_record/validations/numericality.rb +36 -0
  311. data/lib/active_record/validations/presence.rb +45 -0
  312. data/lib/active_record/validations/uniqueness.rb +295 -0
  313. data/lib/active_record/validations.rb +101 -0
  314. data/lib/active_record/version.rb +10 -0
  315. data/lib/active_record.rb +616 -0
  316. data/lib/arel/alias_predication.rb +9 -0
  317. data/lib/arel/attributes/attribute.rb +33 -0
  318. data/lib/arel/collectors/bind.rb +31 -0
  319. data/lib/arel/collectors/composite.rb +46 -0
  320. data/lib/arel/collectors/plain_string.rb +20 -0
  321. data/lib/arel/collectors/sql_string.rb +27 -0
  322. data/lib/arel/collectors/substitute_binds.rb +35 -0
  323. data/lib/arel/crud.rb +48 -0
  324. data/lib/arel/delete_manager.rb +32 -0
  325. data/lib/arel/errors.rb +19 -0
  326. data/lib/arel/expressions.rb +29 -0
  327. data/lib/arel/factory_methods.rb +53 -0
  328. data/lib/arel/filter_predications.rb +9 -0
  329. data/lib/arel/insert_manager.rb +48 -0
  330. data/lib/arel/math.rb +45 -0
  331. data/lib/arel/nodes/ascending.rb +23 -0
  332. data/lib/arel/nodes/binary.rb +125 -0
  333. data/lib/arel/nodes/bind_param.rb +44 -0
  334. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  335. data/lib/arel/nodes/case.rb +55 -0
  336. data/lib/arel/nodes/casted.rb +62 -0
  337. data/lib/arel/nodes/comment.rb +29 -0
  338. data/lib/arel/nodes/count.rb +12 -0
  339. data/lib/arel/nodes/cte.rb +36 -0
  340. data/lib/arel/nodes/delete_statement.rb +44 -0
  341. data/lib/arel/nodes/descending.rb +23 -0
  342. data/lib/arel/nodes/equality.rb +15 -0
  343. data/lib/arel/nodes/extract.rb +24 -0
  344. data/lib/arel/nodes/false.rb +16 -0
  345. data/lib/arel/nodes/filter.rb +10 -0
  346. data/lib/arel/nodes/fragments.rb +35 -0
  347. data/lib/arel/nodes/full_outer_join.rb +8 -0
  348. data/lib/arel/nodes/function.rb +45 -0
  349. data/lib/arel/nodes/grouping.rb +11 -0
  350. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  351. data/lib/arel/nodes/in.rb +15 -0
  352. data/lib/arel/nodes/infix_operation.rb +92 -0
  353. data/lib/arel/nodes/inner_join.rb +8 -0
  354. data/lib/arel/nodes/insert_statement.rb +37 -0
  355. data/lib/arel/nodes/join_source.rb +20 -0
  356. data/lib/arel/nodes/leading_join.rb +8 -0
  357. data/lib/arel/nodes/matches.rb +18 -0
  358. data/lib/arel/nodes/named_function.rb +23 -0
  359. data/lib/arel/nodes/nary.rb +39 -0
  360. data/lib/arel/nodes/node.rb +161 -0
  361. data/lib/arel/nodes/node_expression.rb +13 -0
  362. data/lib/arel/nodes/ordering.rb +27 -0
  363. data/lib/arel/nodes/outer_join.rb +8 -0
  364. data/lib/arel/nodes/over.rb +15 -0
  365. data/lib/arel/nodes/regexp.rb +16 -0
  366. data/lib/arel/nodes/right_outer_join.rb +8 -0
  367. data/lib/arel/nodes/select_core.rb +67 -0
  368. data/lib/arel/nodes/select_statement.rb +41 -0
  369. data/lib/arel/nodes/sql_literal.rb +32 -0
  370. data/lib/arel/nodes/string_join.rb +11 -0
  371. data/lib/arel/nodes/table_alias.rb +35 -0
  372. data/lib/arel/nodes/terminal.rb +16 -0
  373. data/lib/arel/nodes/true.rb +16 -0
  374. data/lib/arel/nodes/unary.rb +44 -0
  375. data/lib/arel/nodes/unary_operation.rb +20 -0
  376. data/lib/arel/nodes/unqualified_column.rb +22 -0
  377. data/lib/arel/nodes/update_statement.rb +46 -0
  378. data/lib/arel/nodes/values_list.rb +9 -0
  379. data/lib/arel/nodes/window.rb +126 -0
  380. data/lib/arel/nodes/with.rb +11 -0
  381. data/lib/arel/nodes.rb +75 -0
  382. data/lib/arel/order_predications.rb +13 -0
  383. data/lib/arel/predications.rb +260 -0
  384. data/lib/arel/select_manager.rb +276 -0
  385. data/lib/arel/table.rb +121 -0
  386. data/lib/arel/tree_manager.rb +65 -0
  387. data/lib/arel/update_manager.rb +49 -0
  388. data/lib/arel/visitors/dot.rb +299 -0
  389. data/lib/arel/visitors/mysql.rb +111 -0
  390. data/lib/arel/visitors/postgresql.rb +99 -0
  391. data/lib/arel/visitors/sqlite.rb +38 -0
  392. data/lib/arel/visitors/to_sql.rb +1033 -0
  393. data/lib/arel/visitors/visitor.rb +45 -0
  394. data/lib/arel/visitors.rb +13 -0
  395. data/lib/arel/window_predications.rb +9 -0
  396. data/lib/arel.rb +73 -0
  397. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  398. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  399. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  400. data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
  401. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
  402. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  403. data/lib/rails/generators/active_record/migration.rb +54 -0
  404. data/lib/rails/generators/active_record/model/USAGE +113 -0
  405. data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
  406. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  407. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  408. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  409. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  410. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  411. data/lib/rails/generators/active_record.rb +19 -0
  412. metadata +505 -0
@@ -0,0 +1,2235 @@
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_support/core_ext/array/wrap"
7
+
8
+ module ActiveRecord
9
+ module QueryMethods
10
+ include ActiveModel::ForbiddenAttributesProtection
11
+
12
+ # +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
13
+ # In this case, +where+ can be chained to return a new relation.
14
+ class WhereChain
15
+ def initialize(scope) # :nodoc:
16
+ @scope = scope
17
+ end
18
+
19
+ # Returns a new relation expressing WHERE + NOT condition according to
20
+ # the conditions in the arguments.
21
+ #
22
+ # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
23
+ # more details on each format.
24
+ #
25
+ # User.where.not("name = 'Jon'")
26
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
27
+ #
28
+ # User.where.not(["name = ?", "Jon"])
29
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
30
+ #
31
+ # User.where.not(name: "Jon")
32
+ # # SELECT * FROM users WHERE name != 'Jon'
33
+ #
34
+ # User.where.not(name: nil)
35
+ # # SELECT * FROM users WHERE name IS NOT NULL
36
+ #
37
+ # User.where.not(name: %w(Ko1 Nobu))
38
+ # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
39
+ #
40
+ # User.where.not(name: "Jon", role: "admin")
41
+ # # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin')
42
+ #
43
+ # If there is a non-nil condition on a nullable column in the hash condition, the records that have
44
+ # nil values on the nullable column won't be returned.
45
+ # User.create!(nullable_country: nil)
46
+ # User.where.not(nullable_country: "UK")
47
+ # # SELECT * FROM users WHERE NOT (nullable_country = 'UK')
48
+ # # => []
49
+ def not(opts, *rest)
50
+ where_clause = @scope.send(:build_where_clause, opts, rest)
51
+
52
+ @scope.where_clause += where_clause.invert
53
+
54
+ @scope
55
+ end
56
+
57
+ # Returns a new relation with joins and where clause to identify
58
+ # associated relations.
59
+ #
60
+ # For example, posts that are associated to a related author:
61
+ #
62
+ # Post.where.associated(:author)
63
+ # # SELECT "posts".* FROM "posts"
64
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
65
+ # # WHERE "authors"."id" IS NOT NULL
66
+ #
67
+ # Additionally, multiple relations can be combined. This will return posts
68
+ # associated to both an author and any comments:
69
+ #
70
+ # Post.where.associated(:author, :comments)
71
+ # # SELECT "posts".* FROM "posts"
72
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
73
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
74
+ # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
75
+ #
76
+ # You can define join type in the scope and +associated+ will not use `JOIN` by default.
77
+ #
78
+ # Post.left_joins(:author).where.associated(:author)
79
+ # # SELECT "posts".* FROM "posts"
80
+ # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
81
+ # # WHERE "authors"."id" IS NOT NULL
82
+ #
83
+ # Post.left_joins(:comments).where.associated(:author)
84
+ # # SELECT "posts".* FROM "posts"
85
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
86
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
87
+ # # WHERE "author"."id" IS NOT NULL
88
+ def associated(*associations)
89
+ associations.each do |association|
90
+ reflection = scope_association_reflection(association)
91
+ unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
92
+ @scope.joins!(association)
93
+ end
94
+
95
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
96
+ if reflection.options[:class_name]
97
+ self.not(association => association_conditions)
98
+ else
99
+ self.not(reflection.table_name => association_conditions)
100
+ end
101
+ end
102
+
103
+ @scope
104
+ end
105
+
106
+ # Returns a new relation with left outer joins and where clause to identify
107
+ # missing relations.
108
+ #
109
+ # For example, posts that are missing a related author:
110
+ #
111
+ # Post.where.missing(:author)
112
+ # # SELECT "posts".* FROM "posts"
113
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
114
+ # # WHERE "authors"."id" IS NULL
115
+ #
116
+ # Additionally, multiple relations can be combined. This will return posts
117
+ # that are missing both an author and any comments:
118
+ #
119
+ # Post.where.missing(:author, :comments)
120
+ # # SELECT "posts".* FROM "posts"
121
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
122
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
123
+ # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
124
+ def missing(*associations)
125
+ associations.each do |association|
126
+ reflection = scope_association_reflection(association)
127
+ @scope.left_outer_joins!(association)
128
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
129
+ if reflection.options[:class_name]
130
+ @scope.where!(association => association_conditions)
131
+ else
132
+ @scope.where!(reflection.table_name => association_conditions)
133
+ end
134
+ end
135
+
136
+ @scope
137
+ end
138
+
139
+ private
140
+ def scope_association_reflection(association)
141
+ model = @scope.model
142
+ reflection = model._reflect_on_association(association)
143
+ unless reflection
144
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.")
145
+ end
146
+ reflection
147
+ end
148
+ end
149
+
150
+ # A wrapper to distinguish CTE joins from other nodes.
151
+ class CTEJoin # :nodoc:
152
+ attr_reader :name
153
+
154
+ def initialize(name)
155
+ @name = name
156
+ end
157
+ end
158
+
159
+ FROZEN_EMPTY_ARRAY = [].freeze
160
+ FROZEN_EMPTY_HASH = {}.freeze
161
+
162
+ Relation::VALUE_METHODS.each do |name|
163
+ method_name, default =
164
+ case name
165
+ when *Relation::MULTI_VALUE_METHODS
166
+ ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
167
+ when *Relation::SINGLE_VALUE_METHODS
168
+ ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
169
+ when *Relation::CLAUSE_METHODS
170
+ ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
171
+ end
172
+
173
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
174
+ def #{method_name} # def includes_values
175
+ @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
176
+ end # end
177
+
178
+ def #{method_name}=(value) # def includes_values=(value)
179
+ assert_modifiable! # assert_modifiable!
180
+ @values[:#{name}] = value # @values[:includes] = value
181
+ end # end
182
+ CODE
183
+ end
184
+
185
+ alias extensions extending_values
186
+
187
+ # Specify associations +args+ to be eager loaded to prevent N + 1 queries.
188
+ # A separate query is performed for each association, unless a join is
189
+ # required by conditions.
190
+ #
191
+ # For example:
192
+ #
193
+ # users = User.includes(:address).limit(5)
194
+ # users.each do |user|
195
+ # user.address.city
196
+ # end
197
+ #
198
+ # # SELECT "users".* FROM "users" LIMIT 5
199
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
200
+ #
201
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
202
+ # are loaded with a single query.
203
+ #
204
+ # Loading the associations in a separate query will often result in a
205
+ # performance improvement over a simple join, as a join can result in many
206
+ # rows that contain redundant data and it performs poorly at scale.
207
+ #
208
+ # You can also specify multiple associations. Each association will result
209
+ # in an additional query:
210
+ #
211
+ # User.includes(:address, :friends).to_a
212
+ # # SELECT "users".* FROM "users"
213
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
214
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
215
+ #
216
+ # Loading nested associations is possible using a Hash:
217
+ #
218
+ # User.includes(:address, friends: [:address, :followers])
219
+ #
220
+ # === Conditions
221
+ #
222
+ # If you want to add string conditions to your included models, you'll have
223
+ # to explicitly reference them. For example:
224
+ #
225
+ # User.includes(:posts).where('posts.name = ?', 'example').to_a
226
+ #
227
+ # Will throw an error, but this will work:
228
+ #
229
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
230
+ # # SELECT "users"."id" AS t0_r0, ... FROM "users"
231
+ # # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
232
+ # # WHERE "posts"."name" = ? [["name", "example"]]
233
+ #
234
+ # As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
235
+ # the posts is no longer performed.
236
+ #
237
+ # Note that #includes works with association names while #references needs
238
+ # the actual table name.
239
+ #
240
+ # If you pass the conditions via a Hash, you don't need to call #references
241
+ # explicitly, as #where references the tables for you. For example, this
242
+ # will work correctly:
243
+ #
244
+ # User.includes(:posts).where(posts: { name: 'example' })
245
+ #
246
+ # NOTE: Conditions affect both sides of an association. For example, the
247
+ # above code will return only users that have a post named "example",
248
+ # <em>and will only include posts named "example"</em>, even when a
249
+ # matching user has other additional posts.
250
+ def includes(*args)
251
+ check_if_method_has_arguments!(__callee__, args)
252
+ spawn.includes!(*args)
253
+ end
254
+
255
+ def includes!(*args) # :nodoc:
256
+ self.includes_values |= args
257
+ self
258
+ end
259
+
260
+ def all # :nodoc:
261
+ spawn
262
+ end
263
+
264
+ # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
265
+ # Performs a single query joining all specified associations. For example:
266
+ #
267
+ # users = User.eager_load(:address).limit(5)
268
+ # users.each do |user|
269
+ # user.address.city
270
+ # end
271
+ #
272
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
273
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
274
+ # # LIMIT 5
275
+ #
276
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
277
+ # are loaded with a single joined query.
278
+ #
279
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
280
+ # similar to #includes:
281
+ #
282
+ # User.eager_load(:address, friends: [:address, :followers])
283
+ # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
284
+ # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
285
+ # # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
286
+ # # ...
287
+ #
288
+ # NOTE: Loading the associations in a join can result in many rows that
289
+ # contain redundant data and it performs poorly at scale.
290
+ def eager_load(*args)
291
+ check_if_method_has_arguments!(__callee__, args)
292
+ spawn.eager_load!(*args)
293
+ end
294
+
295
+ def eager_load!(*args) # :nodoc:
296
+ self.eager_load_values |= args
297
+ self
298
+ end
299
+
300
+ # Specify associations +args+ to be eager loaded using separate queries.
301
+ # A separate query is performed for each association.
302
+ #
303
+ # users = User.preload(:address).limit(5)
304
+ # users.each do |user|
305
+ # user.address.city
306
+ # end
307
+ #
308
+ # # SELECT "users".* FROM "users" LIMIT 5
309
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
310
+ #
311
+ # Instead of loading the 5 addresses with 5 separate queries, all addresses
312
+ # are loaded with a separate query.
313
+ #
314
+ # Loading multiple and nested associations is possible using Hashes and Arrays,
315
+ # similar to #includes:
316
+ #
317
+ # User.preload(:address, friends: [:address, :followers])
318
+ # # SELECT "users".* FROM "users"
319
+ # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
320
+ # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
321
+ # # SELECT ...
322
+ def preload(*args)
323
+ check_if_method_has_arguments!(__callee__, args)
324
+ spawn.preload!(*args)
325
+ end
326
+
327
+ def preload!(*args) # :nodoc:
328
+ self.preload_values |= args
329
+ self
330
+ end
331
+
332
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
333
+ # then the individual association records are collected from the relation. Like so:
334
+ #
335
+ # account.memberships.extract_associated(:user)
336
+ # # => Returns collection of User records
337
+ #
338
+ # This is short-hand for:
339
+ #
340
+ # account.memberships.preload(:user).collect(&:user)
341
+ def extract_associated(association)
342
+ preload(association).collect(&association)
343
+ end
344
+
345
+ # Use to indicate that the given +table_names+ are referenced by an SQL string,
346
+ # and should therefore be +JOIN+ed in any query rather than loaded separately.
347
+ # This method only works in conjunction with #includes.
348
+ # See #includes for more details.
349
+ #
350
+ # User.includes(:posts).where("posts.name = 'foo'")
351
+ # # Doesn't JOIN the posts table, resulting in an error.
352
+ #
353
+ # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
354
+ # # Query now knows the string references posts, so adds a JOIN
355
+ def references(*table_names)
356
+ check_if_method_has_arguments!(__callee__, table_names)
357
+ spawn.references!(*table_names)
358
+ end
359
+
360
+ def references!(*table_names) # :nodoc:
361
+ self.references_values |= table_names
362
+ self
363
+ end
364
+
365
+ # Works in two unique ways.
366
+ #
367
+ # First: takes a block so it can be used just like <tt>Array#select</tt>.
368
+ #
369
+ # Model.all.select { |m| m.field == value }
370
+ #
371
+ # This will build an array of objects from the database for the scope,
372
+ # converting them into an array and iterating through them using
373
+ # <tt>Array#select</tt>.
374
+ #
375
+ # Second: Modifies the SELECT statement for the query so that only certain
376
+ # fields are retrieved:
377
+ #
378
+ # Model.select(:field)
379
+ # # => [#<Model id: nil, field: "value">]
380
+ #
381
+ # Although in the above example it looks as though this method returns an
382
+ # array, it actually returns a relation object and can have other query
383
+ # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
384
+ #
385
+ # The argument to the method can also be an array of fields.
386
+ #
387
+ # Model.select(:field, :other_field, :and_one_more)
388
+ # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
389
+ #
390
+ # The argument also can be a hash of fields and aliases.
391
+ #
392
+ # Model.select(models: { field: :alias, other_field: :other_alias })
393
+ # # => [#<Model id: nil, alias: "value", other_alias: "value">]
394
+ #
395
+ # Model.select(models: [:field, :other_field])
396
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
397
+ #
398
+ # You can also use one or more strings, which will be used unchanged as SELECT fields.
399
+ #
400
+ # Model.select('field AS field_one', 'other_field AS field_two')
401
+ # # => [#<Model id: nil, field_one: "value", field_two: "value">]
402
+ #
403
+ # If an alias was specified, it will be accessible from the resulting objects:
404
+ #
405
+ # Model.select('field AS field_one').first.field_one
406
+ # # => "value"
407
+ #
408
+ # Accessing attributes of an object that do not have fields retrieved by a select
409
+ # except +id+ will throw ActiveModel::MissingAttributeError:
410
+ #
411
+ # Model.select(:field).first.other_field
412
+ # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
413
+ def select(*fields)
414
+ if block_given?
415
+ if fields.any?
416
+ raise ArgumentError, "`select' with block doesn't take arguments."
417
+ end
418
+
419
+ return super()
420
+ end
421
+
422
+ check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
423
+
424
+ fields = process_select_args(fields)
425
+ spawn._select!(*fields)
426
+ end
427
+
428
+ def _select!(*fields) # :nodoc:
429
+ self.select_values |= fields
430
+ self
431
+ end
432
+
433
+ # Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
434
+ #
435
+ # Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
436
+ # use CTE's with MySQL 5.7.
437
+ #
438
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
439
+ # # => ActiveRecord::Relation
440
+ # # WITH posts_with_tags AS (
441
+ # # SELECT * FROM posts WHERE (tags_count > 0)
442
+ # # )
443
+ # # SELECT * FROM posts
444
+ #
445
+ # You can also pass an array of sub-queries to be joined in a +UNION ALL+.
446
+ #
447
+ # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
448
+ # # => ActiveRecord::Relation
449
+ # # WITH posts_with_tags_or_comments AS (
450
+ # # (SELECT * FROM posts WHERE (tags_count > 0))
451
+ # # UNION ALL
452
+ # # (SELECT * FROM posts WHERE (comments_count > 0))
453
+ # # )
454
+ # # SELECT * FROM posts
455
+ #
456
+ # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
457
+ #
458
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
459
+ # # => ActiveRecord::Relation
460
+ # # WITH posts_with_tags AS (
461
+ # # SELECT * FROM posts WHERE (tags_count > 0)
462
+ # # )
463
+ # # SELECT * FROM posts_with_tags AS posts
464
+ #
465
+ # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
466
+ # # => ActiveRecord::Relation
467
+ # # WITH posts_with_tags AS (
468
+ # # SELECT * FROM posts WHERE (tags_count > 0)
469
+ # # )
470
+ # # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
471
+ #
472
+ # It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
473
+ # and you have verified it is safe for the database, you can pass it as SQL literal
474
+ # using +Arel+.
475
+ #
476
+ # Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
477
+ #
478
+ # Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
479
+ # be used with unsafe values that include unsanitized input.
480
+ #
481
+ # To add multiple CTEs just pass multiple key-value pairs
482
+ #
483
+ # Post.with(
484
+ # posts_with_comments: Post.where("comments_count > ?", 0),
485
+ # posts_with_tags: Post.where("tags_count > ?", 0)
486
+ # )
487
+ #
488
+ # or chain multiple +.with+ calls
489
+ #
490
+ # Post
491
+ # .with(posts_with_comments: Post.where("comments_count > ?", 0))
492
+ # .with(posts_with_tags: Post.where("tags_count > ?", 0))
493
+ def with(*args)
494
+ raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
495
+ check_if_method_has_arguments!(__callee__, args)
496
+ spawn.with!(*args)
497
+ end
498
+
499
+ # Like #with, but modifies relation in place.
500
+ def with!(*args) # :nodoc:
501
+ self.with_values += args
502
+ self
503
+ end
504
+
505
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
506
+ #
507
+ # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
508
+ # # => ActiveRecord::Relation
509
+ # # WITH post_and_replies AS (
510
+ # # (SELECT * FROM posts WHERE id = 42)
511
+ # # UNION ALL
512
+ # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
513
+ # # )
514
+ # # SELECT * FROM posts
515
+ #
516
+ # See `#with` for more information.
517
+ def with_recursive(*args)
518
+ check_if_method_has_arguments!(__callee__, args)
519
+ spawn.with_recursive!(*args)
520
+ end
521
+
522
+ # Like #with_recursive but modifies the relation in place.
523
+ def with_recursive!(*args) # :nodoc:
524
+ self.with_values += args
525
+ @with_is_recursive = true
526
+ self
527
+ end
528
+
529
+ # Allows you to change a previously set select statement.
530
+ #
531
+ # Post.select(:title, :body)
532
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
533
+ #
534
+ # Post.select(:title, :body).reselect(:created_at)
535
+ # # SELECT `posts`.`created_at` FROM `posts`
536
+ #
537
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
538
+ # Note that we're unscoping the entire select statement.
539
+ def reselect(*args)
540
+ check_if_method_has_arguments!(__callee__, args)
541
+ args = process_select_args(args)
542
+ spawn.reselect!(*args)
543
+ end
544
+
545
+ # Same as #reselect but operates on relation in-place instead of copying.
546
+ def reselect!(*args) # :nodoc:
547
+ self.select_values = args
548
+ self
549
+ end
550
+
551
+ # Allows to specify a group attribute:
552
+ #
553
+ # User.group(:name)
554
+ # # SELECT "users".* FROM "users" GROUP BY name
555
+ #
556
+ # Returns an array with distinct records based on the +group+ attribute:
557
+ #
558
+ # User.select([:id, :name])
559
+ # # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
560
+ #
561
+ # User.group(:name)
562
+ # # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
563
+ #
564
+ # User.group('name AS grouped_name, age')
565
+ # # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
566
+ #
567
+ # Passing in an array of attributes to group by is also supported.
568
+ #
569
+ # User.select([:id, :first_name]).group(:id, :first_name).first(3)
570
+ # # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
571
+ def group(*args)
572
+ check_if_method_has_arguments!(__callee__, args)
573
+ spawn.group!(*args)
574
+ end
575
+
576
+ def group!(*args) # :nodoc:
577
+ self.group_values += args
578
+ self
579
+ end
580
+
581
+ # Allows you to change a previously set group statement.
582
+ #
583
+ # Post.group(:title, :body)
584
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
585
+ #
586
+ # Post.group(:title, :body).regroup(:title)
587
+ # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
588
+ #
589
+ # This is short-hand for <tt>unscope(:group).group(fields)</tt>.
590
+ # Note that we're unscoping the entire group statement.
591
+ def regroup(*args)
592
+ check_if_method_has_arguments!(__callee__, args)
593
+ spawn.regroup!(*args)
594
+ end
595
+
596
+ # Same as #regroup but operates on relation in-place instead of copying.
597
+ def regroup!(*args) # :nodoc:
598
+ self.group_values = args
599
+ self
600
+ end
601
+
602
+ # Applies an <code>ORDER BY</code> clause to a query.
603
+ #
604
+ # #order accepts arguments in one of several formats.
605
+ #
606
+ # === symbols
607
+ #
608
+ # The symbol represents the name of the column you want to order the results by.
609
+ #
610
+ # User.order(:name)
611
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
612
+ #
613
+ # By default, the order is ascending. If you want descending order, you can
614
+ # map the column name symbol to +:desc+.
615
+ #
616
+ # User.order(email: :desc)
617
+ # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
618
+ #
619
+ # Multiple columns can be passed this way, and they will be applied in the order specified.
620
+ #
621
+ # User.order(:name, email: :desc)
622
+ # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
623
+ #
624
+ # === strings
625
+ #
626
+ # Strings are passed directly to the database, allowing you to specify
627
+ # simple SQL expressions.
628
+ #
629
+ # This could be a source of SQL injection, so only strings composed of plain
630
+ # column names and simple <code>function(column_name)</code> expressions
631
+ # with optional +ASC+/+DESC+ modifiers are allowed.
632
+ #
633
+ # User.order('name')
634
+ # # SELECT "users".* FROM "users" ORDER BY name
635
+ #
636
+ # User.order('name DESC')
637
+ # # SELECT "users".* FROM "users" ORDER BY name DESC
638
+ #
639
+ # User.order('name DESC, email')
640
+ # # SELECT "users".* FROM "users" ORDER BY name DESC, email
641
+ #
642
+ # === Arel
643
+ #
644
+ # If you need to pass in complicated expressions that you have verified
645
+ # are safe for the database, you can use Arel.
646
+ #
647
+ # User.order(Arel.sql('end_date - start_date'))
648
+ # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
649
+ #
650
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
651
+ #
652
+ # User.order(Arel.sql("payload->>'kind'"))
653
+ # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
654
+ def order(*args)
655
+ check_if_method_has_arguments!(__callee__, args) do
656
+ sanitize_order_arguments(args)
657
+ end
658
+ spawn.order!(*args)
659
+ end
660
+
661
+ # Same as #order but operates on relation in-place instead of copying.
662
+ def order!(*args) # :nodoc:
663
+ preprocess_order_args(args) unless args.empty?
664
+ self.order_values |= args
665
+ self
666
+ end
667
+
668
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
669
+ # ordered and filtered by a specific set of +values+.
670
+ #
671
+ # User.in_order_of(:id, [1, 5, 3])
672
+ # # SELECT "users".* FROM "users"
673
+ # # WHERE "users"."id" IN (1, 5, 3)
674
+ # # ORDER BY CASE
675
+ # # WHEN "users"."id" = 1 THEN 1
676
+ # # WHEN "users"."id" = 5 THEN 2
677
+ # # WHEN "users"."id" = 3 THEN 3
678
+ # # END ASC
679
+ #
680
+ # +column+ can point to an enum column; the actual query generated may be different depending
681
+ # on the database adapter and the column definition.
682
+ #
683
+ # class Conversation < ActiveRecord::Base
684
+ # enum :status, [ :active, :archived ]
685
+ # end
686
+ #
687
+ # Conversation.in_order_of(:status, [:archived, :active])
688
+ # # SELECT "conversations".* FROM "conversations"
689
+ # # WHERE "conversations"."status" IN (1, 0)
690
+ # # ORDER BY CASE
691
+ # # WHEN "conversations"."status" = 1 THEN 1
692
+ # # WHEN "conversations"."status" = 0 THEN 2
693
+ # # END ASC
694
+ #
695
+ # +values+ can also include +nil+.
696
+ #
697
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
698
+ # # SELECT "conversations".* FROM "conversations"
699
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
700
+ # # ORDER BY CASE
701
+ # # WHEN "conversations"."status" IS NULL THEN 1
702
+ # # WHEN "conversations"."status" = 1 THEN 2
703
+ # # WHEN "conversations"."status" = 0 THEN 3
704
+ # # END ASC
705
+ #
706
+ # +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+.
707
+ #
708
+ # Conversation.in_order_of(:status, [:archived, :active], filter: false)
709
+ # # SELECT "conversations".* FROM "conversations"
710
+ # # ORDER BY CASE
711
+ # # WHEN "conversations"."status" = 1 THEN 1
712
+ # # WHEN "conversations"."status" = 0 THEN 2
713
+ # # ELSE 3
714
+ # # END ASC
715
+ def in_order_of(column, values, filter: true)
716
+ model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
717
+ return spawn.none! if values.empty?
718
+
719
+ references = column_references([column])
720
+ self.references_values |= references unless references.empty?
721
+
722
+ values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
723
+ arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
724
+
725
+ scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
726
+
727
+ if filter
728
+ where_clause =
729
+ if values.include?(nil)
730
+ arel_column.in(values.compact).or(arel_column.eq(nil))
731
+ else
732
+ arel_column.in(values)
733
+ end
734
+
735
+ scope = scope.where!(where_clause)
736
+ end
737
+
738
+ scope
739
+ end
740
+
741
+ # Replaces any existing order defined on the relation with the specified order.
742
+ #
743
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
744
+ #
745
+ # Subsequent calls to order on the same relation will be appended. For example:
746
+ #
747
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
748
+ #
749
+ # generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
750
+ def reorder(*args)
751
+ check_if_method_has_arguments!(__callee__, args) do
752
+ sanitize_order_arguments(args)
753
+ end
754
+ spawn.reorder!(*args)
755
+ end
756
+
757
+ # Same as #reorder but operates on relation in-place instead of copying.
758
+ def reorder!(*args) # :nodoc:
759
+ preprocess_order_args(args)
760
+ args.uniq!
761
+ self.reordering_value = true
762
+ self.order_values = args
763
+ self
764
+ end
765
+
766
+ VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
767
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
768
+ :includes, :eager_load, :preload, :from, :readonly,
769
+ :having, :optimizer_hints, :with])
770
+
771
+ # Removes an unwanted relation that is already defined on a chain of relations.
772
+ # This is useful when passing around chains of relations and would like to
773
+ # modify the relations without reconstructing the entire chain.
774
+ #
775
+ # User.order('email DESC').unscope(:order) == User.all
776
+ #
777
+ # The method arguments are symbols which correspond to the names of the methods
778
+ # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
779
+ # The method can also be called with multiple arguments. For example:
780
+ #
781
+ # User.order('email DESC').select('id').where(name: "John")
782
+ # .unscope(:order, :select, :where) == User.all
783
+ #
784
+ # One can additionally pass a hash as an argument to unscope specific +:where+ values.
785
+ # This is done by passing a hash with a single key-value pair. The key should be
786
+ # +:where+ and the value should be the where value to unscope. For example:
787
+ #
788
+ # User.where(name: "John", active: true).unscope(where: :name)
789
+ # == User.where(active: true)
790
+ #
791
+ # This method is similar to #except, but unlike
792
+ # #except, it persists across merges:
793
+ #
794
+ # User.order('email').merge(User.except(:order))
795
+ # == User.order('email')
796
+ #
797
+ # User.order('email').merge(User.unscope(:order))
798
+ # == User.all
799
+ #
800
+ # This means it can be used in association definitions:
801
+ #
802
+ # has_many :comments, -> { unscope(where: :trashed) }
803
+ #
804
+ def unscope(*args)
805
+ check_if_method_has_arguments!(__callee__, args)
806
+ spawn.unscope!(*args)
807
+ end
808
+
809
+ def unscope!(*args) # :nodoc:
810
+ self.unscope_values += args
811
+
812
+ args.each do |scope|
813
+ case scope
814
+ when Symbol
815
+ scope = :left_outer_joins if scope == :left_joins
816
+ if !VALID_UNSCOPING_VALUES.include?(scope)
817
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
818
+ end
819
+ assert_modifiable!
820
+ @values.delete(scope)
821
+ when Hash
822
+ scope.each do |key, target_value|
823
+ if key != :where
824
+ raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
825
+ end
826
+
827
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
828
+ self.where_clause = where_clause.except(*target_values)
829
+ end
830
+ else
831
+ raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
832
+ end
833
+ end
834
+
835
+ self
836
+ end
837
+
838
+ # Performs JOINs on +args+. The given symbol(s) should match the name of
839
+ # the association(s).
840
+ #
841
+ # User.joins(:posts)
842
+ # # SELECT "users".*
843
+ # # FROM "users"
844
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
845
+ #
846
+ # Multiple joins:
847
+ #
848
+ # User.joins(:posts, :account)
849
+ # # SELECT "users".*
850
+ # # FROM "users"
851
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
852
+ # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
853
+ #
854
+ # Nested joins:
855
+ #
856
+ # User.joins(posts: [:comments])
857
+ # # SELECT "users".*
858
+ # # FROM "users"
859
+ # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
860
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
861
+ #
862
+ # You can use strings in order to customize your joins:
863
+ #
864
+ # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
865
+ # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
866
+ def joins(*args)
867
+ check_if_method_has_arguments!(__callee__, args)
868
+ spawn.joins!(*args)
869
+ end
870
+
871
+ def joins!(*args) # :nodoc:
872
+ self.joins_values |= args
873
+ self
874
+ end
875
+
876
+ # Performs LEFT OUTER JOINs on +args+:
877
+ #
878
+ # User.left_outer_joins(:posts)
879
+ # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
880
+ #
881
+ def left_outer_joins(*args)
882
+ check_if_method_has_arguments!(__callee__, args)
883
+ spawn.left_outer_joins!(*args)
884
+ end
885
+ alias :left_joins :left_outer_joins
886
+
887
+ def left_outer_joins!(*args) # :nodoc:
888
+ self.left_outer_joins_values |= args
889
+ self
890
+ end
891
+
892
+ # Returns a new relation, which is the result of filtering the current relation
893
+ # according to the conditions in the arguments.
894
+ #
895
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
896
+ # SQL is given as an illustration; the actual query generated may be different depending
897
+ # on the database adapter.
898
+ #
899
+ # === \String
900
+ #
901
+ # A single string, without additional arguments, is passed to the query
902
+ # constructor as an SQL fragment, and used in the where clause of the query.
903
+ #
904
+ # Client.where("orders_count = '2'")
905
+ # # SELECT * from clients where orders_count = '2';
906
+ #
907
+ # Note that building your own string from user input may expose your application
908
+ # to injection attacks if not done properly. As an alternative, it is recommended
909
+ # to use one of the following methods.
910
+ #
911
+ # === \Array
912
+ #
913
+ # If an array is passed, then the first element of the array is treated as a template, and
914
+ # the remaining elements are inserted into the template to generate the condition.
915
+ # Active Record takes care of building the query to avoid injection attacks, and will
916
+ # convert from the ruby type to the database type where needed. Elements are inserted
917
+ # into the string in the order in which they appear.
918
+ #
919
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
920
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
921
+ #
922
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
923
+ # second element of the array. The names in the template are replaced with the corresponding
924
+ # values from the hash.
925
+ #
926
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
927
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
928
+ #
929
+ # This can make for more readable code in complex queries.
930
+ #
931
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
932
+ # than the previous methods; you are responsible for ensuring that the values in the template
933
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
934
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
935
+ # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
936
+ #
937
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
938
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
939
+ #
940
+ # If #where is called with multiple arguments, these are treated as if they were passed as
941
+ # the elements of a single array.
942
+ #
943
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
944
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
945
+ #
946
+ # When using strings to specify conditions, you can use any operator available from
947
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
948
+ # dependencies on the underlying database. If your code is intended for general consumption,
949
+ # test with multiple database backends.
950
+ #
951
+ # === \Hash
952
+ #
953
+ # #where will also accept a hash condition, in which the keys are fields and the values
954
+ # are values to be searched for.
955
+ #
956
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
957
+ #
958
+ # User.where(name: "Joe", email: "joe@example.com")
959
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
960
+ #
961
+ # User.where(name: ["Alice", "Bob"])
962
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
963
+ #
964
+ # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
965
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
966
+ #
967
+ # In the case of a belongs_to relationship, an association key can be used
968
+ # to specify the model if an ActiveRecord object is used as the value.
969
+ #
970
+ # author = Author.find(1)
971
+ #
972
+ # # The following queries will be equivalent:
973
+ # Post.where(author: author)
974
+ # Post.where(author_id: author)
975
+ #
976
+ # This also works with polymorphic belongs_to relationships:
977
+ #
978
+ # treasure = Treasure.create(name: 'gold coins')
979
+ # treasure.price_estimates << PriceEstimate.create(price: 125)
980
+ #
981
+ # # The following queries will be equivalent:
982
+ # PriceEstimate.where(estimate_of: treasure)
983
+ # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
984
+ #
985
+ # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
986
+ # an array of columns with an array of tuples as values.
987
+ #
988
+ # Article.where([:author_id, :id] => [[15, 1], [15, 2]])
989
+ # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
990
+ #
991
+ # === Joins
992
+ #
993
+ # If the relation is the result of a join, you may create a condition which uses any of the
994
+ # tables in the join. For string and array conditions, use the table name in the condition.
995
+ #
996
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
997
+ #
998
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
999
+ #
1000
+ # User.joins(:posts).where("posts.published" => true)
1001
+ # User.joins(:posts).where(posts: { published: true })
1002
+ #
1003
+ # === No Argument
1004
+ #
1005
+ # If no argument is passed, #where returns a new instance of WhereChain, that
1006
+ # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
1007
+ #
1008
+ # Chaining with WhereChain#not:
1009
+ #
1010
+ # User.where.not(name: "Jon")
1011
+ # # SELECT * FROM users WHERE name != 'Jon'
1012
+ #
1013
+ # Chaining with WhereChain#associated:
1014
+ #
1015
+ # Post.where.associated(:author)
1016
+ # # SELECT "posts".* FROM "posts"
1017
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
1018
+ # # WHERE "authors"."id" IS NOT NULL
1019
+ #
1020
+ # Chaining with WhereChain#missing:
1021
+ #
1022
+ # Post.where.missing(:author)
1023
+ # # SELECT "posts".* FROM "posts"
1024
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
1025
+ # # WHERE "authors"."id" IS NULL
1026
+ #
1027
+ # === Blank Condition
1028
+ #
1029
+ # If the condition is any blank-ish object, then #where is a no-op and returns
1030
+ # the current relation.
1031
+ def where(*args)
1032
+ if args.empty?
1033
+ WhereChain.new(spawn)
1034
+ elsif args.length == 1 && args.first.blank?
1035
+ self
1036
+ else
1037
+ spawn.where!(*args)
1038
+ end
1039
+ end
1040
+
1041
+ def where!(opts, *rest) # :nodoc:
1042
+ self.where_clause += build_where_clause(opts, rest)
1043
+ self
1044
+ end
1045
+
1046
+ # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
1047
+ #
1048
+ # Post.where(trashed: true).where(trashed: false)
1049
+ # # WHERE `trashed` = 1 AND `trashed` = 0
1050
+ #
1051
+ # Post.where(trashed: true).rewhere(trashed: false)
1052
+ # # WHERE `trashed` = 0
1053
+ #
1054
+ # Post.where(active: true).where(trashed: true).rewhere(trashed: false)
1055
+ # # WHERE `active` = 1 AND `trashed` = 0
1056
+ #
1057
+ # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
1058
+ # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
1059
+ def rewhere(conditions)
1060
+ return unscope(:where) if conditions.nil?
1061
+
1062
+ scope = spawn
1063
+ where_clause = scope.build_where_clause(conditions)
1064
+
1065
+ scope.unscope!(where: where_clause.extract_attributes)
1066
+ scope.where_clause += where_clause
1067
+ scope
1068
+ end
1069
+
1070
+ # Allows you to invert an entire where clause instead of manually applying conditions.
1071
+ #
1072
+ # class User
1073
+ # scope :active, -> { where(accepted: true, locked: false) }
1074
+ # end
1075
+ #
1076
+ # User.where(accepted: true)
1077
+ # # WHERE `accepted` = 1
1078
+ #
1079
+ # User.where(accepted: true).invert_where
1080
+ # # WHERE `accepted` != 1
1081
+ #
1082
+ # User.active
1083
+ # # WHERE `accepted` = 1 AND `locked` = 0
1084
+ #
1085
+ # User.active.invert_where
1086
+ # # WHERE NOT (`accepted` = 1 AND `locked` = 0)
1087
+ #
1088
+ # Be careful because this inverts all conditions before +invert_where+ call.
1089
+ #
1090
+ # class User
1091
+ # scope :active, -> { where(accepted: true, locked: false) }
1092
+ # scope :inactive, -> { active.invert_where } # Do not attempt it
1093
+ # end
1094
+ #
1095
+ # # It also inverts `where(role: 'admin')` unexpectedly.
1096
+ # User.where(role: 'admin').inactive
1097
+ # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
1098
+ #
1099
+ def invert_where
1100
+ spawn.invert_where!
1101
+ end
1102
+
1103
+ def invert_where! # :nodoc:
1104
+ self.where_clause = where_clause.invert
1105
+ self
1106
+ end
1107
+
1108
+ # Checks whether the given relation is structurally compatible with this relation, to determine
1109
+ # if it's possible to use the #and and #or methods without raising an error. Structurally
1110
+ # compatible is defined as: they must be scoping the same model, and they must differ only by
1111
+ # #where (if no #group has been defined) or #having (if a #group is present).
1112
+ #
1113
+ # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
1114
+ # # => true
1115
+ #
1116
+ # Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
1117
+ # # => false
1118
+ #
1119
+ def structurally_compatible?(other)
1120
+ structurally_incompatible_values_for(other).empty?
1121
+ end
1122
+
1123
+ # Returns a new relation, which is the logical intersection of this relation and the one passed
1124
+ # as an argument.
1125
+ #
1126
+ # The two relations must be structurally compatible: they must be scoping the same model, and
1127
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
1128
+ # present).
1129
+ #
1130
+ # Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
1131
+ # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
1132
+ #
1133
+ def and(other)
1134
+ if other.is_a?(Relation)
1135
+ spawn.and!(other)
1136
+ else
1137
+ raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
1138
+ end
1139
+ end
1140
+
1141
+ def and!(other) # :nodoc:
1142
+ incompatible_values = structurally_incompatible_values_for(other)
1143
+
1144
+ unless incompatible_values.empty?
1145
+ raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
1146
+ end
1147
+
1148
+ self.where_clause |= other.where_clause
1149
+ self.having_clause |= other.having_clause
1150
+ self.references_values |= other.references_values
1151
+
1152
+ self
1153
+ end
1154
+
1155
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
1156
+ # argument.
1157
+ #
1158
+ # The two relations must be structurally compatible: they must be scoping the same model, and
1159
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
1160
+ # present).
1161
+ #
1162
+ # Post.where("id = 1").or(Post.where("author_id = 3"))
1163
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
1164
+ #
1165
+ def or(other)
1166
+ if other.is_a?(Relation)
1167
+ if @none
1168
+ other.spawn
1169
+ else
1170
+ spawn.or!(other)
1171
+ end
1172
+ else
1173
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
1174
+ end
1175
+ end
1176
+
1177
+ def or!(other) # :nodoc:
1178
+ incompatible_values = structurally_incompatible_values_for(other)
1179
+
1180
+ unless incompatible_values.empty?
1181
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
1182
+ end
1183
+
1184
+ self.where_clause = where_clause.or(other.where_clause)
1185
+ self.having_clause = having_clause.or(other.having_clause)
1186
+ self.references_values |= other.references_values
1187
+
1188
+ self
1189
+ end
1190
+
1191
+ # Allows to specify a HAVING clause. Note that you can't use HAVING
1192
+ # without also specifying a GROUP clause.
1193
+ #
1194
+ # Order.having('SUM(price) > 30').group('user_id')
1195
+ def having(opts, *rest)
1196
+ opts.blank? ? self : spawn.having!(opts, *rest)
1197
+ end
1198
+
1199
+ def having!(opts, *rest) # :nodoc:
1200
+ self.having_clause += build_having_clause(opts, rest)
1201
+ self
1202
+ end
1203
+
1204
+ # Specifies a limit for the number of records to retrieve.
1205
+ #
1206
+ # User.limit(10) # generated SQL has 'LIMIT 10'
1207
+ #
1208
+ # User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
1209
+ def limit(value)
1210
+ spawn.limit!(value)
1211
+ end
1212
+
1213
+ def limit!(value) # :nodoc:
1214
+ self.limit_value = value
1215
+ self
1216
+ end
1217
+
1218
+ # Specifies the number of rows to skip before returning rows.
1219
+ #
1220
+ # User.offset(10) # generated SQL has "OFFSET 10"
1221
+ #
1222
+ # Should be used with order.
1223
+ #
1224
+ # User.offset(10).order("name ASC")
1225
+ def offset(value)
1226
+ spawn.offset!(value)
1227
+ end
1228
+
1229
+ def offset!(value) # :nodoc:
1230
+ self.offset_value = value
1231
+ self
1232
+ end
1233
+
1234
+ # Specifies locking settings (default to +true+). For more information
1235
+ # on locking, please see ActiveRecord::Locking.
1236
+ def lock(locks = true)
1237
+ spawn.lock!(locks)
1238
+ end
1239
+
1240
+ def lock!(locks = true) # :nodoc:
1241
+ case locks
1242
+ when String, TrueClass, NilClass
1243
+ self.lock_value = locks || true
1244
+ else
1245
+ self.lock_value = false
1246
+ end
1247
+
1248
+ self
1249
+ end
1250
+
1251
+ # Returns a chainable relation with zero records.
1252
+ #
1253
+ # The returned relation implements the Null Object pattern. It is an
1254
+ # object with defined null behavior and always returns an empty array of
1255
+ # records without querying the database.
1256
+ #
1257
+ # Any subsequent condition chained to the returned relation will continue
1258
+ # generating an empty relation and will not fire any query to the database.
1259
+ #
1260
+ # Used in cases where a method or scope could return zero records but the
1261
+ # result needs to be chainable.
1262
+ #
1263
+ # For example:
1264
+ #
1265
+ # @posts = current_user.visible_posts.where(name: params[:name])
1266
+ # # the visible_posts method is expected to return a chainable Relation
1267
+ #
1268
+ # def visible_posts
1269
+ # case role
1270
+ # when 'Country Manager'
1271
+ # Post.where(country: country)
1272
+ # when 'Reviewer'
1273
+ # Post.published
1274
+ # when 'Bad User'
1275
+ # Post.none # It can't be chained if [] is returned.
1276
+ # end
1277
+ # end
1278
+ #
1279
+ def none
1280
+ spawn.none!
1281
+ end
1282
+
1283
+ def none! # :nodoc:
1284
+ unless @none
1285
+ where!("1=0")
1286
+ @none = true
1287
+ end
1288
+ self
1289
+ end
1290
+
1291
+ def null_relation? # :nodoc:
1292
+ @none
1293
+ end
1294
+
1295
+ # Mark a relation as readonly. Attempting to update a record will result in
1296
+ # an error.
1297
+ #
1298
+ # users = User.readonly
1299
+ # users.first.save
1300
+ # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1301
+ #
1302
+ # To make a readonly relation writable, pass +false+.
1303
+ #
1304
+ # users.readonly(false)
1305
+ # users.first.save
1306
+ # => true
1307
+ def readonly(value = true)
1308
+ spawn.readonly!(value)
1309
+ end
1310
+
1311
+ def readonly!(value = true) # :nodoc:
1312
+ self.readonly_value = value
1313
+ self
1314
+ end
1315
+
1316
+ # Sets the returned relation to strict_loading mode. This will raise an error
1317
+ # if the record tries to lazily load an association.
1318
+ #
1319
+ # user = User.strict_loading.first
1320
+ # user.comments.to_a
1321
+ # => ActiveRecord::StrictLoadingViolationError
1322
+ def strict_loading(value = true)
1323
+ spawn.strict_loading!(value)
1324
+ end
1325
+
1326
+ def strict_loading!(value = true) # :nodoc:
1327
+ self.strict_loading_value = value
1328
+ self
1329
+ end
1330
+
1331
+ # Sets attributes to be used when creating new records from a
1332
+ # relation object.
1333
+ #
1334
+ # users = User.where(name: 'Oscar')
1335
+ # users.new.name # => 'Oscar'
1336
+ #
1337
+ # users = users.create_with(name: 'DHH')
1338
+ # users.new.name # => 'DHH'
1339
+ #
1340
+ # You can pass +nil+ to #create_with to reset attributes:
1341
+ #
1342
+ # users = users.create_with(nil)
1343
+ # users.new.name # => 'Oscar'
1344
+ def create_with(value)
1345
+ spawn.create_with!(value)
1346
+ end
1347
+
1348
+ def create_with!(value) # :nodoc:
1349
+ if value
1350
+ value = sanitize_forbidden_attributes(value)
1351
+ self.create_with_value = create_with_value.merge(value)
1352
+ else
1353
+ self.create_with_value = FROZEN_EMPTY_HASH
1354
+ end
1355
+
1356
+ self
1357
+ end
1358
+
1359
+ # Specifies the table from which the records will be fetched. For example:
1360
+ #
1361
+ # Topic.select('title').from('posts')
1362
+ # # SELECT title FROM posts
1363
+ #
1364
+ # Can accept other relation objects. For example:
1365
+ #
1366
+ # Topic.select('title').from(Topic.approved)
1367
+ # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
1368
+ #
1369
+ # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used:
1370
+ #
1371
+ # Topic.select('a.title').from(Topic.approved, :a)
1372
+ # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
1373
+ #
1374
+ # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used:
1375
+ #
1376
+ # Topic.select('title').from(Topic.approved).from(Topic.inactive)
1377
+ # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
1378
+ #
1379
+ # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list:
1380
+ #
1381
+ # color = "red"
1382
+ # Color
1383
+ # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
1384
+ # .where("colorvalue->>'color' = ?", color)
1385
+ # .select("c.*").to_a
1386
+ # # SELECT c.*
1387
+ # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
1388
+ # # WHERE (colorvalue->>'color' = 'red')
1389
+ def from(value, subquery_name = nil)
1390
+ spawn.from!(value, subquery_name)
1391
+ end
1392
+
1393
+ def from!(value, subquery_name = nil) # :nodoc:
1394
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
1395
+ self
1396
+ end
1397
+
1398
+ # Specifies whether the records should be unique or not. For example:
1399
+ #
1400
+ # User.select(:name)
1401
+ # # Might return two records with the same name
1402
+ #
1403
+ # User.select(:name).distinct
1404
+ # # Returns 1 record per distinct name
1405
+ #
1406
+ # User.select(:name).distinct.distinct(false)
1407
+ # # You can also remove the uniqueness
1408
+ def distinct(value = true)
1409
+ spawn.distinct!(value)
1410
+ end
1411
+
1412
+ # Like #distinct, but modifies relation in place.
1413
+ def distinct!(value = true) # :nodoc:
1414
+ self.distinct_value = value
1415
+ self
1416
+ end
1417
+
1418
+ # Used to extend a scope with additional methods, either through
1419
+ # a module or through a block provided.
1420
+ #
1421
+ # The object returned is a relation, which can be further extended.
1422
+ #
1423
+ # === Using a \Module
1424
+ #
1425
+ # module Pagination
1426
+ # def page(number)
1427
+ # # pagination code goes here
1428
+ # end
1429
+ # end
1430
+ #
1431
+ # scope = Model.all.extending(Pagination)
1432
+ # scope.page(params[:page])
1433
+ #
1434
+ # You can also pass a list of modules:
1435
+ #
1436
+ # scope = Model.all.extending(Pagination, SomethingElse)
1437
+ #
1438
+ # === Using a Block
1439
+ #
1440
+ # scope = Model.all.extending do
1441
+ # def page(number)
1442
+ # # pagination code goes here
1443
+ # end
1444
+ # end
1445
+ # scope.page(params[:page])
1446
+ #
1447
+ # You can also use a block and a module list:
1448
+ #
1449
+ # scope = Model.all.extending(Pagination) do
1450
+ # def per_page(number)
1451
+ # # pagination code goes here
1452
+ # end
1453
+ # end
1454
+ def extending(*modules, &block)
1455
+ if modules.any? || block
1456
+ spawn.extending!(*modules, &block)
1457
+ else
1458
+ self
1459
+ end
1460
+ end
1461
+
1462
+ def extending!(*modules, &block) # :nodoc:
1463
+ modules << Module.new(&block) if block
1464
+ modules.flatten!
1465
+
1466
+ self.extending_values += modules
1467
+ extend(*extending_values) if extending_values.any?
1468
+
1469
+ self
1470
+ end
1471
+
1472
+ # Specify optimizer hints to be used in the SELECT statement.
1473
+ #
1474
+ # Example (for MySQL):
1475
+ #
1476
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
1477
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
1478
+ #
1479
+ # Example (for PostgreSQL with pg_hint_plan):
1480
+ #
1481
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
1482
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
1483
+ def optimizer_hints(*args)
1484
+ check_if_method_has_arguments!(__callee__, args)
1485
+ spawn.optimizer_hints!(*args)
1486
+ end
1487
+
1488
+ def optimizer_hints!(*args) # :nodoc:
1489
+ self.optimizer_hints_values |= args
1490
+ self
1491
+ end
1492
+
1493
+ # Reverse the existing order clause on the relation.
1494
+ #
1495
+ # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
1496
+ def reverse_order
1497
+ spawn.reverse_order!
1498
+ end
1499
+
1500
+ def reverse_order! # :nodoc:
1501
+ orders = order_values.compact_blank
1502
+ self.order_values = reverse_sql_order(orders)
1503
+ self
1504
+ end
1505
+
1506
+ def skip_query_cache!(value = true) # :nodoc:
1507
+ self.skip_query_cache_value = value
1508
+ self
1509
+ end
1510
+
1511
+ def skip_preloading! # :nodoc:
1512
+ self.skip_preloading_value = true
1513
+ self
1514
+ end
1515
+
1516
+ # Adds an SQL comment to queries generated from this relation. For example:
1517
+ #
1518
+ # User.annotate("selecting user names").select(:name)
1519
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
1520
+ #
1521
+ # User.annotate("selecting", "user", "names").select(:name)
1522
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1523
+ #
1524
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1525
+ #
1526
+ # Some escaping is performed, however untrusted user input should not be used.
1527
+ def annotate(*args)
1528
+ check_if_method_has_arguments!(__callee__, args)
1529
+ spawn.annotate!(*args)
1530
+ end
1531
+
1532
+ # Like #annotate, but modifies relation in place.
1533
+ def annotate!(*args) # :nodoc:
1534
+ self.annotate_values += args
1535
+ self
1536
+ end
1537
+
1538
+ # Deduplicate multiple values.
1539
+ def uniq!(name)
1540
+ if values = @values[name]
1541
+ values.uniq! if values.is_a?(Array) && !values.empty?
1542
+ end
1543
+ self
1544
+ end
1545
+
1546
+ # Excludes the specified record (or collection of records) from the resulting
1547
+ # relation. For example:
1548
+ #
1549
+ # Post.excluding(post)
1550
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
1551
+ #
1552
+ # Post.excluding(post_one, post_two)
1553
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1554
+ #
1555
+ # Post.excluding(Post.drafts)
1556
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1557
+ #
1558
+ # This can also be called on associations. As with the above example, either
1559
+ # a single record of collection thereof may be specified:
1560
+ #
1561
+ # post = Post.find(1)
1562
+ # comment = Comment.find(2)
1563
+ # post.comments.excluding(comment)
1564
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
1565
+ #
1566
+ # This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
1567
+ #
1568
+ # An <tt>ArgumentError</tt> will be raised if either no records are
1569
+ # specified, or if any of the records in the collection (if a collection
1570
+ # is passed in) are not instances of the same model that the relation is
1571
+ # scoping.
1572
+ def excluding(*records)
1573
+ relations = records.extract! { |element| element.is_a?(Relation) }
1574
+ records.flatten!(1)
1575
+ records.compact!
1576
+
1577
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1578
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1579
+ end
1580
+
1581
+ spawn.excluding!(records + relations.flat_map(&:ids))
1582
+ end
1583
+ alias :without :excluding
1584
+
1585
+ def excluding!(records) # :nodoc:
1586
+ predicates = [ predicate_builder[primary_key, records].invert ]
1587
+ self.where_clause += Relation::WhereClause.new(predicates)
1588
+ self
1589
+ end
1590
+
1591
+ # Returns the Arel object associated with the relation.
1592
+ def arel(aliases = nil) # :nodoc:
1593
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1594
+ end
1595
+
1596
+ def construct_join_dependency(associations, join_type) # :nodoc:
1597
+ ActiveRecord::Associations::JoinDependency.new(
1598
+ model, table, associations, join_type
1599
+ )
1600
+ end
1601
+
1602
+ protected
1603
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1604
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
1605
+
1606
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1607
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1608
+ end
1609
+ end
1610
+
1611
+ def build_where_clause(opts, rest = []) # :nodoc:
1612
+ opts = sanitize_forbidden_attributes(opts)
1613
+
1614
+ if opts.is_a?(Array)
1615
+ opts, *rest = opts
1616
+ end
1617
+
1618
+ case opts
1619
+ when String
1620
+ if rest.empty?
1621
+ parts = [Arel.sql(opts)]
1622
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1623
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1624
+ elsif opts.include?("?")
1625
+ parts = [build_bound_sql_literal(opts, rest)]
1626
+ else
1627
+ parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1628
+ end
1629
+ when Hash
1630
+ opts = opts.transform_keys do |key|
1631
+ if key.is_a?(Array)
1632
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1633
+ else
1634
+ key = key.to_s
1635
+ model.attribute_aliases[key] || key
1636
+ end
1637
+ end
1638
+ references = PredicateBuilder.references(opts)
1639
+ self.references_values |= references unless references.empty?
1640
+
1641
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1642
+ lookup_table_klass_from_join_dependencies(table_name)
1643
+ end
1644
+ when Arel::Nodes::Node
1645
+ parts = [opts]
1646
+ else
1647
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1648
+ end
1649
+
1650
+ Relation::WhereClause.new(parts)
1651
+ end
1652
+ alias :build_having_clause :build_where_clause
1653
+
1654
+ def async!
1655
+ @async = true
1656
+ self
1657
+ end
1658
+
1659
+ private
1660
+ def async
1661
+ spawn.async!
1662
+ end
1663
+
1664
+ def build_named_bound_sql_literal(statement, values)
1665
+ bound_values = values.transform_values do |value|
1666
+ if ActiveRecord::Relation === value
1667
+ Arel.sql(value.to_sql)
1668
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1669
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1670
+ values.empty? ? nil : values
1671
+ else
1672
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1673
+ value
1674
+ end
1675
+ end
1676
+
1677
+ begin
1678
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1679
+ rescue Arel::BindError => error
1680
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1681
+ end
1682
+ end
1683
+
1684
+ def build_bound_sql_literal(statement, values)
1685
+ bound_values = values.map do |value|
1686
+ if ActiveRecord::Relation === value
1687
+ Arel.sql(value.to_sql)
1688
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1689
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1690
+ values.empty? ? nil : values
1691
+ else
1692
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1693
+ value
1694
+ end
1695
+ end
1696
+
1697
+ begin
1698
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1699
+ rescue Arel::BindError => error
1700
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1701
+ end
1702
+ end
1703
+
1704
+ def lookup_table_klass_from_join_dependencies(table_name)
1705
+ each_join_dependencies do |join|
1706
+ return join.base_klass if table_name == join.table_name
1707
+ end
1708
+ nil
1709
+ end
1710
+
1711
+ def each_join_dependencies(join_dependencies = build_join_dependencies, &block)
1712
+ join_dependencies.each do |join_dependency|
1713
+ join_dependency.each(&block)
1714
+ end
1715
+ end
1716
+
1717
+ def build_join_dependencies
1718
+ joins = joins_values | left_outer_joins_values
1719
+ joins |= eager_load_values unless eager_load_values.empty?
1720
+ joins |= includes_values unless includes_values.empty?
1721
+
1722
+ join_dependencies = []
1723
+ join_dependencies.unshift construct_join_dependency(
1724
+ select_named_joins(joins, join_dependencies), nil
1725
+ )
1726
+ end
1727
+
1728
+ def assert_modifiable!
1729
+ raise UnmodifiableRelation if @loaded || @arel
1730
+ end
1731
+
1732
+ def build_arel(connection, aliases = nil)
1733
+ arel = Arel::SelectManager.new(table)
1734
+
1735
+ build_joins(arel.join_sources, aliases)
1736
+
1737
+ arel.where(where_clause.ast) unless where_clause.empty?
1738
+ arel.having(having_clause.ast) unless having_clause.empty?
1739
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1740
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1741
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1742
+
1743
+ build_order(arel)
1744
+ build_with(arel)
1745
+ build_select(arel)
1746
+
1747
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1748
+ arel.distinct(distinct_value)
1749
+ arel.from(build_from) unless from_clause.empty?
1750
+ arel.lock(lock_value) if lock_value
1751
+
1752
+ unless annotate_values.empty?
1753
+ annotates = annotate_values
1754
+ annotates = annotates.uniq if annotates.size > 1
1755
+ arel.comment(*annotates)
1756
+ end
1757
+
1758
+ arel
1759
+ end
1760
+
1761
+ def build_cast_value(name, value)
1762
+ ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1763
+ end
1764
+
1765
+ def build_from
1766
+ opts = from_clause.value
1767
+ name = from_clause.name
1768
+ case opts
1769
+ when Relation
1770
+ if opts.eager_loading?
1771
+ opts = opts.send(:apply_join_dependency)
1772
+ end
1773
+ name ||= "subquery"
1774
+ opts.arel.as(name.to_s)
1775
+ else
1776
+ opts
1777
+ end
1778
+ end
1779
+
1780
+ def select_named_joins(join_names, stashed_joins = nil, &block)
1781
+ cte_joins, associations = join_names.partition do |join_name|
1782
+ Symbol === join_name && with_values.any? { _1.key?(join_name) }
1783
+ end
1784
+
1785
+ cte_joins.each do |cte_name|
1786
+ block&.call(CTEJoin.new(cte_name))
1787
+ end
1788
+
1789
+ select_association_list(associations, stashed_joins, &block)
1790
+ end
1791
+
1792
+ def select_association_list(associations, stashed_joins = nil)
1793
+ result = []
1794
+ associations.each do |association|
1795
+ case association
1796
+ when Hash, Symbol, Array
1797
+ result << association
1798
+ when ActiveRecord::Associations::JoinDependency
1799
+ stashed_joins&.<< association
1800
+ else
1801
+ yield association if block_given?
1802
+ end
1803
+ end
1804
+ result
1805
+ end
1806
+
1807
+ def build_join_buckets
1808
+ buckets = Hash.new { |h, k| h[k] = [] }
1809
+
1810
+ unless left_outer_joins_values.empty?
1811
+ stashed_left_joins = []
1812
+ left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
1813
+ if left_join.is_a?(CTEJoin)
1814
+ buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
1815
+ else
1816
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1817
+ end
1818
+ end
1819
+
1820
+ if joins_values.empty?
1821
+ buckets[:named_join] = left_joins
1822
+ buckets[:stashed_join] = stashed_left_joins
1823
+ return buckets, Arel::Nodes::OuterJoin
1824
+ else
1825
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1826
+ end
1827
+ end
1828
+
1829
+ joins = joins_values.dup
1830
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1831
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1832
+ end
1833
+
1834
+ joins.each_with_index do |join, i|
1835
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
1836
+ end
1837
+
1838
+ while joins.first.is_a?(Arel::Nodes::Join)
1839
+ join_node = joins.shift
1840
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1841
+ buckets[:join_node] << join_node
1842
+ else
1843
+ buckets[:leading_join] << join_node
1844
+ end
1845
+ end
1846
+
1847
+ buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
1848
+ if join.is_a?(Arel::Nodes::Join)
1849
+ buckets[:join_node] << join
1850
+ elsif join.is_a?(CTEJoin)
1851
+ buckets[:join_node] << build_with_join_node(join.name)
1852
+ else
1853
+ raise "unknown class: %s" % join.class.name
1854
+ end
1855
+ end
1856
+
1857
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1858
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1859
+
1860
+ return buckets, Arel::Nodes::InnerJoin
1861
+ end
1862
+
1863
+ def build_joins(join_sources, aliases = nil)
1864
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1865
+
1866
+ buckets, join_type = build_join_buckets
1867
+
1868
+ named_joins = buckets[:named_join]
1869
+ stashed_joins = buckets[:stashed_join]
1870
+ leading_joins = buckets[:leading_join]
1871
+ join_nodes = buckets[:join_node]
1872
+
1873
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1874
+
1875
+ unless named_joins.empty? && stashed_joins.empty?
1876
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1877
+ join_dependency = construct_join_dependency(named_joins, join_type)
1878
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1879
+ end
1880
+
1881
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1882
+ join_sources
1883
+ end
1884
+
1885
+ def build_select(arel)
1886
+ if select_values.any?
1887
+ arel.project(*arel_columns(select_values))
1888
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1889
+ arel.project(*model.column_names.map { |field| table[field] })
1890
+ else
1891
+ arel.project(table[Arel.star])
1892
+ end
1893
+ end
1894
+
1895
+ def build_with(arel)
1896
+ return if with_values.empty?
1897
+
1898
+ with_statements = with_values.map do |with_value|
1899
+ raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1900
+
1901
+ build_with_value_from_hash(with_value)
1902
+ end
1903
+
1904
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1905
+ end
1906
+
1907
+ def build_with_value_from_hash(hash)
1908
+ hash.map do |name, value|
1909
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1910
+ end
1911
+ end
1912
+
1913
+ def build_with_expression_from_value(value)
1914
+ case value
1915
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1916
+ when ActiveRecord::Relation then value.arel
1917
+ when Arel::SelectManager then value
1918
+ when Array then value.map { |q| build_with_expression_from_value(q) }.reduce { |result, value| result.union(:all, value) }
1919
+ else
1920
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1921
+ end
1922
+ end
1923
+
1924
+ def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
1925
+ with_table = Arel::Table.new(name)
1926
+
1927
+ table.join(with_table, kind).on(
1928
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1929
+ ).join_sources.first
1930
+ end
1931
+
1932
+ def arel_columns(columns)
1933
+ columns.flat_map do |field|
1934
+ case field
1935
+ when Symbol
1936
+ arel_column(field.to_s) do |attr_name|
1937
+ model.adapter_class.quote_table_name(attr_name)
1938
+ end
1939
+ when String
1940
+ arel_column(field, &:itself)
1941
+ when Proc
1942
+ field.call
1943
+ when Hash
1944
+ arel_columns_from_hash(field)
1945
+ else
1946
+ field
1947
+ end
1948
+ end
1949
+ end
1950
+
1951
+ def arel_column(field)
1952
+ field = model.attribute_aliases[field] || field
1953
+ from = from_clause.name || from_clause.value
1954
+
1955
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1956
+ table[field]
1957
+ elsif field.match?(/\A\w+\.\w+\z/)
1958
+ table, column = field.split(".")
1959
+ predicate_builder.resolve_arel_attribute(table, column) do
1960
+ lookup_table_klass_from_join_dependencies(table)
1961
+ end
1962
+ else
1963
+ yield field
1964
+ end
1965
+ end
1966
+
1967
+ def table_name_matches?(from)
1968
+ table_name = Regexp.escape(table.name)
1969
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1970
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1971
+ end
1972
+
1973
+ def reverse_sql_order(order_query)
1974
+ if order_query.empty?
1975
+ return [table[primary_key].desc] if primary_key
1976
+ raise IrreversibleOrderError,
1977
+ "Relation has no current order and table has no primary key to be used as default order"
1978
+ end
1979
+
1980
+ order_query.flat_map do |o|
1981
+ case o
1982
+ when Arel::Attribute
1983
+ o.desc
1984
+ when Arel::Nodes::Ordering
1985
+ o.reverse
1986
+ when Arel::Nodes::NodeExpression
1987
+ o.desc
1988
+ when String
1989
+ if does_not_support_reverse?(o)
1990
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1991
+ end
1992
+ o.split(",").map! do |s|
1993
+ s.strip!
1994
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1995
+ end
1996
+ else
1997
+ o
1998
+ end
1999
+ end
2000
+ end
2001
+
2002
+ def does_not_support_reverse?(order)
2003
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
2004
+ # override methods like #count.
2005
+ order = String.new(order) unless order.instance_of?(String)
2006
+
2007
+ # Uses SQL function with multiple arguments.
2008
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
2009
+ # Uses "nulls first" like construction.
2010
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
2011
+ end
2012
+
2013
+ def build_order(arel)
2014
+ orders = order_values.compact_blank
2015
+ arel.order(*orders) unless orders.empty?
2016
+ end
2017
+
2018
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
2019
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
2020
+
2021
+ def validate_order_args(args)
2022
+ args.each do |arg|
2023
+ next unless arg.is_a?(Hash)
2024
+ arg.each do |_key, value|
2025
+ if value.is_a?(Hash)
2026
+ validate_order_args([value])
2027
+ elsif VALID_DIRECTIONS.exclude?(value)
2028
+ raise ArgumentError,
2029
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
2030
+ end
2031
+ end
2032
+ end
2033
+ end
2034
+
2035
+ def flattened_args(args)
2036
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2037
+ end
2038
+
2039
+ def preprocess_order_args(order_args)
2040
+ model.disallow_raw_sql!(
2041
+ flattened_args(order_args),
2042
+ permit: model.adapter_class.column_name_with_order_matcher
2043
+ )
2044
+
2045
+ validate_order_args(order_args)
2046
+
2047
+ references = column_references(order_args)
2048
+ self.references_values |= references unless references.empty?
2049
+
2050
+ # if a symbol is given we prepend the quoted table name
2051
+ order_args.map! do |arg|
2052
+ case arg
2053
+ when Symbol
2054
+ order_column(arg.to_s).asc
2055
+ when Hash
2056
+ arg.map do |key, value|
2057
+ if value.is_a?(Hash)
2058
+ value.map do |field, dir|
2059
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2060
+ end
2061
+ else
2062
+ case key
2063
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2064
+ key.public_send(value.downcase)
2065
+ else
2066
+ order_column(key.to_s).public_send(value.downcase)
2067
+ end
2068
+ end
2069
+ end
2070
+ else
2071
+ arg
2072
+ end
2073
+ end.flatten!
2074
+ end
2075
+
2076
+ def sanitize_order_arguments(order_args)
2077
+ order_args.map! do |arg|
2078
+ model.sanitize_sql_for_order(arg)
2079
+ end
2080
+ end
2081
+
2082
+ def column_references(order_args)
2083
+ order_args.flat_map do |arg|
2084
+ case arg
2085
+ when String, Symbol
2086
+ extract_table_name_from(arg)
2087
+ when Hash
2088
+ arg
2089
+ .map do |key, value|
2090
+ case value
2091
+ when Hash
2092
+ key.to_s
2093
+ else
2094
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
2095
+ end
2096
+ end
2097
+ when Arel::Attribute
2098
+ arg.relation.name
2099
+ when Arel::Nodes::Ordering
2100
+ if arg.expr.is_a?(Arel::Attribute)
2101
+ arg.expr.relation.name
2102
+ end
2103
+ end
2104
+ end.compact
2105
+ end
2106
+
2107
+ def extract_table_name_from(string)
2108
+ string.match(/^\W?(\w+)\W?\./) && $1
2109
+ end
2110
+
2111
+ def order_column(field)
2112
+ arel_column(field) do |attr_name|
2113
+ if attr_name == "count" && !group_values.empty?
2114
+ table[attr_name]
2115
+ else
2116
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
2117
+ end
2118
+ end
2119
+ end
2120
+
2121
+ def build_case_for_value_position(column, values, filter: true)
2122
+ node = Arel::Nodes::Case.new
2123
+ values.each.with_index(1) do |value, order|
2124
+ node.when(column.eq(value)).then(order)
2125
+ end
2126
+
2127
+ node = node.else(values.length + 1) unless filter
2128
+ Arel::Nodes::Ascending.new(node)
2129
+ end
2130
+
2131
+ def resolve_arel_attributes(attrs)
2132
+ attrs.flat_map do |attr|
2133
+ case attr
2134
+ when Arel::Predications
2135
+ attr
2136
+ when Hash
2137
+ attr.flat_map do |table, columns|
2138
+ table = table.to_s
2139
+ Array(columns).map do |column|
2140
+ predicate_builder.resolve_arel_attribute(table, column)
2141
+ end
2142
+ end
2143
+ else
2144
+ attr = attr.to_s
2145
+ if attr.include?(".")
2146
+ table, column = attr.split(".", 2)
2147
+ predicate_builder.resolve_arel_attribute(table, column)
2148
+ else
2149
+ attr
2150
+ end
2151
+ end
2152
+ end
2153
+ end
2154
+
2155
+ # Checks to make sure that the arguments are not blank. Note that if some
2156
+ # blank-like object were initially passed into the query method, then this
2157
+ # method will not raise an error.
2158
+ #
2159
+ # Example:
2160
+ #
2161
+ # Post.references() # raises an error
2162
+ # Post.references([]) # does not raise an error
2163
+ #
2164
+ # This particular method should be called with a method_name (__callee__) and the args
2165
+ # passed into that method as an input. For example:
2166
+ #
2167
+ # def references(*args)
2168
+ # check_if_method_has_arguments!(__callee__, args)
2169
+ # ...
2170
+ # end
2171
+ def check_if_method_has_arguments!(method_name, args, message = nil)
2172
+ if args.blank?
2173
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
2174
+ else
2175
+ yield args if block_given?
2176
+
2177
+ args.flatten!
2178
+ args.compact_blank!
2179
+ end
2180
+ end
2181
+
2182
+ def process_select_args(fields)
2183
+ fields.flat_map do |field|
2184
+ if field.is_a?(Hash)
2185
+ arel_columns_from_hash(field)
2186
+ else
2187
+ field
2188
+ end
2189
+ end
2190
+ end
2191
+
2192
+ def arel_columns_from_hash(fields)
2193
+ fields.flat_map do |key, columns_aliases|
2194
+ case columns_aliases
2195
+ when Hash
2196
+ columns_aliases.map do |column, column_alias|
2197
+ if values[:joins]&.include?(key)
2198
+ references = PredicateBuilder.references({ key.to_s => fields[key] })
2199
+ self.references_values |= references unless references.empty?
2200
+ end
2201
+ arel_column("#{key}.#{column}") do
2202
+ predicate_builder.resolve_arel_attribute(key.to_s, column)
2203
+ end.as(column_alias.to_s)
2204
+ end
2205
+ when Array
2206
+ columns_aliases.map do |column|
2207
+ arel_column("#{key}.#{column}", &:itself)
2208
+ end
2209
+ when String, Symbol
2210
+ arel_column(key.to_s) do
2211
+ predicate_builder.resolve_arel_attribute(model.table_name, key.to_s)
2212
+ end.as(columns_aliases.to_s)
2213
+ end
2214
+ end
2215
+ end
2216
+
2217
+ STRUCTURAL_VALUE_METHODS = (
2218
+ Relation::VALUE_METHODS -
2219
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
2220
+ ).freeze # :nodoc:
2221
+
2222
+ def structurally_incompatible_values_for(other)
2223
+ values = other.values
2224
+ STRUCTURAL_VALUE_METHODS.reject do |method|
2225
+ v1, v2 = @values[method], values[method]
2226
+ if v1.is_a?(Array)
2227
+ next true unless v2.is_a?(Array)
2228
+ v1 = v1.uniq
2229
+ v2 = v2.uniq
2230
+ end
2231
+ v1 == v2
2232
+ end
2233
+ end
2234
+ end
2235
+ end