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,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module ActiveRecord
6
+ module Encryption
7
+ # Container of configuration options
8
+ class Config
9
+ attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
10
+ :support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
11
+ :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
12
+
13
+ def initialize
14
+ set_defaults
15
+ end
16
+
17
+ # Configure previous encryption schemes.
18
+ #
19
+ # config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
20
+ def previous=(previous_schemes_properties)
21
+ previous_schemes_properties.each do |properties|
22
+ add_previous_scheme(**properties)
23
+ end
24
+ end
25
+
26
+ def support_sha1_for_non_deterministic_encryption=(value)
27
+ if value && has_primary_key?
28
+ sha1_key_generator = ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: OpenSSL::Digest::SHA1)
29
+ sha1_key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key, key_generator: sha1_key_generator)
30
+ add_previous_scheme key_provider: sha1_key_provider
31
+ end
32
+ end
33
+
34
+ %w(key_derivation_salt primary_key deterministic_key).each do |key|
35
+ silence_redefinition_of_method "has_#{key}?"
36
+ define_method("has_#{key}?") do
37
+ instance_variable_get(:"@#{key}").presence
38
+ end
39
+
40
+ silence_redefinition_of_method key
41
+ define_method(key) do
42
+ public_send("has_#{key}?") or
43
+ raise Errors::Configuration, "Missing Active Record encryption credential: active_record_encryption.#{key}"
44
+ end
45
+ end
46
+
47
+ private
48
+ def set_defaults
49
+ self.store_key_references = false
50
+ self.support_unencrypted_data = false
51
+ self.encrypt_fixtures = false
52
+ self.validate_column_size = true
53
+ self.add_to_filter_parameters = true
54
+ self.excluded_from_filter_parameters = []
55
+ self.previous_schemes = []
56
+ self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
57
+ self.hash_digest_class = OpenSSL::Digest::SHA1
58
+
59
+ # TODO: Setting to false for now as the implementation is a bit experimental
60
+ self.extend_queries = false
61
+ end
62
+
63
+ def add_previous_scheme(**properties)
64
+ previous_schemes << ActiveRecord::Encryption::Scheme.new(**properties)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Configuration API for ActiveRecord::Encryption
6
+ module Configurable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ mattr_reader :config, default: Config.new
11
+ mattr_accessor :encrypted_attribute_declaration_listeners
12
+ end
13
+
14
+ class_methods do
15
+ # Expose getters for context properties
16
+ Context::PROPERTIES.each do |name|
17
+ delegate name, to: :context
18
+ end
19
+
20
+ def configure(primary_key: nil, deterministic_key: nil, key_derivation_salt: nil, **properties) # :nodoc:
21
+ config.primary_key = primary_key
22
+ config.deterministic_key = deterministic_key
23
+ config.key_derivation_salt = key_derivation_salt
24
+
25
+ # Set the default for this property here instead of in +Config#set_defaults+ as this needs
26
+ # to happen *after* the keys have been set.
27
+ properties[:support_sha1_for_non_deterministic_encryption] = true if properties[:support_sha1_for_non_deterministic_encryption].nil?
28
+
29
+ properties.each do |name, value|
30
+ ActiveRecord::Encryption.config.send "#{name}=", value if ActiveRecord::Encryption.config.respond_to?("#{name}=")
31
+ end
32
+
33
+ ActiveRecord::Encryption.reset_default_context
34
+
35
+ properties.each do |name, value|
36
+ ActiveRecord::Encryption.context.send "#{name}=", value if ActiveRecord::Encryption.context.respond_to?("#{name}=")
37
+ end
38
+ end
39
+
40
+ # Register callback to be invoked when an encrypted attribute is declared.
41
+ #
42
+ # === Example
43
+ #
44
+ # ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name|
45
+ # ...
46
+ # end
47
+ def on_encrypted_attribute_declared(&block)
48
+ self.encrypted_attribute_declaration_listeners ||= Concurrent::Array.new
49
+ self.encrypted_attribute_declaration_listeners << block
50
+ end
51
+
52
+ def encrypted_attribute_was_declared(klass, name) # :nodoc:
53
+ self.encrypted_attribute_declaration_listeners&.each do |block|
54
+ block.call(klass, name)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An encryption context configures the different entities used to perform encryption:
6
+ #
7
+ # * A key provider
8
+ # * A key generator
9
+ # * An encryptor, the facade to encrypt data
10
+ # * A cipher, the encryption algorithm
11
+ # * A message serializer
12
+ class Context
13
+ PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ]
14
+
15
+ attr_accessor(*PROPERTIES)
16
+
17
+ def initialize
18
+ set_defaults
19
+ end
20
+
21
+ alias frozen_encryption? frozen_encryption
22
+
23
+ silence_redefinition_of_method :key_provider
24
+ def key_provider
25
+ @key_provider ||= build_default_key_provider
26
+ end
27
+
28
+ private
29
+ def set_defaults
30
+ self.frozen_encryption = false
31
+ self.key_generator = ActiveRecord::Encryption::KeyGenerator.new
32
+ self.cipher = ActiveRecord::Encryption::Cipher.new
33
+ self.encryptor = ActiveRecord::Encryption::Encryptor.new
34
+ self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new
35
+ end
36
+
37
+ def build_default_key_provider
38
+ ActiveRecord::Encryption::DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # ActiveRecord::Encryption uses encryption contexts to configure the different entities used to
6
+ # encrypt/decrypt at a given moment in time.
7
+ #
8
+ # By default, the library uses a default encryption context. This is the Context that gets configured
9
+ # initially via +config.active_record.encryption+ options. Library users can define nested encryption contexts
10
+ # when running blocks of code.
11
+ #
12
+ # See Context.
13
+ module Contexts
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ mattr_accessor :default_context, default: Context.new
18
+ thread_mattr_accessor :custom_contexts
19
+ end
20
+
21
+ class_methods do
22
+ # Configures a custom encryption context to use when running the provided block of code.
23
+ #
24
+ # It supports overriding all the properties defined in +Context+.
25
+ #
26
+ # Example:
27
+ #
28
+ # ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do
29
+ # ...
30
+ # end
31
+ #
32
+ # Encryption contexts can be nested.
33
+ def with_encryption_context(properties)
34
+ self.custom_contexts ||= []
35
+ self.custom_contexts << default_context.dup
36
+ properties.each do |key, value|
37
+ self.current_custom_context.send("#{key}=", value)
38
+ end
39
+
40
+ yield
41
+ ensure
42
+ self.custom_contexts.pop
43
+ end
44
+
45
+ # Runs the provided block in an encryption context where encryption is disabled:
46
+ #
47
+ # * Reading encrypted content will return its ciphertexts.
48
+ # * Writing encrypted content will write its clear text.
49
+ def without_encryption(&block)
50
+ with_encryption_context encryptor: ActiveRecord::Encryption::NullEncryptor.new, &block
51
+ end
52
+
53
+ # Runs the provided block in an encryption context where:
54
+ #
55
+ # * Reading encrypted content will return its ciphertext.
56
+ # * Writing encrypted content will fail.
57
+ def protecting_encrypted_data(&block)
58
+ with_encryption_context encryptor: ActiveRecord::Encryption::EncryptingOnlyEncryptor.new, frozen_encryption: true, &block
59
+ end
60
+
61
+ # Returns the current context. By default it will return the current context.
62
+ def context
63
+ self.current_custom_context || self.default_context
64
+ end
65
+
66
+ def current_custom_context
67
+ self.custom_contexts&.last
68
+ end
69
+
70
+ def reset_default_context
71
+ self.default_context = Context.new
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A KeyProvider that derives keys from passwords.
6
+ class DerivedSecretKeyProvider < KeyProvider
7
+ def initialize(passwords, key_generator: ActiveRecord::Encryption.key_generator)
8
+ super(Array(passwords).collect { |password| derive_key_from(password, using: key_generator) })
9
+ end
10
+
11
+ private
12
+ def derive_key_from(password, using: key_generator)
13
+ secret = using.derive_key_from(password)
14
+ ActiveRecord::Encryption::Key.new(secret)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A KeyProvider that derives keys from passwords.
6
+ class DeterministicKeyProvider < DerivedSecretKeyProvider
7
+ def initialize(password)
8
+ passwords = Array(password)
9
+ raise ActiveRecord::Encryption::Errors::Configuration, "Deterministic encryption keys can't be rotated" if passwords.length > 1
10
+ super(passwords)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # This is the concern mixed in Active Record models to make them encryptable. It adds the +encrypts+
6
+ # attribute declaration, as well as the API to encrypt and decrypt records.
7
+ module EncryptableRecord
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_attribute :encrypted_attributes
12
+
13
+ validate :cant_modify_encrypted_attributes_when_frozen, if: -> { has_encrypted_attributes? && ActiveRecord::Encryption.context.frozen_encryption? }
14
+ end
15
+
16
+ class_methods do
17
+ # Encrypts the +name+ attribute.
18
+ #
19
+ # ==== Options
20
+ #
21
+ # * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
22
+ # +ActiveRecord::Encryption.key_provider+.
23
+ # * <tt>:key</tt> - A password to derive the key from. It's a shorthand for a +:key_provider+ that
24
+ # serves derivated keys. Both options can't be used at the same time.
25
+ # * <tt>:deterministic</tt> - By default, encryption is not deterministic. It will use a random
26
+ # initialization vector for each encryption operation. This means that encrypting the same content
27
+ # with the same key twice will generate different ciphertexts. When set to +true+, it will generate the
28
+ # initialization vector based on the encrypted content. This means that the same content will generate
29
+ # the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
30
+ # will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
31
+ # <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
32
+ # data.
33
+ # * <tt>:support_unencrypted_data</tt> - If `config.active_record.encryption.support_unencrypted_data` is +true+,
34
+ # you can set this to +false+ to opt out of unencrypted data support for this attribute. This is useful for
35
+ # scenarios where you encrypt one column, and want to disable support for unencrypted data without having to tweak
36
+ # the global setting.
37
+ # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
38
+ # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
39
+ # in preserving it.
40
+ # * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
41
+ # designated column +original_<name>+. When reading the encrypted content, the version with the original case is
42
+ # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
43
+ # is true.
44
+ # * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
45
+ # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
46
+ # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
47
+ # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
48
+ # encryption is used, they will be used to generate additional ciphertexts to check in the queries.
49
+ def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
50
+ self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
51
+
52
+ names.each do |name|
53
+ encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
54
+ end
55
+ end
56
+
57
+ # Returns the list of deterministic encryptable attributes in the model class.
58
+ def deterministic_encrypted_attributes
59
+ @deterministic_encrypted_attributes ||= encrypted_attributes&.find_all do |attribute_name|
60
+ type_for_attribute(attribute_name).deterministic?
61
+ end
62
+ end
63
+
64
+ # Given a attribute name, it returns the name of the source attribute when it's a preserved one.
65
+ def source_attribute_from_preserved_attribute(attribute_name)
66
+ attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if attribute_name.start_with?(ORIGINAL_ATTRIBUTE_PREFIX)
67
+ end
68
+
69
+ private
70
+ def scheme_for(key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
71
+ ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
72
+ support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
73
+ scheme.previous_schemes = global_previous_schemes_for(scheme) +
74
+ Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
75
+ end
76
+ end
77
+
78
+ def global_previous_schemes_for(scheme)
79
+ ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme|
80
+ scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme)
81
+ end
82
+ end
83
+
84
+ def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
85
+ encrypted_attributes << name.to_sym
86
+
87
+ decorate_attributes([name]) do |name, cast_type|
88
+ scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
89
+ downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
90
+
91
+ ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type, default: columns_hash[name.to_s]&.default)
92
+ end
93
+
94
+ preserve_original_encrypted(name) if ignore_case
95
+ ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
96
+ end
97
+
98
+ def preserve_original_encrypted(name)
99
+ original_attribute_name = "#{ORIGINAL_ATTRIBUTE_PREFIX}#{name}".to_sym
100
+
101
+ if !ActiveRecord::Encryption.config.support_unencrypted_data && !column_names.include?(original_attribute_name.to_s)
102
+ raise Errors::Configuration, "To use :ignore_case for '#{name}' you must create an additional column named '#{original_attribute_name}'"
103
+ end
104
+
105
+ encrypts original_attribute_name
106
+ override_accessors_to_preserve_original name, original_attribute_name
107
+ end
108
+
109
+ def override_accessors_to_preserve_original(name, original_attribute_name)
110
+ include(Module.new do
111
+ define_method name do
112
+ if ((value = super()) && encrypted_attribute?(name)) || !ActiveRecord::Encryption.config.support_unencrypted_data
113
+ send(original_attribute_name)
114
+ else
115
+ value
116
+ end
117
+ end
118
+
119
+ define_method "#{name}=" do |value|
120
+ self.send "#{original_attribute_name}=", value
121
+ super(value)
122
+ end
123
+ end)
124
+ end
125
+
126
+ def load_schema! # :nodoc:
127
+ super
128
+
129
+ add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
130
+ end
131
+
132
+ def add_length_validation_for_encrypted_columns
133
+ encrypted_attributes&.each do |attribute_name|
134
+ validate_column_size attribute_name
135
+ end
136
+ end
137
+
138
+ def validate_column_size(attribute_name)
139
+ if limit = columns_hash[attribute_name.to_s]&.limit
140
+ validates_length_of attribute_name, maximum: limit
141
+ end
142
+ end
143
+ end
144
+
145
+ # Returns whether a given attribute is encrypted or not.
146
+ def encrypted_attribute?(attribute_name)
147
+ name = attribute_name.to_s
148
+ name = self.class.attribute_aliases[name] || name
149
+
150
+ return false unless self.class.encrypted_attributes&.include? name.to_sym
151
+
152
+ type = type_for_attribute(name)
153
+ type.encrypted? read_attribute_before_type_cast(name)
154
+ end
155
+
156
+ # Returns the ciphertext for +attribute_name+.
157
+ def ciphertext_for(attribute_name)
158
+ if encrypted_attribute?(attribute_name)
159
+ read_attribute_before_type_cast(attribute_name)
160
+ else
161
+ read_attribute_for_database(attribute_name)
162
+ end
163
+ end
164
+
165
+ # Encrypts all the encryptable attributes and saves the changes.
166
+ def encrypt
167
+ encrypt_attributes if has_encrypted_attributes?
168
+ end
169
+
170
+ # Decrypts all the encryptable attributes and saves the changes.
171
+ def decrypt
172
+ decrypt_attributes if has_encrypted_attributes?
173
+ end
174
+
175
+ private
176
+ ORIGINAL_ATTRIBUTE_PREFIX = "original_"
177
+
178
+ def _create_record(attribute_names = self.attribute_names)
179
+ if has_encrypted_attributes?
180
+ # Always persist encrypted attributes, because an attribute might be
181
+ # encrypting a column default value.
182
+ attribute_names |= self.class.encrypted_attributes.map(&:to_s)
183
+ end
184
+ super
185
+ end
186
+
187
+ def encrypt_attributes
188
+ validate_encryption_allowed
189
+
190
+ update_columns build_encrypt_attribute_assignments
191
+ end
192
+
193
+ def decrypt_attributes
194
+ validate_encryption_allowed
195
+
196
+ decrypt_attribute_assignments = build_decrypt_attribute_assignments
197
+ ActiveRecord::Encryption.without_encryption { update_columns decrypt_attribute_assignments }
198
+ end
199
+
200
+ def validate_encryption_allowed
201
+ raise ActiveRecord::Encryption::Errors::Configuration, "can't be modified because it is encrypted" if ActiveRecord::Encryption.context.frozen_encryption?
202
+ end
203
+
204
+ def has_encrypted_attributes?
205
+ self.class.encrypted_attributes.present?
206
+ end
207
+
208
+ def build_encrypt_attribute_assignments
209
+ Array(self.class.encrypted_attributes).index_with do |attribute_name|
210
+ self[attribute_name]
211
+ end
212
+ end
213
+
214
+ def build_decrypt_attribute_assignments
215
+ Array(self.class.encrypted_attributes).to_h do |attribute_name|
216
+ type = type_for_attribute(attribute_name)
217
+ encrypted_value = ciphertext_for(attribute_name)
218
+ new_value = type.deserialize(encrypted_value)
219
+ [attribute_name, new_value]
220
+ end
221
+ end
222
+
223
+ def cant_modify_encrypted_attributes_when_frozen
224
+ self.class.encrypted_attributes.each do |attribute|
225
+ errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An ActiveModel::Type::Value that encrypts/decrypts strings of text.
6
+ #
7
+ # This is the central piece that connects the encryption system with +encrypts+ declarations in the
8
+ # model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
9
+ # for that attribute.
10
+ class EncryptedAttributeType < ::ActiveModel::Type::Value
11
+ include ActiveModel::Type::Helpers::Mutable
12
+
13
+ attr_reader :scheme, :cast_type
14
+
15
+ delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
16
+ delegate :accessor, :type, to: :cast_type
17
+
18
+ # ==== Options
19
+ #
20
+ # * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
21
+ # * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
22
+ # (after decrypting). ActiveModel::Type::String by default.
23
+ def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil)
24
+ super()
25
+ @scheme = scheme
26
+ @cast_type = cast_type
27
+ @previous_type = previous_type
28
+ @default = default
29
+ end
30
+
31
+ def cast(value)
32
+ cast_type.cast(value)
33
+ end
34
+
35
+ def deserialize(value)
36
+ cast_type.deserialize decrypt(value)
37
+ end
38
+
39
+ def serialize(value)
40
+ if serialize_with_oldest?
41
+ serialize_with_oldest(value)
42
+ else
43
+ serialize_with_current(value)
44
+ end
45
+ end
46
+
47
+ def encrypted?(value)
48
+ with_context { encryptor.encrypted? value }
49
+ end
50
+
51
+ def changed_in_place?(raw_old_value, new_value)
52
+ old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
53
+ old_value != new_value
54
+ end
55
+
56
+ def previous_types # :nodoc:
57
+ @previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests
58
+ @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
59
+ end
60
+
61
+ def support_unencrypted_data?
62
+ ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
63
+ end
64
+
65
+ private
66
+ def previous_schemes_including_clean_text
67
+ previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
68
+ end
69
+
70
+ def previous_types_without_clean_text
71
+ @previous_types_without_clean_text ||= build_previous_types_for(previous_schemes)
72
+ end
73
+
74
+ def build_previous_types_for(schemes)
75
+ schemes.collect do |scheme|
76
+ EncryptedAttributeType.new(scheme: scheme, previous_type: true)
77
+ end
78
+ end
79
+
80
+ def previous_type?
81
+ @previous_type
82
+ end
83
+
84
+ def decrypt_as_text(value)
85
+ with_context do
86
+ unless value.nil?
87
+ if @default && @default == value
88
+ value
89
+ else
90
+ encryptor.decrypt(value, **decryption_options)
91
+ end
92
+ end
93
+ end
94
+ rescue ActiveRecord::Encryption::Errors::Base => error
95
+ if previous_types_without_clean_text.blank?
96
+ handle_deserialize_error(error, value)
97
+ else
98
+ try_to_deserialize_with_previous_encrypted_types(value)
99
+ end
100
+ end
101
+
102
+ def decrypt(value)
103
+ text_to_database_type decrypt_as_text(value)
104
+ end
105
+
106
+ def try_to_deserialize_with_previous_encrypted_types(value)
107
+ previous_types.each.with_index do |type, index|
108
+ break type.deserialize(value)
109
+ rescue ActiveRecord::Encryption::Errors::Base => error
110
+ handle_deserialize_error(error, value) if index == previous_types.length - 1
111
+ end
112
+ end
113
+
114
+ def handle_deserialize_error(error, value)
115
+ if error.is_a?(Errors::Decryption) && support_unencrypted_data?
116
+ value
117
+ else
118
+ raise error
119
+ end
120
+ end
121
+
122
+ def serialize_with_oldest?
123
+ @serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present?
124
+ end
125
+
126
+ def serialize_with_oldest(value)
127
+ previous_types.first.serialize(value)
128
+ end
129
+
130
+ def serialize_with_current(value)
131
+ casted_value = cast_type.serialize(value)
132
+ casted_value = casted_value&.downcase if downcase?
133
+ encrypt(casted_value.to_s) unless casted_value.nil?
134
+ end
135
+
136
+ def encrypt_as_text(value)
137
+ with_context do
138
+ if encryptor.binary? && !cast_type.binary?
139
+ raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
140
+ end
141
+
142
+ encryptor.encrypt(value, **encryption_options)
143
+ end
144
+ end
145
+
146
+ def encrypt(value)
147
+ text_to_database_type encrypt_as_text(value)
148
+ end
149
+
150
+ def encryptor
151
+ ActiveRecord::Encryption.encryptor
152
+ end
153
+
154
+ def encryption_options
155
+ { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
156
+ end
157
+
158
+ def decryption_options
159
+ { key_provider: key_provider }.compact
160
+ end
161
+
162
+ def clean_text_scheme
163
+ @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
164
+ end
165
+
166
+ def text_to_database_type(value)
167
+ if value && cast_type.binary?
168
+ ActiveModel::Type::Binary::Data.new(value)
169
+ else
170
+ value
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end