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,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # = Active Record \Named \Scopes
5
+ module Scoping
6
+ module Named
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ # Returns an ActiveRecord::Relation scope object.
11
+ #
12
+ # posts = Post.all
13
+ # posts.size # Fires "select count(*) from posts" and returns the count
14
+ # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
15
+ #
16
+ # fruits = Fruit.all
17
+ # fruits = fruits.where(color: 'red') if options[:red_only]
18
+ # fruits = fruits.limit(10) if limited?
19
+ #
20
+ # You can define a scope that applies to all finders using
21
+ # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
22
+ def all(all_queries: nil)
23
+ scope = current_scope
24
+
25
+ if scope
26
+ if self == scope.model
27
+ scope.clone
28
+ else
29
+ relation.merge!(scope)
30
+ end
31
+ else
32
+ default_scoped(all_queries: all_queries)
33
+ end
34
+ end
35
+
36
+ def scope_for_association(scope = relation) # :nodoc:
37
+ if current_scope&.empty_scope?
38
+ scope
39
+ else
40
+ default_scoped(scope)
41
+ end
42
+ end
43
+
44
+ # Returns a scope for the model with default scopes.
45
+ def default_scoped(scope = relation, all_queries: nil)
46
+ build_default_scope(scope, all_queries: all_queries) || scope
47
+ end
48
+
49
+ def default_extensions # :nodoc:
50
+ if scope = scope_for_association || build_default_scope
51
+ scope.extensions
52
+ else
53
+ []
54
+ end
55
+ end
56
+
57
+ # Adds a class method for retrieving and querying objects.
58
+ # The method is intended to return an ActiveRecord::Relation
59
+ # object, which is composable with other scopes.
60
+ # If it returns +nil+ or +false+, an
61
+ # {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead.
62
+ #
63
+ # A \scope represents a narrowing of a database query, such as
64
+ # <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
65
+ #
66
+ # class Shirt < ActiveRecord::Base
67
+ # scope :red, -> { where(color: 'red') }
68
+ # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
69
+ # end
70
+ #
71
+ # The above calls to #scope define class methods <tt>Shirt.red</tt> and
72
+ # <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
73
+ # represents the query <tt>Shirt.where(color: 'red')</tt>.
74
+ #
75
+ # Note that this is simply 'syntactic sugar' for defining an actual
76
+ # class method:
77
+ #
78
+ # class Shirt < ActiveRecord::Base
79
+ # def self.red
80
+ # where(color: 'red')
81
+ # end
82
+ # end
83
+ #
84
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
85
+ # <tt>Shirt.red</tt> is not an Array but an ActiveRecord::Relation,
86
+ # which is composable with other scopes; it resembles the association object
87
+ # constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
88
+ # declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
89
+ # <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
90
+ # association objects, named \scopes act like an Array, implementing
91
+ # Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
92
+ # and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
93
+ # <tt>Shirt.red</tt> really was an array.
94
+ #
95
+ # These named \scopes are composable. For instance,
96
+ # <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
97
+ # both red and dry clean only. Nested finds and calculations also work
98
+ # with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
99
+ # returns the number of garments for which these criteria obtain.
100
+ # Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
101
+ #
102
+ # All scopes are available as class methods on the ActiveRecord::Base
103
+ # descendant upon which the \scopes were defined. But they are also
104
+ # available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
105
+ # associations. If,
106
+ #
107
+ # class Person < ActiveRecord::Base
108
+ # has_many :shirts
109
+ # end
110
+ #
111
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
112
+ # Elton's red, dry clean only shirts.
113
+ #
114
+ # \Named scopes can also have extensions, just as with
115
+ # {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations:
116
+ #
117
+ # class Shirt < ActiveRecord::Base
118
+ # scope :red, -> { where(color: 'red') } do
119
+ # def dom_id
120
+ # 'red_shirts'
121
+ # end
122
+ # end
123
+ # end
124
+ #
125
+ # Scopes can also be used while creating/building a record.
126
+ #
127
+ # class Article < ActiveRecord::Base
128
+ # scope :published, -> { where(published: true) }
129
+ # end
130
+ #
131
+ # Article.published.new.published # => true
132
+ # Article.published.create.published # => true
133
+ #
134
+ # \Class methods on your model are automatically available
135
+ # on scopes. Assuming the following setup:
136
+ #
137
+ # class Article < ActiveRecord::Base
138
+ # scope :published, -> { where(published: true) }
139
+ # scope :featured, -> { where(featured: true) }
140
+ #
141
+ # def self.latest_article
142
+ # order('published_at desc').first
143
+ # end
144
+ #
145
+ # def self.titles
146
+ # pluck(:title)
147
+ # end
148
+ # end
149
+ #
150
+ # We are able to call the methods like this:
151
+ #
152
+ # Article.published.featured.latest_article
153
+ # Article.featured.titles
154
+ def scope(name, body, &block)
155
+ unless body.respond_to?(:call)
156
+ raise ArgumentError, "The scope body needs to be callable."
157
+ end
158
+
159
+ if dangerous_class_method?(name)
160
+ raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
161
+ "on the model \"#{self.name}\", but Active Record already defined " \
162
+ "a class method with the same name."
163
+ end
164
+
165
+ if method_defined_within?(name, Relation)
166
+ raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
167
+ "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \
168
+ "an instance method with the same name."
169
+ end
170
+
171
+ extension = Module.new(&block) if block
172
+
173
+ if body.respond_to?(:to_proc)
174
+ singleton_class.define_method(name) do |*args|
175
+ scope = all._exec_scope(*args, &body)
176
+ scope = scope.extending(extension) if extension
177
+ scope
178
+ end
179
+ else
180
+ singleton_class.define_method(name) do |*args|
181
+ scope = body.call(*args) || all
182
+ scope = scope.extending(extension) if extension
183
+ scope
184
+ end
185
+ end
186
+ singleton_class.send(:ruby2_keywords, name)
187
+
188
+ generate_relation_method(name)
189
+ end
190
+
191
+ private
192
+ def singleton_method_added(name)
193
+ super
194
+ # Most Kernel extends are both singleton and instance methods so
195
+ # respond_to is a fast check, but we don't want to define methods
196
+ # only on the module (ex. Module#name)
197
+ generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module ActiveRecord
6
+ module Scoping
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include Default
11
+ include Named
12
+ end
13
+
14
+ module ClassMethods # :nodoc:
15
+ # Collects attributes from scopes that should be applied when creating
16
+ # an AR instance for the particular class this is called on.
17
+ def scope_attributes
18
+ all.scope_for_create
19
+ end
20
+
21
+ # Are there attributes associated with this scope?
22
+ def scope_attributes?
23
+ current_scope
24
+ end
25
+
26
+ def current_scope(skip_inherited_scope = false)
27
+ ScopeRegistry.current_scope(self, skip_inherited_scope)
28
+ end
29
+
30
+ def current_scope=(scope)
31
+ ScopeRegistry.set_current_scope(self, scope)
32
+ end
33
+
34
+ def global_current_scope(skip_inherited_scope = false)
35
+ ScopeRegistry.global_current_scope(self, skip_inherited_scope)
36
+ end
37
+
38
+ def global_current_scope=(scope)
39
+ ScopeRegistry.set_global_current_scope(self, scope)
40
+ end
41
+
42
+ def scope_registry
43
+ ScopeRegistry.instance
44
+ end
45
+ end
46
+
47
+ def populate_with_current_scope_attributes # :nodoc:
48
+ return unless self.class.scope_attributes?
49
+
50
+ attributes = self.class.scope_attributes
51
+ _assign_attributes(attributes) if attributes.any?
52
+ end
53
+
54
+ def initialize_internals_callback # :nodoc:
55
+ super
56
+ populate_with_current_scope_attributes
57
+ end
58
+
59
+ # This class stores the +:current_scope+ and +:ignore_default_scope+ values
60
+ # for different classes. The registry is stored as either a thread or fiber
61
+ # local depending on the application configuration.
62
+ #
63
+ # This class allows you to store and get the scope values on different
64
+ # classes and different types of scopes. For example, if you are attempting
65
+ # to get the current_scope for the +Board+ model, then you would use the
66
+ # following code:
67
+ #
68
+ # registry = ActiveRecord::Scoping::ScopeRegistry
69
+ # registry.set_current_scope(Board, some_new_scope)
70
+ #
71
+ # Now when you run:
72
+ #
73
+ # registry.current_scope(Board)
74
+ #
75
+ # You will obtain whatever was defined in +some_new_scope+.
76
+ class ScopeRegistry # :nodoc:
77
+ class << self
78
+ delegate :current_scope, :set_current_scope, :ignore_default_scope, :set_ignore_default_scope,
79
+ :global_current_scope, :set_global_current_scope, to: :instance
80
+
81
+ def instance
82
+ ActiveSupport::IsolatedExecutionState[:active_record_scope_registry] ||= new
83
+ end
84
+ end
85
+
86
+ def initialize
87
+ @current_scope = {}
88
+ @ignore_default_scope = {}
89
+ @global_current_scope = {}
90
+ end
91
+
92
+ def current_scope(model, skip_inherited_scope = false)
93
+ value_for(@current_scope, model, skip_inherited_scope)
94
+ end
95
+
96
+ def set_current_scope(model, value)
97
+ set_value_for(@current_scope, model, value)
98
+ end
99
+
100
+ def ignore_default_scope(model, skip_inherited_scope = false)
101
+ value_for(@ignore_default_scope, model, skip_inherited_scope)
102
+ end
103
+
104
+ def set_ignore_default_scope(model, value)
105
+ set_value_for(@ignore_default_scope, model, value)
106
+ end
107
+
108
+ def global_current_scope(model, skip_inherited_scope = false)
109
+ value_for(@global_current_scope, model, skip_inherited_scope)
110
+ end
111
+
112
+ def set_global_current_scope(model, value)
113
+ set_value_for(@global_current_scope, model, value)
114
+ end
115
+
116
+ private
117
+ # Obtains the value for a given +scope_type+ and +model+.
118
+ def value_for(scope_type, model, skip_inherited_scope = false)
119
+ return scope_type[model.name] if skip_inherited_scope
120
+ klass = model
121
+ base = model.base_class
122
+ while klass != base
123
+ value = scope_type[klass.name]
124
+ return value if value
125
+ klass = klass.superclass
126
+ end
127
+ scope_type[klass.name]
128
+ end
129
+
130
+ # Sets the +value+ for a given +scope_type+ and +model+.
131
+ def set_value_for(scope_type, model, value)
132
+ scope_type[model.name] = value
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module SecurePassword
5
+ extend ActiveSupport::Concern
6
+
7
+ include ActiveModel::SecurePassword
8
+
9
+ module ClassMethods
10
+ # Given a set of attributes, finds a record using the non-password
11
+ # attributes, and then authenticates that record using the password
12
+ # attributes. Returns the record if authentication succeeds; otherwise,
13
+ # returns +nil+.
14
+ #
15
+ # Regardless of whether a record is found, +authenticate_by+ will
16
+ # cryptographically digest the given password attributes. This behavior
17
+ # helps mitigate timing-based enumeration attacks, wherein an attacker can
18
+ # determine if a passworded record exists even without knowing the
19
+ # password.
20
+ #
21
+ # Raises an ArgumentError if the set of attributes doesn't contain at
22
+ # least one password and one non-password attribute.
23
+ #
24
+ # ==== Examples
25
+ #
26
+ # class User < ActiveRecord::Base
27
+ # has_secure_password
28
+ # end
29
+ #
30
+ # User.create(name: "John Doe", email: "jdoe@example.com", password: "abc123")
31
+ #
32
+ # User.authenticate_by(email: "jdoe@example.com", password: "abc123").name # => "John Doe" (in 373.4ms)
33
+ # User.authenticate_by(email: "jdoe@example.com", password: "wrong") # => nil (in 373.9ms)
34
+ # User.authenticate_by(email: "wrong@example.com", password: "abc123") # => nil (in 373.6ms)
35
+ #
36
+ # User.authenticate_by(email: "jdoe@example.com", password: nil) # => nil (no queries executed)
37
+ # User.authenticate_by(email: "jdoe@example.com", password: "") # => nil (no queries executed)
38
+ #
39
+ # User.authenticate_by(email: "jdoe@example.com") # => ArgumentError
40
+ # User.authenticate_by(password: "abc123") # => ArgumentError
41
+ def authenticate_by(attributes)
42
+ passwords, identifiers = attributes.to_h.partition do |name, value|
43
+ !has_attribute?(name) && has_attribute?("#{name}_digest")
44
+ end.map(&:to_h)
45
+
46
+ raise ArgumentError, "One or more password arguments are required" if passwords.empty?
47
+ raise ArgumentError, "One or more finder arguments are required" if identifiers.empty?
48
+
49
+ return if passwords.any? { |name, value| value.nil? || value.empty? }
50
+
51
+ if record = find_by(identifiers)
52
+ record if passwords.count { |name, value| record.public_send(:"authenticate_#{name}", value) } == passwords.size
53
+ else
54
+ new(passwords)
55
+ nil
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module SecureToken
5
+ class MinimumLengthError < StandardError; end
6
+
7
+ MINIMUM_TOKEN_LENGTH = 24
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods
12
+ # Example using #has_secure_token
13
+ #
14
+ # # Schema: User(token:string, auth_token:string)
15
+ # class User < ActiveRecord::Base
16
+ # has_secure_token
17
+ # has_secure_token :auth_token, length: 36
18
+ # end
19
+ #
20
+ # user = User.new
21
+ # user.save
22
+ # user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
23
+ # user.auth_token # => "tU9bLuZseefXQ4yQxQo8wjtBvsAfPc78os6R"
24
+ # user.regenerate_token # => true
25
+ # user.regenerate_auth_token # => true
26
+ #
27
+ # +SecureRandom::base58+ is used to generate at minimum a 24-character unique token, so collisions are highly unlikely.
28
+ #
29
+ # Note that it's still possible to generate a race condition in the database in the same way that
30
+ # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
31
+ # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
32
+ #
33
+ # === Options
34
+ #
35
+ # [:length]
36
+ # Length of the Secure Random, with a minimum of 24 characters. It will
37
+ # default to 24.
38
+ #
39
+ # [:on]
40
+ # The callback when the value is generated. When called with <tt>on:
41
+ # :initialize</tt>, the value is generated in an
42
+ # <tt>after_initialize</tt> callback, otherwise the value will be used
43
+ # in a <tt>before_</tt> callback. When not specified, +:on+ will use the value of
44
+ # <tt>config.active_record.generate_secure_token_on</tt>, which defaults to +:initialize+
45
+ # starting in \Rails 7.1.
46
+ def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH, on: ActiveRecord.generate_secure_token_on)
47
+ if length < MINIMUM_TOKEN_LENGTH
48
+ raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
49
+ end
50
+
51
+ # Load securerandom only when has_secure_token is used.
52
+ require "active_support/core_ext/securerandom"
53
+ define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
54
+ set_callback on, on == :initialize ? :after : :before do
55
+ if new_record? && !query_attribute(attribute)
56
+ send("#{attribute}=", self.class.generate_unique_secure_token(length: length))
57
+ end
58
+ end
59
+ end
60
+
61
+ def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
62
+ SecureRandom.base58(length)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ # = Active Record \Serialization
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+ include ActiveModel::Serializers::JSON
8
+
9
+ included do
10
+ self.include_root_in_json = false
11
+ end
12
+
13
+ def serializable_hash(options = nil)
14
+ if self.class._has_attribute?(self.class.inheritance_column)
15
+ options = options ? options.dup : {}
16
+
17
+ options[:except] = Array(options[:except]).map(&:to_s)
18
+ options[:except] |= Array(self.class.inheritance_column)
19
+ end
20
+
21
+ super(options)
22
+ end
23
+
24
+ private
25
+ def attribute_names_for_serialization
26
+ attribute_names
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # = Active Record Signed Id
5
+ module SignedId
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ ##
10
+ # :singleton-method:
11
+ # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
12
+ # Within \Rails, this is automatically set using the \Rails application key generator.
13
+ class_attribute :signed_id_verifier_secret, instance_writer: false
14
+ end
15
+
16
+ module RelationMethods # :nodoc:
17
+ def find_signed(...)
18
+ scoping { model.find_signed(...) }
19
+ end
20
+
21
+ def find_signed!(...)
22
+ scoping { model.find_signed!(...) }
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ # Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
28
+ # This is particularly useful for things like password reset or email verification, where you want
29
+ # the bearer of the signed id to be able to interact with the underlying record, but usually only within
30
+ # a certain time period.
31
+ #
32
+ # You set the time period that the signed id is valid for during generation, using the instance method
33
+ # <tt>signed_id(expires_in: 15.minutes)</tt>. If the time has elapsed before a signed find is attempted,
34
+ # the signed id will no longer be valid, and nil is returned.
35
+ #
36
+ # It's possible to further restrict the use of a signed id with a purpose. This helps when you have a
37
+ # general base model, like a User, which might have signed ids for several things, like password reset
38
+ # or email verification. The purpose that was set during generation must match the purpose set when
39
+ # finding. If there's a mismatch, nil is again returned.
40
+ #
41
+ # ==== Examples
42
+ #
43
+ # signed_id = User.first.signed_id expires_in: 15.minutes, purpose: :password_reset
44
+ #
45
+ # User.find_signed signed_id # => nil, since the purpose does not match
46
+ #
47
+ # travel 16.minutes
48
+ # User.find_signed signed_id, purpose: :password_reset # => nil, since the signed id has expired
49
+ #
50
+ # travel_back
51
+ # User.find_signed signed_id, purpose: :password_reset # => User.first
52
+ def find_signed(signed_id, purpose: nil)
53
+ raise UnknownPrimaryKey.new(self) if primary_key.nil?
54
+
55
+ if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
56
+ find_by primary_key => id
57
+ end
58
+ end
59
+
60
+ # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
61
+ # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
62
+ # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
63
+ # the valid signed id can't find a record.
64
+ #
65
+ # === Examples
66
+ #
67
+ # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
68
+ #
69
+ # signed_id = User.first.signed_id
70
+ # User.first.destroy
71
+ # User.find_signed! signed_id # => ActiveRecord::RecordNotFound
72
+ def find_signed!(signed_id, purpose: nil)
73
+ if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
74
+ find(id)
75
+ end
76
+ end
77
+
78
+ # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
79
+ # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
80
+ # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
81
+ def signed_id_verifier
82
+ @signed_id_verifier ||= begin
83
+ secret = signed_id_verifier_secret
84
+ secret = secret.call if secret.respond_to?(:call)
85
+
86
+ if secret.nil?
87
+ raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
88
+ else
89
+ ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
90
+ end
91
+ end
92
+ end
93
+
94
+ # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
95
+ # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
96
+ # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
97
+ def signed_id_verifier=(verifier)
98
+ @signed_id_verifier = verifier
99
+ end
100
+
101
+ # :nodoc:
102
+ def combine_signed_id_purposes(purpose)
103
+ [ base_class.name.underscore, purpose.to_s ].compact_blank.join("/")
104
+ end
105
+ end
106
+
107
+
108
+ # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
109
+ #
110
+ # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
111
+ # However, as with any message signed with a +ActiveSupport::MessageVerifier+,
112
+ # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
113
+ # It's just encoded and protected against tampering.
114
+ #
115
+ # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
116
+ # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
117
+ # when passed to +find_signed+ (or raise with +find_signed!+).
118
+ #
119
+ # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
120
+ # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
121
+ # record. If a purpose is set, this too must match.
122
+ #
123
+ # If you accidentally let a signed id out in the wild that you wish to retract sooner than its expiration date
124
+ # (or maybe you forgot to set an expiration date while meaning to!), you can use the purpose to essentially
125
+ # version the signed_id, like so:
126
+ #
127
+ # user.signed_id purpose: :v2
128
+ #
129
+ # And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not
130
+ # created with the purpose will no longer find the record.
131
+ def signed_id(expires_in: nil, expires_at: nil, purpose: nil)
132
+ raise ArgumentError, "Cannot get a signed_id for a new record" if new_record?
133
+
134
+ self.class.signed_id_verifier.generate id, expires_in: expires_in, expires_at: expires_at, purpose: self.class.combine_signed_id_purposes(purpose)
135
+ end
136
+ end
137
+ end