omg-activerecord 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +355 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record/aggregations.rb +287 -0
  8. data/lib/active_record/association_relation.rb +50 -0
  9. data/lib/active_record/associations/alias_tracker.rb +90 -0
  10. data/lib/active_record/associations/association.rb +417 -0
  11. data/lib/active_record/associations/association_scope.rb +175 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +163 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  14. data/lib/active_record/associations/builder/association.rb +170 -0
  15. data/lib/active_record/associations/builder/belongs_to.rb +160 -0
  16. data/lib/active_record/associations/builder/collection_association.rb +80 -0
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
  18. data/lib/active_record/associations/builder/has_many.rb +23 -0
  19. data/lib/active_record/associations/builder/has_one.rb +61 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +48 -0
  21. data/lib/active_record/associations/collection_association.rb +535 -0
  22. data/lib/active_record/associations/collection_proxy.rb +1163 -0
  23. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  24. data/lib/active_record/associations/errors.rb +265 -0
  25. data/lib/active_record/associations/foreign_association.rb +40 -0
  26. data/lib/active_record/associations/has_many_association.rb +167 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +232 -0
  28. data/lib/active_record/associations/has_one_association.rb +142 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/join_dependency.rb +301 -0
  34. data/lib/active_record/associations/nested_error.rb +47 -0
  35. data/lib/active_record/associations/preloader/association.rb +316 -0
  36. data/lib/active_record/associations/preloader/batch.rb +48 -0
  37. data/lib/active_record/associations/preloader/branch.rb +153 -0
  38. data/lib/active_record/associations/preloader/through_association.rb +150 -0
  39. data/lib/active_record/associations/preloader.rb +135 -0
  40. data/lib/active_record/associations/singular_association.rb +76 -0
  41. data/lib/active_record/associations/through_association.rb +132 -0
  42. data/lib/active_record/associations.rb +1897 -0
  43. data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
  44. data/lib/active_record/attribute_assignment.rb +82 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
  46. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  47. data/lib/active_record/attribute_methods/dirty.rb +262 -0
  48. data/lib/active_record/attribute_methods/primary_key.rb +158 -0
  49. data/lib/active_record/attribute_methods/query.rb +50 -0
  50. data/lib/active_record/attribute_methods/read.rb +46 -0
  51. data/lib/active_record/attribute_methods/serialization.rb +232 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
  53. data/lib/active_record/attribute_methods/write.rb +49 -0
  54. data/lib/active_record/attribute_methods.rb +542 -0
  55. data/lib/active_record/attributes.rb +307 -0
  56. data/lib/active_record/autosave_association.rb +586 -0
  57. data/lib/active_record/base.rb +338 -0
  58. data/lib/active_record/callbacks.rb +452 -0
  59. data/lib/active_record/coders/column_serializer.rb +61 -0
  60. data/lib/active_record/coders/json.rb +15 -0
  61. data/lib/active_record/coders/yaml_column.rb +95 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
  64. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
  78. data/lib/active_record/connection_adapters/column.rb +122 -0
  79. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  80. data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
  81. data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
  82. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  83. data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  89. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
  90. data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
  91. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  92. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  93. data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
  94. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
  95. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
  115. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  116. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  117. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  118. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
  119. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
  120. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  121. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  122. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
  123. data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
  124. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
  125. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
  126. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
  127. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
  128. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
  129. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  130. data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
  131. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
  132. data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
  133. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  134. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  135. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
  136. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  137. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
  138. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
  139. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
  140. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
  141. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
  142. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
  143. data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
  144. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
  145. data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
  146. data/lib/active_record/connection_adapters.rb +176 -0
  147. data/lib/active_record/connection_handling.rb +413 -0
  148. data/lib/active_record/core.rb +836 -0
  149. data/lib/active_record/counter_cache.rb +230 -0
  150. data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
  151. data/lib/active_record/database_configurations/database_config.rb +104 -0
  152. data/lib/active_record/database_configurations/hash_config.rb +172 -0
  153. data/lib/active_record/database_configurations/url_config.rb +78 -0
  154. data/lib/active_record/database_configurations.rb +309 -0
  155. data/lib/active_record/delegated_type.rb +289 -0
  156. data/lib/active_record/deprecator.rb +7 -0
  157. data/lib/active_record/destroy_association_async_job.rb +38 -0
  158. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  159. data/lib/active_record/dynamic_matchers.rb +121 -0
  160. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  161. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  162. data/lib/active_record/encryption/cipher.rb +53 -0
  163. data/lib/active_record/encryption/config.rb +70 -0
  164. data/lib/active_record/encryption/configurable.rb +60 -0
  165. data/lib/active_record/encryption/context.rb +42 -0
  166. data/lib/active_record/encryption/contexts.rb +76 -0
  167. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  168. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  169. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  170. data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
  171. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  172. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  173. data/lib/active_record/encryption/encryptor.rb +177 -0
  174. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  175. data/lib/active_record/encryption/errors.rb +15 -0
  176. data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
  177. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  178. data/lib/active_record/encryption/key.rb +28 -0
  179. data/lib/active_record/encryption/key_generator.rb +53 -0
  180. data/lib/active_record/encryption/key_provider.rb +46 -0
  181. data/lib/active_record/encryption/message.rb +33 -0
  182. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  183. data/lib/active_record/encryption/message_serializer.rb +96 -0
  184. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  185. data/lib/active_record/encryption/properties.rb +76 -0
  186. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  187. data/lib/active_record/encryption/scheme.rb +107 -0
  188. data/lib/active_record/encryption.rb +58 -0
  189. data/lib/active_record/enum.rb +424 -0
  190. data/lib/active_record/errors.rb +614 -0
  191. data/lib/active_record/explain.rb +63 -0
  192. data/lib/active_record/explain_registry.rb +37 -0
  193. data/lib/active_record/explain_subscriber.rb +34 -0
  194. data/lib/active_record/fixture_set/file.rb +89 -0
  195. data/lib/active_record/fixture_set/model_metadata.rb +42 -0
  196. data/lib/active_record/fixture_set/render_context.rb +19 -0
  197. data/lib/active_record/fixture_set/table_row.rb +208 -0
  198. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  199. data/lib/active_record/fixtures.rb +850 -0
  200. data/lib/active_record/future_result.rb +182 -0
  201. data/lib/active_record/gem_version.rb +17 -0
  202. data/lib/active_record/inheritance.rb +366 -0
  203. data/lib/active_record/insert_all.rb +328 -0
  204. data/lib/active_record/integration.rb +209 -0
  205. data/lib/active_record/internal_metadata.rb +164 -0
  206. data/lib/active_record/legacy_yaml_adapter.rb +15 -0
  207. data/lib/active_record/locale/en.yml +48 -0
  208. data/lib/active_record/locking/optimistic.rb +228 -0
  209. data/lib/active_record/locking/pessimistic.rb +102 -0
  210. data/lib/active_record/log_subscriber.rb +149 -0
  211. data/lib/active_record/marshalling.rb +56 -0
  212. data/lib/active_record/message_pack.rb +124 -0
  213. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  214. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  215. data/lib/active_record/middleware/database_selector.rb +87 -0
  216. data/lib/active_record/middleware/shard_selector.rb +62 -0
  217. data/lib/active_record/migration/command_recorder.rb +406 -0
  218. data/lib/active_record/migration/compatibility.rb +490 -0
  219. data/lib/active_record/migration/default_strategy.rb +22 -0
  220. data/lib/active_record/migration/execution_strategy.rb +19 -0
  221. data/lib/active_record/migration/join_table.rb +16 -0
  222. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  223. data/lib/active_record/migration.rb +1626 -0
  224. data/lib/active_record/model_schema.rb +635 -0
  225. data/lib/active_record/nested_attributes.rb +633 -0
  226. data/lib/active_record/no_touching.rb +65 -0
  227. data/lib/active_record/normalization.rb +163 -0
  228. data/lib/active_record/persistence.rb +968 -0
  229. data/lib/active_record/promise.rb +84 -0
  230. data/lib/active_record/query_cache.rb +56 -0
  231. data/lib/active_record/query_logs.rb +247 -0
  232. data/lib/active_record/query_logs_formatter.rb +30 -0
  233. data/lib/active_record/querying.rb +122 -0
  234. data/lib/active_record/railtie.rb +440 -0
  235. data/lib/active_record/railties/console_sandbox.rb +5 -0
  236. data/lib/active_record/railties/controller_runtime.rb +65 -0
  237. data/lib/active_record/railties/databases.rake +641 -0
  238. data/lib/active_record/railties/job_runtime.rb +23 -0
  239. data/lib/active_record/readonly_attributes.rb +66 -0
  240. data/lib/active_record/reflection.rb +1287 -0
  241. data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
  242. data/lib/active_record/relation/batches.rb +491 -0
  243. data/lib/active_record/relation/calculations.rb +679 -0
  244. data/lib/active_record/relation/delegation.rb +154 -0
  245. data/lib/active_record/relation/finder_methods.rb +661 -0
  246. data/lib/active_record/relation/from_clause.rb +30 -0
  247. data/lib/active_record/relation/merger.rb +192 -0
  248. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  249. data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
  250. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  251. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
  252. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  253. data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
  254. data/lib/active_record/relation/predicate_builder.rb +181 -0
  255. data/lib/active_record/relation/query_attribute.rb +68 -0
  256. data/lib/active_record/relation/query_methods.rb +2235 -0
  257. data/lib/active_record/relation/record_fetch_warning.rb +52 -0
  258. data/lib/active_record/relation/spawn_methods.rb +78 -0
  259. data/lib/active_record/relation/where_clause.rb +218 -0
  260. data/lib/active_record/relation.rb +1495 -0
  261. data/lib/active_record/result.rb +249 -0
  262. data/lib/active_record/runtime_registry.rb +82 -0
  263. data/lib/active_record/sanitization.rb +254 -0
  264. data/lib/active_record/schema.rb +77 -0
  265. data/lib/active_record/schema_dumper.rb +364 -0
  266. data/lib/active_record/schema_migration.rb +106 -0
  267. data/lib/active_record/scoping/default.rb +205 -0
  268. data/lib/active_record/scoping/named.rb +202 -0
  269. data/lib/active_record/scoping.rb +136 -0
  270. data/lib/active_record/secure_password.rb +60 -0
  271. data/lib/active_record/secure_token.rb +66 -0
  272. data/lib/active_record/serialization.rb +29 -0
  273. data/lib/active_record/signed_id.rb +137 -0
  274. data/lib/active_record/statement_cache.rb +164 -0
  275. data/lib/active_record/store.rb +299 -0
  276. data/lib/active_record/suppressor.rb +59 -0
  277. data/lib/active_record/table_metadata.rb +85 -0
  278. data/lib/active_record/tasks/database_tasks.rb +681 -0
  279. data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
  280. data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
  281. data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
  282. data/lib/active_record/test_databases.rb +24 -0
  283. data/lib/active_record/test_fixtures.rb +321 -0
  284. data/lib/active_record/testing/query_assertions.rb +121 -0
  285. data/lib/active_record/timestamp.rb +177 -0
  286. data/lib/active_record/token_for.rb +123 -0
  287. data/lib/active_record/touch_later.rb +70 -0
  288. data/lib/active_record/transaction.rb +132 -0
  289. data/lib/active_record/transactions.rb +523 -0
  290. data/lib/active_record/translation.rb +22 -0
  291. data/lib/active_record/type/adapter_specific_registry.rb +144 -0
  292. data/lib/active_record/type/date.rb +9 -0
  293. data/lib/active_record/type/date_time.rb +9 -0
  294. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  295. data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
  296. data/lib/active_record/type/internal/timezone.rb +22 -0
  297. data/lib/active_record/type/json.rb +30 -0
  298. data/lib/active_record/type/serialized.rb +76 -0
  299. data/lib/active_record/type/text.rb +11 -0
  300. data/lib/active_record/type/time.rb +35 -0
  301. data/lib/active_record/type/type_map.rb +58 -0
  302. data/lib/active_record/type/unsigned_integer.rb +16 -0
  303. data/lib/active_record/type.rb +83 -0
  304. data/lib/active_record/type_caster/connection.rb +33 -0
  305. data/lib/active_record/type_caster/map.rb +23 -0
  306. data/lib/active_record/type_caster.rb +9 -0
  307. data/lib/active_record/validations/absence.rb +25 -0
  308. data/lib/active_record/validations/associated.rb +65 -0
  309. data/lib/active_record/validations/length.rb +26 -0
  310. data/lib/active_record/validations/numericality.rb +36 -0
  311. data/lib/active_record/validations/presence.rb +45 -0
  312. data/lib/active_record/validations/uniqueness.rb +295 -0
  313. data/lib/active_record/validations.rb +101 -0
  314. data/lib/active_record/version.rb +10 -0
  315. data/lib/active_record.rb +616 -0
  316. data/lib/arel/alias_predication.rb +9 -0
  317. data/lib/arel/attributes/attribute.rb +33 -0
  318. data/lib/arel/collectors/bind.rb +31 -0
  319. data/lib/arel/collectors/composite.rb +46 -0
  320. data/lib/arel/collectors/plain_string.rb +20 -0
  321. data/lib/arel/collectors/sql_string.rb +27 -0
  322. data/lib/arel/collectors/substitute_binds.rb +35 -0
  323. data/lib/arel/crud.rb +48 -0
  324. data/lib/arel/delete_manager.rb +32 -0
  325. data/lib/arel/errors.rb +19 -0
  326. data/lib/arel/expressions.rb +29 -0
  327. data/lib/arel/factory_methods.rb +53 -0
  328. data/lib/arel/filter_predications.rb +9 -0
  329. data/lib/arel/insert_manager.rb +48 -0
  330. data/lib/arel/math.rb +45 -0
  331. data/lib/arel/nodes/ascending.rb +23 -0
  332. data/lib/arel/nodes/binary.rb +125 -0
  333. data/lib/arel/nodes/bind_param.rb +44 -0
  334. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  335. data/lib/arel/nodes/case.rb +55 -0
  336. data/lib/arel/nodes/casted.rb +62 -0
  337. data/lib/arel/nodes/comment.rb +29 -0
  338. data/lib/arel/nodes/count.rb +12 -0
  339. data/lib/arel/nodes/cte.rb +36 -0
  340. data/lib/arel/nodes/delete_statement.rb +44 -0
  341. data/lib/arel/nodes/descending.rb +23 -0
  342. data/lib/arel/nodes/equality.rb +15 -0
  343. data/lib/arel/nodes/extract.rb +24 -0
  344. data/lib/arel/nodes/false.rb +16 -0
  345. data/lib/arel/nodes/filter.rb +10 -0
  346. data/lib/arel/nodes/fragments.rb +35 -0
  347. data/lib/arel/nodes/full_outer_join.rb +8 -0
  348. data/lib/arel/nodes/function.rb +45 -0
  349. data/lib/arel/nodes/grouping.rb +11 -0
  350. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  351. data/lib/arel/nodes/in.rb +15 -0
  352. data/lib/arel/nodes/infix_operation.rb +92 -0
  353. data/lib/arel/nodes/inner_join.rb +8 -0
  354. data/lib/arel/nodes/insert_statement.rb +37 -0
  355. data/lib/arel/nodes/join_source.rb +20 -0
  356. data/lib/arel/nodes/leading_join.rb +8 -0
  357. data/lib/arel/nodes/matches.rb +18 -0
  358. data/lib/arel/nodes/named_function.rb +23 -0
  359. data/lib/arel/nodes/nary.rb +39 -0
  360. data/lib/arel/nodes/node.rb +161 -0
  361. data/lib/arel/nodes/node_expression.rb +13 -0
  362. data/lib/arel/nodes/ordering.rb +27 -0
  363. data/lib/arel/nodes/outer_join.rb +8 -0
  364. data/lib/arel/nodes/over.rb +15 -0
  365. data/lib/arel/nodes/regexp.rb +16 -0
  366. data/lib/arel/nodes/right_outer_join.rb +8 -0
  367. data/lib/arel/nodes/select_core.rb +67 -0
  368. data/lib/arel/nodes/select_statement.rb +41 -0
  369. data/lib/arel/nodes/sql_literal.rb +32 -0
  370. data/lib/arel/nodes/string_join.rb +11 -0
  371. data/lib/arel/nodes/table_alias.rb +35 -0
  372. data/lib/arel/nodes/terminal.rb +16 -0
  373. data/lib/arel/nodes/true.rb +16 -0
  374. data/lib/arel/nodes/unary.rb +44 -0
  375. data/lib/arel/nodes/unary_operation.rb +20 -0
  376. data/lib/arel/nodes/unqualified_column.rb +22 -0
  377. data/lib/arel/nodes/update_statement.rb +46 -0
  378. data/lib/arel/nodes/values_list.rb +9 -0
  379. data/lib/arel/nodes/window.rb +126 -0
  380. data/lib/arel/nodes/with.rb +11 -0
  381. data/lib/arel/nodes.rb +75 -0
  382. data/lib/arel/order_predications.rb +13 -0
  383. data/lib/arel/predications.rb +260 -0
  384. data/lib/arel/select_manager.rb +276 -0
  385. data/lib/arel/table.rb +121 -0
  386. data/lib/arel/tree_manager.rb +65 -0
  387. data/lib/arel/update_manager.rb +49 -0
  388. data/lib/arel/visitors/dot.rb +299 -0
  389. data/lib/arel/visitors/mysql.rb +111 -0
  390. data/lib/arel/visitors/postgresql.rb +99 -0
  391. data/lib/arel/visitors/sqlite.rb +38 -0
  392. data/lib/arel/visitors/to_sql.rb +1033 -0
  393. data/lib/arel/visitors/visitor.rb +45 -0
  394. data/lib/arel/visitors.rb +13 -0
  395. data/lib/arel/window_predications.rb +9 -0
  396. data/lib/arel.rb +73 -0
  397. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  398. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  399. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  400. data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
  401. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
  402. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  403. data/lib/rails/generators/active_record/migration.rb +54 -0
  404. data/lib/rails/generators/active_record/model/USAGE +113 -0
  405. data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
  406. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  407. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  408. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  409. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  410. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  411. data/lib/rails/generators/active_record.rb +19 -0
  412. metadata +505 -0
@@ -0,0 +1,309 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "active_record/database_configurations/database_config"
5
+ require "active_record/database_configurations/hash_config"
6
+ require "active_record/database_configurations/url_config"
7
+ require "active_record/database_configurations/connection_url_resolver"
8
+
9
+ module ActiveRecord
10
+ # = Active Record Database Configurations
11
+ #
12
+ # +ActiveRecord::DatabaseConfigurations+ returns an array of +DatabaseConfig+
13
+ # objects that are constructed from the application's database
14
+ # configuration hash or URL string.
15
+ #
16
+ # The array of +DatabaseConfig+ objects in an application default to either a
17
+ # HashConfig or UrlConfig. You can retrieve your application's config by using
18
+ # ActiveRecord::Base.configurations.
19
+ #
20
+ # If you register a custom handler, objects will be created according to the
21
+ # conditions of the handler. See ::register_db_config_handler for more on
22
+ # registering custom handlers.
23
+ class DatabaseConfigurations
24
+ class InvalidConfigurationError < StandardError; end
25
+
26
+ attr_reader :configurations
27
+ delegate :any?, to: :configurations
28
+
29
+ singleton_class.attr_accessor :db_config_handlers # :nodoc:
30
+ self.db_config_handlers = [] # :nodoc:
31
+
32
+ # Allows an application to register a custom handler for database configuration
33
+ # objects. This is useful for creating a custom handler that responds to
34
+ # methods your application needs but Active Record doesn't implement. For
35
+ # example if you are using Vitess, you may want your Vitess configurations
36
+ # to respond to `sharded?`. To implement this define the following in an
37
+ # initializer:
38
+ #
39
+ # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
40
+ # next unless config.key?(:vitess)
41
+ # VitessConfig.new(env_name, name, config)
42
+ # end
43
+ #
44
+ # Note: applications must handle the condition in which custom config should be
45
+ # created in your handler registration otherwise all objects will use the custom
46
+ # handler.
47
+ #
48
+ # Then define your +VitessConfig+ to respond to the methods your application
49
+ # needs. It is recommended that you inherit from one of the existing
50
+ # database config classes to avoid having to reimplement all methods. Custom
51
+ # config handlers should only implement methods Active Record does not.
52
+ #
53
+ # class VitessConfig < ActiveRecord::DatabaseConfigurations::UrlConfig
54
+ # def sharded?
55
+ # configuration_hash.fetch("sharded", false)
56
+ # end
57
+ # end
58
+ #
59
+ # For configs that have a +:vitess+ key, a +VitessConfig+ object will be
60
+ # created instead of a +UrlConfig+.
61
+ def self.register_db_config_handler(&block)
62
+ db_config_handlers << block
63
+ end
64
+
65
+ register_db_config_handler do |env_name, name, url, config|
66
+ if url
67
+ UrlConfig.new(env_name, name, url, config)
68
+ else
69
+ HashConfig.new(env_name, name, config)
70
+ end
71
+ end
72
+
73
+ def initialize(configurations = {})
74
+ @configurations = build_configs(configurations)
75
+ end
76
+
77
+ # Collects the configs for the environment and optionally the specification
78
+ # name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
79
+ #
80
+ # If a name is provided a single +DatabaseConfig+ object will be
81
+ # returned, otherwise an array of +DatabaseConfig+ objects will be
82
+ # returned that corresponds with the environment and type requested.
83
+ #
84
+ # ==== Options
85
+ #
86
+ # * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
87
+ # configs for all environments.
88
+ # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
89
+ # to +nil+. If no +env_name+ is specified the config for the default env and the
90
+ # passed +name+ will be returned.
91
+ # * <tt>config_key:</tt> Selects configs that contain a particular key in the configuration
92
+ # hash. Useful for selecting configs that use a custom db config handler or finding
93
+ # configs with hashes that contain a particular key.
94
+ # * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
95
+ # hidden by <tt>database_tasks: false</tt> in the returned list. Most of the time we're only
96
+ # iterating over the primary connections (i.e. migrations don't need to run for the
97
+ # write and read connection). Defaults to +false+.
98
+ def configs_for(env_name: nil, name: nil, config_key: nil, include_hidden: false)
99
+ env_name ||= default_env if name
100
+ configs = env_with_configs(env_name)
101
+
102
+ unless include_hidden
103
+ configs = configs.select do |db_config|
104
+ db_config.database_tasks?
105
+ end
106
+ end
107
+
108
+ if config_key
109
+ configs = configs.select do |db_config|
110
+ db_config.configuration_hash.key?(config_key)
111
+ end
112
+ end
113
+
114
+ if name
115
+ configs.find do |db_config|
116
+ db_config.name == name.to_s
117
+ end
118
+ else
119
+ configs
120
+ end
121
+ end
122
+
123
+ # Returns a single +DatabaseConfig+ object based on the requested environment.
124
+ #
125
+ # If the application has multiple databases +find_db_config+ will return
126
+ # the first +DatabaseConfig+ for the environment.
127
+ def find_db_config(env)
128
+ env = env.to_s
129
+ configurations.find do |db_config|
130
+ db_config.for_current_env? && (db_config.env_name == env || db_config.name == env)
131
+ end || configurations.find do |db_config|
132
+ db_config.env_name == env
133
+ end
134
+ end
135
+
136
+ # A primary configuration is one that is named primary or if there is
137
+ # no primary, the first configuration for an environment will be treated
138
+ # as primary. This is used as the "default" configuration and is used
139
+ # when the application needs to treat one configuration differently. For
140
+ # example, when Rails dumps the schema, the primary configuration's schema
141
+ # file will be named `schema.rb` instead of `primary_schema.rb`.
142
+ def primary?(name) # :nodoc:
143
+ return true if name == "primary"
144
+
145
+ first_config = find_db_config(default_env)
146
+ first_config && name == first_config.name
147
+ end
148
+
149
+ # Checks if the application's configurations are empty.
150
+ def empty?
151
+ configurations.empty?
152
+ end
153
+ alias :blank? :empty?
154
+
155
+ # Returns fully resolved connection, accepts hash, string or symbol.
156
+ # Always returns a DatabaseConfiguration::DatabaseConfig
157
+ #
158
+ # == Examples
159
+ #
160
+ # Symbol representing current environment.
161
+ #
162
+ # DatabaseConfigurations.new("production" => {}).resolve(:production)
163
+ # # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
164
+ #
165
+ # One layer deep hash of connection values.
166
+ #
167
+ # DatabaseConfigurations.new({}).resolve("adapter" => "sqlite3")
168
+ # # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
169
+ #
170
+ # Connection URL.
171
+ #
172
+ # DatabaseConfigurations.new({}).resolve("postgresql://localhost/foo")
173
+ # # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
174
+ def resolve(config) # :nodoc:
175
+ return config if DatabaseConfigurations::DatabaseConfig === config
176
+
177
+ case config
178
+ when Symbol
179
+ resolve_symbol_connection(config)
180
+ when Hash, String
181
+ build_db_config_from_raw_config(default_env, "primary", config)
182
+ else
183
+ raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config.inspect}"
184
+ end
185
+ end
186
+
187
+ private
188
+ def default_env
189
+ ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
190
+ end
191
+
192
+ def env_with_configs(env = nil)
193
+ if env
194
+ configurations.select { |db_config| db_config.env_name == env }
195
+ else
196
+ configurations
197
+ end
198
+ end
199
+
200
+ def build_configs(configs)
201
+ return configs.configurations if configs.is_a?(DatabaseConfigurations)
202
+ return configs if configs.is_a?(Array)
203
+
204
+ db_configs = configs.flat_map do |env_name, config|
205
+ if config.is_a?(Hash) && config.values.all?(Hash)
206
+ walk_configs(env_name.to_s, config)
207
+ else
208
+ build_db_config_from_raw_config(env_name.to_s, "primary", config)
209
+ end
210
+ end
211
+
212
+ unless db_configs.find(&:for_current_env?)
213
+ db_configs << environment_url_config(default_env, "primary", {})
214
+ end
215
+
216
+ merge_db_environment_variables(default_env, db_configs.compact)
217
+ end
218
+
219
+ def walk_configs(env_name, config)
220
+ config.map do |name, sub_config|
221
+ build_db_config_from_raw_config(env_name, name.to_s, sub_config)
222
+ end
223
+ end
224
+
225
+ def resolve_symbol_connection(name)
226
+ if db_config = find_db_config(name)
227
+ db_config
228
+ else
229
+ raise AdapterNotSpecified, <<~MSG
230
+ The `#{name}` database is not configured for the `#{default_env}` environment.
231
+
232
+ Available database configurations are:
233
+
234
+ #{build_configuration_sentence}
235
+ MSG
236
+ end
237
+ end
238
+
239
+ def build_configuration_sentence
240
+ configs = configs_for(include_hidden: true)
241
+
242
+ configs.group_by(&:env_name).map do |env, config|
243
+ names = config.map(&:name)
244
+ if names.size > 1
245
+ "#{env}: #{names.join(", ")}"
246
+ else
247
+ env
248
+ end
249
+ end.join("\n")
250
+ end
251
+
252
+ def build_db_config_from_raw_config(env_name, name, config)
253
+ case config
254
+ when String
255
+ build_db_config_from_string(env_name, name, config)
256
+ when Hash
257
+ build_db_config_from_hash(env_name, name, config.symbolize_keys)
258
+ else
259
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
260
+ end
261
+ end
262
+
263
+ def build_db_config_from_string(env_name, name, config)
264
+ url = config
265
+ uri = URI.parse(url)
266
+ if uri.scheme
267
+ UrlConfig.new(env_name, name, url)
268
+ else
269
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
270
+ end
271
+ end
272
+
273
+ def build_db_config_from_hash(env_name, name, config)
274
+ url = config[:url]
275
+ config_without_url = config.dup
276
+ config_without_url.delete :url
277
+
278
+ DatabaseConfigurations.db_config_handlers.reverse_each do |handler|
279
+ config = handler.call(env_name, name, url, config_without_url)
280
+ return config if config
281
+ end
282
+
283
+ nil
284
+ end
285
+
286
+ def merge_db_environment_variables(current_env, configs)
287
+ configs.map do |config|
288
+ next config if config.is_a?(UrlConfig) || config.env_name != current_env
289
+
290
+ url_config = environment_url_config(current_env, config.name, config.configuration_hash)
291
+ url_config || config
292
+ end
293
+ end
294
+
295
+ def environment_url_config(env, name, config)
296
+ url = environment_value_for(name)
297
+ return unless url
298
+
299
+ UrlConfig.new(env, name, url, config)
300
+ end
301
+
302
+ def environment_value_for(name)
303
+ name_env_key = "#{name.upcase}_DATABASE_URL"
304
+ url = ENV[name_env_key]
305
+ url ||= ENV["DATABASE_URL"] if name == "primary"
306
+ url
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inquiry"
4
+
5
+ module ActiveRecord
6
+ # = Delegated types
7
+ #
8
+ # Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
9
+ # purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
10
+ # where all attributes from all levels of the hierarchy are represented in a single table. Both have their
11
+ # places, but neither are without their drawbacks.
12
+ #
13
+ # The problem with purely abstract classes is that all concrete subclasses must persist all the shared
14
+ # attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to
15
+ # do queries across the hierarchy. For example, imagine you have the following hierarchy:
16
+ #
17
+ # Entry < ApplicationRecord
18
+ # Message < Entry
19
+ # Comment < Entry
20
+ #
21
+ # How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated?
22
+ # Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't
23
+ # pull from both tables at once and use a consistent OFFSET/LIMIT scheme.
24
+ #
25
+ # You can get around the pagination problem by using single-table inheritance, but now you're forced into
26
+ # a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message
27
+ # has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's
28
+ # little divergence between the subclasses and their attributes.
29
+ #
30
+ # But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class
31
+ # that is represented by its own table, where all the superclass attributes that are shared amongst all the
32
+ # "subclasses" are stored. And then each of the subclasses have their own individual tables for additional
33
+ # attributes that are particular to their implementation. This is similar to what's called multi-table
34
+ # inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the
35
+ # hierarchy and share responsibilities.
36
+ #
37
+ # Let's look at that entry/message/comment example using delegated types:
38
+ #
39
+ # # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ]
40
+ # class Entry < ApplicationRecord
41
+ # belongs_to :account
42
+ # belongs_to :creator
43
+ # delegated_type :entryable, types: %w[ Message Comment ]
44
+ # end
45
+ #
46
+ # module Entryable
47
+ # extend ActiveSupport::Concern
48
+ #
49
+ # included do
50
+ # has_one :entry, as: :entryable, touch: true
51
+ # end
52
+ # end
53
+ #
54
+ # # Schema: messages[ id, subject, body, created_at, updated_at ]
55
+ # class Message < ApplicationRecord
56
+ # include Entryable
57
+ # end
58
+ #
59
+ # # Schema: comments[ id, content, created_at, updated_at ]
60
+ # class Comment < ApplicationRecord
61
+ # include Entryable
62
+ # end
63
+ #
64
+ # As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes
65
+ # resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
66
+ # in particular. You can now easily do things like:
67
+ #
68
+ # Account.find(1).entries.order(created_at: :desc).limit(50)
69
+ #
70
+ # Which is exactly what you want when displaying both comments and messages together. The entry itself can
71
+ # be rendered as its delegated type easily, like so:
72
+ #
73
+ # # entries/_entry.html.erb
74
+ # <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
75
+ #
76
+ # # entries/entryables/_message.html.erb
77
+ # <div class="message">
78
+ # <div class="subject"><%= entry.message.subject %></div>
79
+ # <p><%= entry.message.body %></p>
80
+ # <i>Posted on <%= entry.created_at %> by <%= entry.creator.name %></i>
81
+ # </div>
82
+ #
83
+ # # entries/entryables/_comment.html.erb
84
+ # <div class="comment">
85
+ # <%= entry.creator.name %> said: <%= entry.comment.content %>
86
+ # </div>
87
+ #
88
+ # == Sharing behavior with concerns and controllers
89
+ #
90
+ # The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both
91
+ # messages and comments, and which acts primarily on the shared attributes. Imagine:
92
+ #
93
+ # class Entry < ApplicationRecord
94
+ # include Eventable, Forwardable, Redeliverable
95
+ # end
96
+ #
97
+ # Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+
98
+ # that both act on entries, and thus provide the shared functionality to both messages and comments.
99
+ #
100
+ # == Creating new records
101
+ #
102
+ # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
103
+ # like so:
104
+ #
105
+ # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
106
+ #
107
+ # If you need more complicated composition, or you need to perform dependent validation, you should build a factory
108
+ # method or class to take care of the complicated needs. This could be as simple as:
109
+ #
110
+ # class Entry < ApplicationRecord
111
+ # def self.create_with_comment(content, creator: Current.user, account: Current.account)
112
+ # create! entryable: Comment.new(content: content), creator: creator, account: account
113
+ # end
114
+ # end
115
+ #
116
+ # == Querying across records
117
+ #
118
+ # A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
119
+ # tricky, but not impossible.
120
+ #
121
+ # The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
122
+ # in appropriate places:
123
+ #
124
+ # Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
125
+ #
126
+ # For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
127
+ # the method:
128
+ #
129
+ # # app/models/concerns/entryable.rb
130
+ # scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
131
+ #
132
+ # Now the query can be shortened significantly:
133
+ #
134
+ # Comment.where(content: 'Hello!').with_entry(creator: Current.user)
135
+ #
136
+ # == Adding further delegation
137
+ #
138
+ # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
139
+ # an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism.
140
+ # So here's a simple example of that:
141
+ #
142
+ # class Entry < ApplicationRecord
143
+ # delegated_type :entryable, types: %w[ Message Comment ]
144
+ # delegate :title, to: :entryable
145
+ # end
146
+ #
147
+ # class Message < ApplicationRecord
148
+ # def title
149
+ # subject
150
+ # end
151
+ # end
152
+ #
153
+ # class Comment < ApplicationRecord
154
+ # def title
155
+ # content.truncate(20)
156
+ # end
157
+ # end
158
+ #
159
+ # Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
160
+ #
161
+ # == Nested \Attributes
162
+ #
163
+ # Enabling nested attributes on a delegated_type association allows you to
164
+ # create the entry and message in one go:
165
+ #
166
+ # class Entry < ApplicationRecord
167
+ # delegated_type :entryable, types: %w[ Message Comment ]
168
+ # accepts_nested_attributes_for :entryable
169
+ # end
170
+ #
171
+ # params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } }
172
+ # entry = Entry.create(params[:entry])
173
+ # entry.entryable.id # => 2
174
+ # entry.entryable.subject # => 'Smiling'
175
+ module DelegatedType
176
+ # Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
177
+ # That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
178
+ # type convenience methods:
179
+ #
180
+ # class Entry < ApplicationRecord
181
+ # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
182
+ # end
183
+ #
184
+ # Entry#entryable_class # => +Message+ or +Comment+
185
+ # Entry#entryable_name # => "message" or "comment"
186
+ # Entry.messages # => Entry.where(entryable_type: "Message")
187
+ # Entry#message? # => true when entryable_type == "Message"
188
+ # Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
189
+ # Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
190
+ # Entry.comments # => Entry.where(entryable_type: "Comment")
191
+ # Entry#comment? # => true when entryable_type == "Comment"
192
+ # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
193
+ # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
194
+ #
195
+ # You can also declare namespaced types:
196
+ #
197
+ # class Entry < ApplicationRecord
198
+ # delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
199
+ # end
200
+ #
201
+ # Entry.access_notice_messages
202
+ # entry.access_notice_message
203
+ # entry.access_notice_message?
204
+ #
205
+ # === Options
206
+ #
207
+ # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
208
+ # The following options can be included to specialize the behavior of the delegated type convenience methods.
209
+ #
210
+ # [:foreign_key]
211
+ # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
212
+ # +role+ with an "_id" suffix. So a class that defines a
213
+ # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
214
+ # the default <tt>:foreign_key</tt>.
215
+ # [:foreign_type]
216
+ # Specify the column used to store the associated object's type. By default this is inferred to be the passed
217
+ # +role+ with a "_type" suffix. A class that defines a
218
+ # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
219
+ # the default <tt>:foreign_type</tt>.
220
+ # [:primary_key]
221
+ # Specify the method that returns the primary key of associated object used for the convenience methods.
222
+ # By default this is +id+.
223
+ # [+:inverse_of+]
224
+ # Specifies the name of the #belongs_to association on the associated object
225
+ # that is the inverse of this #has_one association. By default, the class
226
+ # singularized class name is used unless a <tt>:foreign_key</tt> option is
227
+ # also provided. For example, a call to
228
+ # <tt>Entry.delegated_type</tt> will default to <tt>inverse_of: :entry</tt>.
229
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional
230
+ # associations for more detail.
231
+ #
232
+ # Option examples:
233
+ # class Entry < ApplicationRecord
234
+ # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
235
+ # end
236
+ #
237
+ # Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
238
+ # Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
239
+ def delegated_type(role, types:, **options)
240
+ options[:inverse_of] = model_name.singular unless options.key?(:inverse_of) || options.key?(:foreign_key)
241
+
242
+ belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
243
+ define_delegated_type_methods role, types: types, options: options
244
+ end
245
+
246
+ private
247
+ def define_delegated_type_methods(role, types:, options:)
248
+ primary_key = options[:primary_key] || "id"
249
+ role_type = options[:foreign_type] || "#{role}_type"
250
+ role_id = options[:foreign_key] || "#{role}_id"
251
+
252
+ define_singleton_method "#{role}_types" do
253
+ types.map(&:to_s)
254
+ end
255
+
256
+ define_method "#{role}_class" do
257
+ public_send(role_type).constantize
258
+ end
259
+
260
+ define_method "#{role}_name" do
261
+ public_send("#{role}_class").model_name.singular.inquiry
262
+ end
263
+
264
+ define_method "build_#{role}" do |*params|
265
+ public_send("#{role}=", public_send("#{role}_class").new(*params))
266
+ end
267
+
268
+ types.each do |type|
269
+ scope_name = type.tableize.tr("/", "_")
270
+ singular = scope_name.singularize
271
+ query = "#{singular}?"
272
+
273
+ scope scope_name, -> { where(role_type => type) }
274
+
275
+ define_method query do
276
+ public_send(role_type) == type
277
+ end
278
+
279
+ define_method singular do
280
+ public_send(role) if public_send(query)
281
+ end
282
+
283
+ define_method "#{singular}_#{primary_key}" do
284
+ public_send(role_id) if public_send(query)
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DestroyAssociationAsyncError < StandardError
5
+ end
6
+
7
+ # = Active Record Destroy Association Async Job
8
+ #
9
+ # Job to destroy the records associated with a destroyed record in background.
10
+ class DestroyAssociationAsyncJob < ActiveJob::Base
11
+ queue_as { ActiveRecord.queues[:destroy] }
12
+
13
+ discard_on ActiveJob::DeserializationError
14
+
15
+ def perform(
16
+ owner_model_name: nil, owner_id: nil,
17
+ association_class: nil, association_ids: nil, association_primary_key_column: nil,
18
+ ensuring_owner_was_method: nil
19
+ )
20
+ association_model = association_class.constantize
21
+ owner_class = owner_model_name.constantize
22
+ owner = owner_class.find_by(owner_class.primary_key => [owner_id])
23
+
24
+ if !owner_destroyed?(owner, ensuring_owner_was_method)
25
+ raise DestroyAssociationAsyncError, "owner record not destroyed"
26
+ end
27
+
28
+ association_model.where(association_primary_key_column => association_ids).find_each do |r|
29
+ r.destroy
30
+ end
31
+ end
32
+
33
+ private
34
+ def owner_destroyed?(owner, ensuring_owner_was_method)
35
+ !owner || (ensuring_owner_was_method && owner.public_send(ensuring_owner_was_method))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DisableJoinsAssociationRelation < Relation # :nodoc:
5
+ attr_reader :ids, :key
6
+
7
+ def initialize(klass, key, ids)
8
+ @ids = ids.uniq
9
+ @key = key
10
+ super(klass)
11
+ end
12
+
13
+ def limit(value)
14
+ records.take(value)
15
+ end
16
+
17
+ def first(limit = nil)
18
+ if limit
19
+ records.limit(limit).first
20
+ else
21
+ records.first
22
+ end
23
+ end
24
+
25
+ def load
26
+ super
27
+ records = @records
28
+
29
+ records_by_id = records.group_by do |record|
30
+ record[key]
31
+ end
32
+
33
+ records = ids.flat_map { |id| records_by_id[id] }
34
+ records.compact!
35
+
36
+ @records = records
37
+ end
38
+ end
39
+ end