activerecord 7.0.0 → 7.2.1

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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +2 -4
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -18,20 +18,22 @@ module ActiveRecord
18
18
  #
19
19
  # === Options
20
20
  #
21
- # * <tt>:key_provider</tt> - Configure a +KeyProvider+ for serving the keys to encrypt and
22
- # decrypt this attribute. If not provided, it will default to +ActiveRecord::Encryption.key_provider+.
21
+ # * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
22
+ # +ActiveRecord::Encryption.key_provider+.
23
23
  # * <tt>:key</tt> - A password to derive the key from. It's a shorthand for a +:key_provider+ that
24
24
  # serves derivated keys. Both options can't be used at the same time.
25
- # * <tt>:key_provider</tt> - Set a +:key_provider+ to provide encryption and decryption keys. If not
26
- # provided, it will default to the key provider set with `config.key_provider`.
27
25
  # * <tt>:deterministic</tt> - By default, encryption is not deterministic. It will use a random
28
26
  # initialization vector for each encryption operation. This means that encrypting the same content
29
27
  # with the same key twice will generate different ciphertexts. When set to +true+, it will generate the
30
28
  # initialization vector based on the encrypted content. This means that the same content will generate
31
29
  # the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption
32
30
  # will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
33
- # +deterministic: { fixed: false }+. That will make it use the newest encryption scheme for encrypting new
31
+ # <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
34
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.
35
37
  # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
36
38
  # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
37
39
  # in preserving it.
@@ -44,13 +46,11 @@ module ActiveRecord
44
46
  # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
45
47
  # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
46
48
  # encryption is used, they will be used to generate additional ciphertexts to check in the queries.
47
- def encrypts(*names, key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
49
+ def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
48
50
  self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
49
- scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, downcase: downcase, \
50
- ignore_case: ignore_case, previous: previous, **context_properties
51
51
 
52
52
  names.each do |name|
53
- encrypt_attribute name, scheme
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
54
  end
55
55
  end
56
56
 
@@ -63,32 +63,35 @@ module ActiveRecord
63
63
 
64
64
  # Given a attribute name, it returns the name of the source attribute when it's a preserved one.
65
65
  def source_attribute_from_preserved_attribute(attribute_name)
66
- attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if /^#{ORIGINAL_ATTRIBUTE_PREFIX}/.match?(attribute_name)
66
+ attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if attribute_name.start_with?(ORIGINAL_ATTRIBUTE_PREFIX)
67
67
  end
68
68
 
69
69
  private
70
- def scheme_for(key_provider: nil, key: nil, deterministic: false, downcase: false, ignore_case: false, previous: [], **context_properties)
70
+ def scheme_for(key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
71
71
  ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic,
72
- downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
72
+ support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme|
73
73
  scheme.previous_schemes = global_previous_schemes_for(scheme) +
74
- Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
74
+ Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) }
75
75
  end
76
76
  end
77
77
 
78
78
  def global_previous_schemes_for(scheme)
79
- ActiveRecord::Encryption.config.previous_schemes.collect do |previous_scheme|
80
- scheme.merge(previous_scheme)
79
+ ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme|
80
+ scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme)
81
81
  end
82
82
  end
83
83
 
84
- def encrypt_attribute(name, attribute_scheme)
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
85
  encrypted_attributes << name.to_sym
86
86
 
87
- attribute name do |cast_type|
88
- ActiveRecord::Encryption::EncryptedAttributeType.new scheme: attribute_scheme, cast_type: cast_type
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)
89
92
  end
90
93
 
91
- preserve_original_encrypted(name) if attribute_scheme.ignore_case?
94
+ preserve_original_encrypted(name) if ignore_case
92
95
  ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name)
93
96
  end
94
97
 
@@ -120,7 +123,7 @@ module ActiveRecord
120
123
  end)
121
124
  end
122
125
 
123
- def load_schema!
126
+ def load_schema! # :nodoc:
124
127
  super
125
128
 
126
129
  add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
@@ -141,12 +144,22 @@ module ActiveRecord
141
144
 
142
145
  # Returns whether a given attribute is encrypted or not.
143
146
  def encrypted_attribute?(attribute_name)
144
- ActiveRecord::Encryption.encryptor.encrypted? ciphertext_for(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)
145
154
  end
146
155
 
147
156
  # Returns the ciphertext for +attribute_name+.
148
157
  def ciphertext_for(attribute_name)
149
- read_attribute_before_type_cast(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
150
163
  end
151
164
 
152
165
  # Encrypts all the encryptable attributes and saves the changes.
@@ -162,6 +175,15 @@ module ActiveRecord
162
175
  private
163
176
  ORIGINAL_ATTRIBUTE_PREFIX = "original_"
164
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
+
165
187
  def encrypt_attributes
166
188
  validate_encryption_allowed
167
189
 
@@ -190,16 +212,16 @@ module ActiveRecord
190
212
  end
191
213
 
192
214
  def build_decrypt_attribute_assignments
193
- Array(self.class.encrypted_attributes).collect do |attribute_name|
215
+ Array(self.class.encrypted_attributes).to_h do |attribute_name|
194
216
  type = type_for_attribute(attribute_name)
195
217
  encrypted_value = ciphertext_for(attribute_name)
196
218
  new_value = type.deserialize(encrypted_value)
197
219
  [attribute_name, new_value]
198
- end.to_h
220
+ end
199
221
  end
200
222
 
201
223
  def cant_modify_encrypted_attributes_when_frozen
202
- self.class&.encrypted_attributes.each do |attribute|
224
+ self.class.encrypted_attributes.each do |attribute|
203
225
  errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
204
226
  end
205
227
  end
@@ -2,29 +2,34 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Encryption
5
- # An +ActiveModel::Type+ that encrypts/decrypts strings of text.
5
+ # An ActiveModel::Type::Value that encrypts/decrypts strings of text.
6
6
  #
7
7
  # This is the central piece that connects the encryption system with +encrypts+ declarations in the
8
8
  # model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
9
9
  # for that attribute.
10
- class EncryptedAttributeType < ::ActiveRecord::Type::Text
10
+ class EncryptedAttributeType < ::ActiveModel::Type::Value
11
11
  include ActiveModel::Type::Helpers::Mutable
12
12
 
13
13
  attr_reader :scheme, :cast_type
14
14
 
15
15
  delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
16
- delegate :accessor, to: :cast_type
16
+ delegate :accessor, :type, to: :cast_type
17
17
 
18
18
  # === Options
19
19
  #
20
20
  # * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
21
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)
22
+ # (after decrypting). ActiveModel::Type::String by default.
23
+ def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil)
24
24
  super()
25
25
  @scheme = scheme
26
26
  @cast_type = cast_type
27
27
  @previous_type = previous_type
28
+ @default = default
29
+ end
30
+
31
+ def cast(value)
32
+ cast_type.cast(value)
28
33
  end
29
34
 
30
35
  def deserialize(value)
@@ -39,6 +44,10 @@ module ActiveRecord
39
44
  end
40
45
  end
41
46
 
47
+ def encrypted?(value)
48
+ with_context { encryptor.encrypted? value }
49
+ end
50
+
42
51
  def changed_in_place?(raw_old_value, new_value)
43
52
  old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
44
53
  old_value != new_value
@@ -49,6 +58,10 @@ module ActiveRecord
49
58
  @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
50
59
  end
51
60
 
61
+ def support_unencrypted_data?
62
+ ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
63
+ end
64
+
52
65
  private
53
66
  def previous_schemes_including_clean_text
54
67
  previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
@@ -68,9 +81,15 @@ module ActiveRecord
68
81
  @previous_type
69
82
  end
70
83
 
71
- def decrypt(value)
84
+ def decrypt_as_text(value)
72
85
  with_context do
73
- encryptor.decrypt(value, **decryption_options) unless value.nil?
86
+ unless value.nil?
87
+ if @default && @default == value
88
+ value
89
+ else
90
+ encryptor.decrypt(value, **decryption_options)
91
+ end
92
+ end
74
93
  end
75
94
  rescue ActiveRecord::Encryption::Errors::Base => error
76
95
  if previous_types_without_clean_text.blank?
@@ -80,6 +99,10 @@ module ActiveRecord
80
99
  end
81
100
  end
82
101
 
102
+ def decrypt(value)
103
+ text_to_database_type decrypt_as_text(value)
104
+ end
105
+
83
106
  def try_to_deserialize_with_previous_encrypted_types(value)
84
107
  previous_types.each.with_index do |type, index|
85
108
  break type.deserialize(value)
@@ -110,31 +133,43 @@ module ActiveRecord
110
133
  encrypt(casted_value.to_s) unless casted_value.nil?
111
134
  end
112
135
 
113
- def encrypt(value)
136
+ def encrypt_as_text(value)
114
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
+
115
142
  encryptor.encrypt(value, **encryption_options)
116
143
  end
117
144
  end
118
145
 
119
- def encryptor
120
- ActiveRecord::Encryption.encryptor
146
+ def encrypt(value)
147
+ text_to_database_type encrypt_as_text(value)
121
148
  end
122
149
 
123
- def support_unencrypted_data?
124
- ActiveRecord::Encryption.config.support_unencrypted_data && !previous_type?
150
+ def encryptor
151
+ ActiveRecord::Encryption.encryptor
125
152
  end
126
153
 
127
154
  def encryption_options
128
- @encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
155
+ { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
129
156
  end
130
157
 
131
158
  def decryption_options
132
- @decryption_options ||= { key_provider: key_provider }.compact
159
+ { key_provider: key_provider }.compact
133
160
  end
134
161
 
135
162
  def clean_text_scheme
136
163
  @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
137
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
138
173
  end
139
174
  end
140
175
  end
@@ -6,17 +6,25 @@ require "active_support/core_ext/numeric"
6
6
 
7
7
  module ActiveRecord
8
8
  module Encryption
9
- # An encryptor exposes the encryption API that +ActiveRecord::Encryption::EncryptedAttributeType+
9
+ # An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType
10
10
  # uses for encrypting and decrypting attribute values.
11
11
  #
12
- # It interacts with a +KeyProvider+ for getting the keys, and delegate to
13
- # +ActiveRecord::Encryption::Cipher+ the actual encryption algorithm.
12
+ # It interacts with a KeyProvider for getting the keys, and delegate to
13
+ # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
14
  class Encryptor
15
+ # === Options
16
+ #
17
+ # * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
18
+ # Defaults to +true+.
19
+ def initialize(compress: true)
20
+ @compress = compress
21
+ end
22
+
15
23
  # Encrypts +clean_text+ and returns the encrypted result
16
24
  #
17
25
  # Internally, it will:
18
26
  #
19
- # 1. Create a new +ActiveRecord::Encryption::Message+
27
+ # 1. Create a new ActiveRecord::Encryption::Message
20
28
  # 2. Compress and encrypt +clean_text+ as the message payload
21
29
  # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
22
30
  # by default)
@@ -26,10 +34,10 @@ module ActiveRecord
26
34
  #
27
35
  # [:key_provider]
28
36
  # Key provider to use for the encryption operation. It will default to
29
- # +ActiveRecord::Encryption.key_provider+ when not provided
37
+ # +ActiveRecord::Encryption.key_provider+ when not provided.
30
38
  #
31
39
  # [:cipher_options]
32
- # +Cipher+-specific options that will be passed to the Cipher configured in
40
+ # Cipher-specific options that will be passed to the Cipher configured in
33
41
  # +ActiveRecord::Encryption.cipher+
34
42
  def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
35
43
  clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
@@ -38,7 +46,7 @@ module ActiveRecord
38
46
  serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
39
47
  end
40
48
 
41
- # Decrypts a +clean_text+ and returns the result as clean text
49
+ # Decrypts an +encrypted_text+ and returns the result as clean text
42
50
  #
43
51
  # === Options
44
52
  #
@@ -47,7 +55,7 @@ module ActiveRecord
47
55
  # +ActiveRecord::Encryption.key_provider+ when not provided
48
56
  #
49
57
  # [:cipher_options]
50
- # +Cipher+-specific options that will be passed to the Cipher configured in
58
+ # Cipher-specific options that will be passed to the Cipher configured in
51
59
  # +ActiveRecord::Encryption.cipher+
52
60
  def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
53
61
  message = deserialize_message(encrypted_text)
@@ -66,6 +74,10 @@ module ActiveRecord
66
74
  false
67
75
  end
68
76
 
77
+ def binary?
78
+ serializer.binary?
79
+ end
80
+
69
81
  private
70
82
  DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
71
83
  ENCODING_ERRORS = [EncodingError, Errors::Encoding]
@@ -100,7 +112,6 @@ module ActiveRecord
100
112
  end
101
113
 
102
114
  def deserialize_message(message)
103
- raise Errors::Encoding unless message.is_a?(String)
104
115
  serializer.load message
105
116
  rescue ArgumentError, TypeError, Errors::ForbiddenClass
106
117
  raise Errors::Encoding
@@ -112,13 +123,17 @@ module ActiveRecord
112
123
 
113
124
  # Under certain threshold, ZIP compression is actually worse that not compressing
114
125
  def compress_if_worth_it(string)
115
- if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
126
+ if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
116
127
  [compress(string), true]
117
128
  else
118
129
  [string, false]
119
130
  end
120
131
  end
121
132
 
133
+ def compress?
134
+ @compress
135
+ end
136
+
122
137
  def compress(data)
123
138
  Zlib::Deflate.deflate(data).tap do |compressed_data|
124
139
  compressed_data.force_encoding(data.encoding)
@@ -4,13 +4,13 @@ module ActiveRecord
4
4
  module Encryption
5
5
  # Implements a simple envelope encryption approach where:
6
6
  #
7
- # * It generates a random data-encryption key for each encryption operation
7
+ # * It generates a random data-encryption key for each encryption operation.
8
8
  # * It stores the generated key along with the encrypted payload. It encrypts this key
9
- # with the master key provided in the credential +active_record.encryption.master key+
9
+ # with the master key provided in the +active_record_encryption.primary_key+ credential.
10
10
  #
11
11
  # This provider can work with multiple master keys. It will use the last one for encrypting.
12
12
  #
13
- # When `config.store_key_references` is true, it will also store a reference to
13
+ # When +config.active_record.encryption.store_key_references+ is true, it will also store a reference to
14
14
  # the specific master key that was used to encrypt the data-encryption key. When not set,
15
15
  # it will try all the configured master keys looking for the right one, in order to
16
16
  # return the right decryption key.
@@ -1,119 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
4
- #
5
- # Active Record Encryption supports querying the db using deterministic attributes. For example:
6
- #
7
- # Contact.find_by(email_address: "jorge@hey.com")
8
- #
9
- # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
10
- # a problem while the data is being encrypted. This won't work. During that time, you need these
11
- # queries to be:
12
- #
13
- # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
14
- #
15
- # This patches ActiveRecord to support this automatically. It addresses both:
16
- #
17
- # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
18
- # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
19
- #
20
- # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
21
- # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
22
- # as it's invoked (so that the proper prepared statement is cached).
23
- #
24
- # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
25
- # make sure performance overhead is acceptable.
26
- #
27
- # We will extend this to support previous "encryption context" versions in future iterations
28
- #
29
- # @TODO Experimental. Support for every kind of query is pending
30
- # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
31
3
  module ActiveRecord
32
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`.
33
23
  module ExtendedDeterministicQueries
34
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).
35
28
  ActiveRecord::Relation.prepend(RelationQueries)
36
29
  ActiveRecord::Base.include(CoreQueries)
37
30
  ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
38
- Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
39
31
  end
40
32
 
41
- module EncryptedQueryArgumentProcessor
42
- extend ActiveSupport::Concern
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?
43
45
 
44
- private
45
- def process_encrypted_query_arguments(args, check_for_additional_values)
46
46
  if args.is_a?(Array) && (options = args.first).is_a?(Hash)
47
- self.deterministic_encrypted_attributes&.each do |attribute_name|
48
- type = type_for_attribute(attribute_name)
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)
49
59
  if !type.previous_types.empty? && value = options[attribute_name]
50
60
  options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type)
51
61
  end
52
62
  end
53
63
  end
54
- end
55
64
 
56
- def process_encrypted_query_argument(value, check_for_additional_values, type)
57
- return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue)
65
+ args
66
+ end
58
67
 
59
- case value
60
- when String, Array
61
- list = Array(value)
62
- list + list.flat_map do |each_value|
63
- if check_for_additional_values && each_value.is_a?(AdditionalValue)
64
- each_value
65
- else
66
- additional_values_for(each_value, type)
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
67
81
  end
82
+ else
83
+ value
68
84
  end
69
- else
70
- value
71
85
  end
72
- end
73
86
 
74
- def additional_values_for(value, type)
75
- type.previous_types.collect do |additional_type|
76
- AdditionalValue.new(value, additional_type)
87
+ def additional_values_for(value, type)
88
+ type.previous_types.collect do |additional_type|
89
+ AdditionalValue.new(value, additional_type)
90
+ end
77
91
  end
78
- end
92
+ end
79
93
  end
80
94
 
81
95
  module RelationQueries
82
- include EncryptedQueryArgumentProcessor
83
-
84
96
  def where(*args)
85
- process_encrypted_query_arguments_if_needed(args)
86
- super
97
+ super(*EncryptedQuery.process_arguments(self, args, true))
87
98
  end
88
99
 
89
100
  def exists?(*args)
90
- process_encrypted_query_arguments_if_needed(args)
91
- super
101
+ super(*EncryptedQuery.process_arguments(self, args, true))
92
102
  end
93
103
 
94
- def find_or_create_by(attributes, &block)
95
- find_by(attributes.dup) || create(attributes, &block)
96
- end
104
+ def scope_for_create
105
+ return super unless klass.deterministic_encrypted_attributes&.any?
97
106
 
98
- def find_or_create_by!(attributes, &block)
99
- find_by(attributes.dup) || create!(attributes, &block)
100
- end
107
+ scope_attributes = super
108
+ wheres = where_values_hash
101
109
 
102
- private
103
- def process_encrypted_query_arguments_if_needed(args)
104
- process_encrypted_query_arguments(args, true) unless self.deterministic_encrypted_attributes&.empty?
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
105
116
  end
117
+
118
+ scope_attributes
119
+ end
106
120
  end
107
121
 
108
122
  module CoreQueries
109
123
  extend ActiveSupport::Concern
110
124
 
111
125
  class_methods do
112
- include EncryptedQueryArgumentProcessor
113
-
114
126
  def find_by(*args)
115
- process_encrypted_query_arguments(args, false) unless self.deterministic_encrypted_attributes&.empty?
116
- super
127
+ super(*EncryptedQuery.process_arguments(self, args, false))
117
128
  end
118
129
  end
119
130
  end
@@ -141,20 +152,6 @@ module ActiveRecord
141
152
  end
142
153
  end
143
154
  end
144
-
145
- module InWithAdditionalValues
146
- def proc_for_binds
147
- -> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, encryption_aware_type_caster) }
148
- end
149
-
150
- def encryption_aware_type_caster
151
- if attribute.type_caster.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)
152
- attribute.type_caster.cast_type
153
- else
154
- attribute.type_caster
155
- end
156
- end
157
- end
158
155
  end
159
156
  end
160
157
  end
@@ -12,9 +12,9 @@ module ActiveRecord
12
12
  super(record, attribute, value)
13
13
 
14
14
  klass = record.class
15
- klass.deterministic_encrypted_attributes&.each do |attribute_name|
16
- encrypted_type = klass.type_for_attribute(attribute_name)
17
- [ encrypted_type, *encrypted_type.previous_types ].each do |type|
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
18
  encrypted_value = type.serialize(value)
19
19
  ActiveRecord::Encryption.without_encryption do
20
20
  super(record, attribute, encrypted_value)