diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f34890565ed..f9f977ac8ba 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Support encrypted attributes on columns with default db values. + +This adds support for encrypted attributes defined on columns with default values. +It will encrypt those values at creation time. Before, it would raise an +error unless `config.active_record.encryption.support_unencrypted_data` was true. + +*Jorge Manrubia* and *Dima Fatko* + * Allow overriding `reading_request?` in `DatabaseSelector::Resolver` The default implementation checks if a request is a `get?` or `head?`, diff --git a/activerecord/lib/active_record/encryption/encryptable_record.rb b/activerecord/lib/active_record/encryption/encryptable_record.rb index cf7cede8ee0..04e84c44c88 100644 --- a/activerecord/lib/active_record/encryption/encryptable_record.rb +++ b/activerecord/lib/active_record/encryption/encryptable_record.rb @@ -82,7 +82,7 @@ module ActiveRecord def encrypt_attribute(name, attribute_scheme) encrypted_attributes << name.to_sym - attribute name do |cast_type| + attribute name, default: -> { columns_hash[name.to_s]&.default } do |cast_type| ActiveRecord::Encryption::EncryptedAttributeType.new scheme: attribute_scheme, cast_type: cast_type end diff --git a/activerecord/lib/active_record/encryption/encrypted_attribute_type.rb b/activerecord/lib/active_record/encryption/encrypted_attribute_type.rb index e0979ca987b..0dfb8e356ab 100644 --- a/activerecord/lib/active_record/encryption/encrypted_attribute_type.rb +++ b/activerecord/lib/active_record/encryption/encrypted_attribute_type.rb @@ -40,7 +40,7 @@ module ActiveRecord end def changed_in_place?(raw_old_value, new_value) - old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value) + old_value = raw_old_value.nil? ? nil : deserialize_previous_value_to_determine_change(raw_old_value) old_value != new_value end @@ -135,6 +135,14 @@ module ActiveRecord def clean_text_scheme @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new) end + + def deserialize_previous_value_to_determine_change(raw_old_value) + deserialize(raw_old_value) + # We tolerate unencrypted data when determining if a column changed + # to support default DB values in encrypted attributes + rescue ActiveRecord::Encryption::Errors::Decryption + nil + end end end end diff --git a/activerecord/test/cases/encryption/encryptable_record_test.rb b/activerecord/test/cases/encryption/encryptable_record_test.rb index 40f2e76a98e..3fc8aa7c666 100644 --- a/activerecord/test/cases/encryption/encryptable_record_test.rb +++ b/activerecord/test/cases/encryption/encryptable_record_test.rb @@ -292,6 +292,11 @@ class ActiveRecord::Encryption::EncryptableRecordTest < ActiveRecord::Encryption assert_equal Encoding::US_ASCII, book.reload.name.encoding end + test "support encrypted attributes defined on columns with default values" do + book = EncryptedBook.create! + assert_encrypted_attribute(book, :name, "") + end + private class FailingKeyProvider def decryption_key(message) end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 89c3f85304f..cb81bf14fdf 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -141,7 +141,7 @@ ActiveRecord::Schema.define do create_table :encrypted_books, id: :integer, force: true do |t| t.references :author t.string :format - t.column :name, :string + t.column :name, :string, default: "" t.column :original_name, :string t.datetime :created_at