activerecord 6.0.0 → 7.2.3

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module EncryptedFixtures
6
+ def initialize(fixture, model_class)
7
+ @clean_values = {}
8
+ encrypt_fixture_data(fixture, model_class)
9
+ process_preserved_original_columns(fixture, model_class)
10
+ super
11
+ end
12
+
13
+ private
14
+ def encrypt_fixture_data(fixture, model_class)
15
+ model_class&.encrypted_attributes&.each do |attribute_name|
16
+ if clean_value = fixture[attribute_name.to_s]
17
+ @clean_values[attribute_name.to_s] = clean_value
18
+
19
+ type = model_class.type_for_attribute(attribute_name)
20
+ encrypted_value = type.serialize(clean_value)
21
+ fixture[attribute_name.to_s] = encrypted_value
22
+ end
23
+ end
24
+ end
25
+
26
+ def process_preserved_original_columns(fixture, model_class)
27
+ model_class&.encrypted_attributes&.each do |attribute_name|
28
+ if source_attribute_name = model_class.source_attribute_from_preserved_attribute(attribute_name)
29
+ clean_value = @clean_values[source_attribute_name.to_s]
30
+ type = model_class.type_for_attribute(attribute_name)
31
+ encrypted_value = type.serialize(clean_value)
32
+ fixture[attribute_name.to_s] = encrypted_value
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An encryptor that can encrypt data but can't decrypt it.
6
+ class EncryptingOnlyEncryptor < Encryptor
7
+ def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
8
+ encrypted_text
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "zlib"
5
+ require "active_support/core_ext/numeric"
6
+
7
+ module ActiveRecord
8
+ module Encryption
9
+ # An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType
10
+ # uses for encrypting and decrypting attribute values.
11
+ #
12
+ # It interacts with a KeyProvider for getting the keys, and delegate to
13
+ # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
+ class Encryptor
15
+ # ==== Options
16
+ #
17
+ # [+:compress+]
18
+ # Boolean indicating whether records should be compressed before
19
+ # encryption. Defaults to +true+.
20
+ def initialize(compress: true)
21
+ @compress = compress
22
+ end
23
+
24
+ # Encrypts +clean_text+ and returns the encrypted result.
25
+ #
26
+ # Internally, it will:
27
+ #
28
+ # 1. Create a new ActiveRecord::Encryption::Message.
29
+ # 2. Compress and encrypt +clean_text+ as the message payload.
30
+ # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+
31
+ # (+ActiveRecord::Encryption::SafeMarshal+ by default).
32
+ # 4. Encode the result with Base64.
33
+ #
34
+ # ==== Options
35
+ #
36
+ # [+:key_provider+]
37
+ # Key provider to use for the encryption operation. It will default to
38
+ # +ActiveRecord::Encryption.key_provider+ when not provided.
39
+ #
40
+ # [+:cipher_options+]
41
+ # Cipher-specific options that will be passed to the Cipher configured in
42
+ # +ActiveRecord::Encryption.cipher+.
43
+ def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
44
+ clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
45
+
46
+ validate_payload_type(clear_text)
47
+ serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
48
+ end
49
+
50
+ # Decrypts an +encrypted_text+ and returns the result as clean text.
51
+ #
52
+ # ==== Options
53
+ #
54
+ # [+:key_provider+]
55
+ # Key provider to use for the encryption operation. It will default to
56
+ # +ActiveRecord::Encryption.key_provider+ when not provided.
57
+ #
58
+ # [+:cipher_options+]
59
+ # Cipher-specific options that will be passed to the Cipher configured in
60
+ # +ActiveRecord::Encryption.cipher+.
61
+ def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
62
+ message = deserialize_message(encrypted_text)
63
+ keys = key_provider.decryption_keys(message)
64
+ raise Errors::Decryption unless keys.present?
65
+ uncompress_if_needed(cipher.decrypt(message, key: keys.collect(&:secret), **cipher_options), message.headers.compressed)
66
+ rescue *(ENCODING_ERRORS + DECRYPT_ERRORS)
67
+ raise Errors::Decryption
68
+ end
69
+
70
+ # Returns whether the text is encrypted or not.
71
+ def encrypted?(text)
72
+ deserialize_message(text)
73
+ true
74
+ rescue Errors::Encoding, *DECRYPT_ERRORS
75
+ false
76
+ end
77
+
78
+ def binary?
79
+ serializer.binary?
80
+ end
81
+
82
+ private
83
+ DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
84
+ ENCODING_ERRORS = [EncodingError, Errors::Encoding]
85
+ THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
86
+
87
+ def default_key_provider
88
+ ActiveRecord::Encryption.key_provider
89
+ end
90
+
91
+ def validate_payload_type(clear_text)
92
+ unless clear_text.is_a?(String)
93
+ raise ActiveRecord::Encryption::Errors::ForbiddenClass, "The encryptor can only encrypt string values (#{clear_text.class})"
94
+ end
95
+ end
96
+
97
+ def cipher
98
+ ActiveRecord::Encryption.cipher
99
+ end
100
+
101
+ def build_encrypted_message(clear_text, key_provider:, cipher_options:)
102
+ key = key_provider.encryption_key
103
+
104
+ clear_text, was_compressed = compress_if_worth_it(clear_text)
105
+ cipher.encrypt(clear_text, key: key.secret, **cipher_options).tap do |message|
106
+ message.headers.add(key.public_tags)
107
+ message.headers.compressed = true if was_compressed
108
+ end
109
+ end
110
+
111
+ def serialize_message(message)
112
+ serializer.dump(message)
113
+ end
114
+
115
+ def deserialize_message(message)
116
+ serializer.load message
117
+ rescue ArgumentError, TypeError, Errors::ForbiddenClass
118
+ raise Errors::Encoding
119
+ end
120
+
121
+ def serializer
122
+ ActiveRecord::Encryption.message_serializer
123
+ end
124
+
125
+ # Under certain threshold, ZIP compression is actually worse that not compressing
126
+ def compress_if_worth_it(string)
127
+ if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
128
+ [compress(string), true]
129
+ else
130
+ [string, false]
131
+ end
132
+ end
133
+
134
+ def compress?
135
+ @compress
136
+ end
137
+
138
+ def compress(data)
139
+ Zlib::Deflate.deflate(data).tap do |compressed_data|
140
+ compressed_data.force_encoding(data.encoding)
141
+ end
142
+ end
143
+
144
+ def uncompress_if_needed(data, compressed)
145
+ if compressed
146
+ uncompress(data)
147
+ else
148
+ data
149
+ end
150
+ end
151
+
152
+ def uncompress(data)
153
+ Zlib::Inflate.inflate(data).tap do |uncompressed_data|
154
+ uncompressed_data.force_encoding(data.encoding)
155
+ end
156
+ end
157
+
158
+ def force_encoding_if_needed(value)
159
+ if forced_encoding_for_deterministic_encryption && value && value.encoding != forced_encoding_for_deterministic_encryption
160
+ value.encode(forced_encoding_for_deterministic_encryption, invalid: :replace, undef: :replace)
161
+ else
162
+ value
163
+ end
164
+ end
165
+
166
+ def forced_encoding_for_deterministic_encryption
167
+ ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Implements a simple envelope encryption approach where:
6
+ #
7
+ # * It generates a random data-encryption key for each encryption operation.
8
+ # * It stores the generated key along with the encrypted payload. It encrypts this key
9
+ # with the master key provided in the +active_record_encryption.primary_key+ credential.
10
+ #
11
+ # This provider can work with multiple master keys. It will use the last one for encrypting.
12
+ #
13
+ # When +config.active_record.encryption.store_key_references+ is true, it will also store a reference to
14
+ # the specific master key that was used to encrypt the data-encryption key. When not set,
15
+ # it will try all the configured master keys looking for the right one, in order to
16
+ # return the right decryption key.
17
+ class EnvelopeEncryptionKeyProvider
18
+ def encryption_key
19
+ random_secret = generate_random_secret
20
+ ActiveRecord::Encryption::Key.new(random_secret).tap do |key|
21
+ key.public_tags.encrypted_data_key = encrypt_data_key(random_secret)
22
+ key.public_tags.encrypted_data_key_id = active_primary_key.id if ActiveRecord::Encryption.config.store_key_references
23
+ end
24
+ end
25
+
26
+ def decryption_keys(encrypted_message)
27
+ secret = decrypt_data_key(encrypted_message)
28
+ secret ? [ActiveRecord::Encryption::Key.new(secret)] : []
29
+ end
30
+
31
+ def active_primary_key
32
+ @active_primary_key ||= primary_key_provider.encryption_key
33
+ end
34
+
35
+ private
36
+ def encrypt_data_key(random_secret)
37
+ ActiveRecord::Encryption.cipher.encrypt(random_secret, key: active_primary_key.secret)
38
+ end
39
+
40
+ def decrypt_data_key(encrypted_message)
41
+ encrypted_data_key = encrypted_message.headers.encrypted_data_key
42
+ key = primary_key_provider.decryption_keys(encrypted_message)&.collect(&:secret)
43
+ ActiveRecord::Encryption.cipher.decrypt encrypted_data_key, key: key if key
44
+ end
45
+
46
+ def primary_key_provider
47
+ @primary_key_provider ||= DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
48
+ end
49
+
50
+ def generate_random_secret
51
+ ActiveRecord::Encryption.key_generator.generate_random_key
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module Errors
6
+ class Base < StandardError; end
7
+ class Encoding < Base; end
8
+ class Decryption < Base; end
9
+ class Encryption < Base; end
10
+ class Configuration < Base; end
11
+ class ForbiddenClass < Base; end
12
+ class EncryptedContentIntegrity < Base; end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
6
+ #
7
+ # Active Record \Encryption supports querying the db using deterministic attributes. For example:
8
+ #
9
+ # Contact.find_by(email_address: "jorge@hey.com")
10
+ #
11
+ # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
12
+ # a problem while the data is being encrypted. This won't work. During that time, you need these
13
+ # queries to be:
14
+ #
15
+ # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
16
+ #
17
+ # This patches ActiveRecord to support this automatically. It addresses both:
18
+ #
19
+ # * ActiveRecord::Base - Used in <tt>Contact.find_by_email_address(...)</tt>
20
+ # * ActiveRecord::Relation - Used in <tt>Contact.internal.find_by_email_address(...)</tt>
21
+ #
22
+ # This module is included if `config.active_record.encryption.extend_queries` is `true`.
23
+ module ExtendedDeterministicQueries
24
+ def self.install_support
25
+ # ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does
26
+ # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
27
+ # as it's invoked (so that the proper prepared statement is cached).
28
+ ActiveRecord::Relation.prepend(RelationQueries)
29
+ ActiveRecord::Base.include(CoreQueries)
30
+ ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
31
+ end
32
+
33
+ # When modifying this file run performance tests in
34
+ # +activerecord/test/cases/encryption/performance/extended_deterministic_queries_performance_test.rb+
35
+ # to make sure performance overhead is acceptable.
36
+ #
37
+ # @TODO We will extend this to support previous "encryption context" versions in future iterations
38
+ # @TODO Experimental. Support for every kind of query is pending
39
+ # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
40
+
41
+ module EncryptedQuery # :nodoc:
42
+ class << self
43
+ def process_arguments(owner, args, check_for_additional_values)
44
+ return args if owner.deterministic_encrypted_attributes&.empty?
45
+
46
+ if args.is_a?(Array) && (options = args.first).is_a?(Hash)
47
+ options = options.transform_keys do |key|
48
+ if key.is_a?(Array)
49
+ key.map(&:to_s)
50
+ else
51
+ key.to_s
52
+ end
53
+ end
54
+ args[0] = options
55
+
56
+ owner.deterministic_encrypted_attributes&.each do |attribute_name|
57
+ attribute_name = attribute_name.to_s
58
+ type = owner.type_for_attribute(attribute_name)
59
+ if !type.previous_types.empty? && value = options[attribute_name]
60
+ options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
61
+ end
62
+ end
63
+ end
64
+
65
+ args
66
+ end
67
+
68
+ private
69
+ def process_encrypted_query_argument(value, check_for_additional_values, type)
70
+ return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
71
+
72
+ case value
73
+ when String, Array
74
+ list = Array(value)
75
+ list + list.flat_map do |each_value|
76
+ if check_for_additional_values && each_value.is_a?(AdditionalValue)
77
+ each_value
78
+ else
79
+ additional_values_for(each_value, type)
80
+ end
81
+ end
82
+ else
83
+ value
84
+ end
85
+ end
86
+
87
+ def additional_values_for(value, type)
88
+ type.previous_types.collect do |additional_type|
89
+ AdditionalValue.new(value, additional_type)
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ module RelationQueries
96
+ def where(*args)
97
+ super(*EncryptedQuery.process_arguments(self, args, true))
98
+ end
99
+
100
+ def exists?(*args)
101
+ super(*EncryptedQuery.process_arguments(self, args, true))
102
+ end
103
+
104
+ def scope_for_create
105
+ return super unless klass.deterministic_encrypted_attributes&.any?
106
+
107
+ scope_attributes = super
108
+ wheres = where_values_hash
109
+
110
+ klass.deterministic_encrypted_attributes.each do |attribute_name|
111
+ attribute_name = attribute_name.to_s
112
+ values = wheres[attribute_name]
113
+ if values.is_a?(Array) && values[1..].all?(AdditionalValue)
114
+ scope_attributes[attribute_name] = values.first
115
+ end
116
+ end
117
+
118
+ scope_attributes
119
+ end
120
+ end
121
+
122
+ module CoreQueries
123
+ extend ActiveSupport::Concern
124
+
125
+ class_methods do
126
+ def find_by(*args)
127
+ super(*EncryptedQuery.process_arguments(self, args, false))
128
+ end
129
+ end
130
+ end
131
+
132
+ class AdditionalValue
133
+ attr_reader :value, :type
134
+
135
+ def initialize(value, type)
136
+ @type = type
137
+ @value = process(value)
138
+ end
139
+
140
+ private
141
+ def process(value)
142
+ type.serialize(value)
143
+ end
144
+ end
145
+
146
+ module ExtendedEncryptableType
147
+ def serialize(data)
148
+ if data.is_a?(AdditionalValue)
149
+ data.value
150
+ else
151
+ super
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ module ExtendedDeterministicUniquenessValidator
6
+ def self.install_support
7
+ ActiveRecord::Validations::UniquenessValidator.prepend(EncryptedUniquenessValidator)
8
+ end
9
+
10
+ module EncryptedUniquenessValidator
11
+ def validate_each(record, attribute, value)
12
+ super(record, attribute, value)
13
+
14
+ klass = record.class
15
+ if klass.deterministic_encrypted_attributes&.include?(attribute)
16
+ encrypted_type = klass.type_for_attribute(attribute)
17
+ encrypted_type.previous_types.each do |type|
18
+ encrypted_value = type.serialize(value)
19
+ ActiveRecord::Encryption.without_encryption do
20
+ super(record, attribute, encrypted_value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A key is a container for a given +secret+
6
+ #
7
+ # Optionally, it can include +public_tags+. These tags are meant to be stored
8
+ # in clean (public) and can be used, for example, to include information that
9
+ # references the key for a future retrieval operation.
10
+ class Key
11
+ attr_reader :secret, :public_tags
12
+
13
+ def initialize(secret)
14
+ @secret = secret
15
+ @public_tags = Properties.new
16
+ end
17
+
18
+ def self.derive_from(password)
19
+ secret = ActiveRecord::Encryption.key_generator.derive_key_from(password)
20
+ ActiveRecord::Encryption::Key.new(secret)
21
+ end
22
+
23
+ def id
24
+ Digest::SHA1.hexdigest(secret).first(4)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module ActiveRecord
6
+ module Encryption
7
+ # Utility for generating and deriving random keys.
8
+ class KeyGenerator
9
+ attr_reader :hash_digest_class
10
+
11
+ def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
12
+ @hash_digest_class = hash_digest_class
13
+ end
14
+
15
+ # Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
16
+ def generate_random_key(length: key_length)
17
+ SecureRandom.random_bytes(length)
18
+ end
19
+
20
+ # Returns a random key in hexadecimal format. The key will have a size in bytes of +:length+ (configured +Cipher+'s
21
+ # length by default)
22
+ #
23
+ # Hexadecimal format is handy for representing keys as printable text. To maximize the space of characters used, it is
24
+ # good practice including not printable characters. Hexadecimal format ensures that generated keys are representable with
25
+ # plain text
26
+ #
27
+ # To convert back to the original string with the desired length:
28
+ #
29
+ # [ value ].pack("H*")
30
+ def generate_random_hex_key(length: key_length)
31
+ generate_random_key(length: length).unpack("H*")[0]
32
+ end
33
+
34
+ # Derives a key from the given password. The key will have a size in bytes of +:length+ (configured +Cipher+'s length
35
+ # by default)
36
+ #
37
+ # The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
38
+ def derive_key_from(password, length: key_length)
39
+ ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
40
+ .generate_key(key_derivation_salt, length)
41
+ end
42
+
43
+ private
44
+ def key_derivation_salt
45
+ @key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
46
+ end
47
+
48
+ def key_length
49
+ @key_length ||= ActiveRecord::Encryption.cipher.key_length
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A +KeyProvider+ serves keys:
6
+ #
7
+ # * An encryption key
8
+ # * A list of potential decryption keys. Serving multiple decryption keys supports rotation-schemes
9
+ # where new keys are added but old keys need to continue working
10
+ class KeyProvider
11
+ def initialize(keys)
12
+ @keys = Array(keys)
13
+ end
14
+
15
+ # Returns the last key in the list as the active key to perform encryptions
16
+ #
17
+ # When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include
18
+ # a public tag referencing the key itself. That key will be stored in the public
19
+ # headers of the encrypted message
20
+ def encryption_key
21
+ @encryption_key ||= @keys.last.tap do |key|
22
+ key.public_tags.encrypted_data_key_id = key.id if ActiveRecord::Encryption.config.store_key_references
23
+ end
24
+
25
+ @encryption_key
26
+ end
27
+
28
+ # Returns the list of decryption keys
29
+ #
30
+ # When the message holds a reference to its encryption key, it will return an array
31
+ # with that key. If not, it will return the list of keys.
32
+ def decryption_keys(encrypted_message)
33
+ if encrypted_message.headers.encrypted_data_key_id
34
+ keys_grouped_by_id[encrypted_message.headers.encrypted_data_key_id]
35
+ else
36
+ @keys
37
+ end
38
+ end
39
+
40
+ private
41
+ def keys_grouped_by_id
42
+ @keys_grouped_by_id ||= @keys.group_by(&:id)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A message defines the structure of the data we store in encrypted attributes. It contains:
6
+ #
7
+ # * An encrypted payload
8
+ # * A list of unencrypted headers
9
+ #
10
+ # See Encryptor#encrypt
11
+ class Message
12
+ attr_accessor :payload, :headers
13
+
14
+ def initialize(payload: nil, headers: {})
15
+ validate_payload_type(payload)
16
+
17
+ @payload = payload
18
+ @headers = Properties.new(headers)
19
+ end
20
+
21
+ def ==(other_message)
22
+ payload == other_message.payload && headers == other_message.headers
23
+ end
24
+
25
+ private
26
+ def validate_payload_type(payload)
27
+ unless payload.is_a?(String) || payload.nil?
28
+ raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Only string payloads allowed"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end