omg-activerecord 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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