diff --git a/lib/attr_encrypted.rb b/lib/attr_encrypted.rb index 3895b721..f743000a 100644 --- a/lib/attr_encrypted.rb +++ b/lib/attr_encrypted.rb @@ -158,12 +158,11 @@ def attr_encrypted(*attributes) end define_method(attribute) do - instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name))) + read_encrypted_attribute(attribute) end define_method("#{attribute}=") do |value| - send("#{encrypted_attribute_name}=", encrypt(attribute, value)) - instance_variable_set("@#{attribute}", value) + write_encrypted_attribute(attribute, value) end define_method("#{attribute}?") do @@ -395,6 +394,17 @@ def evaluate_attr_encrypted_option(option) end end + def read_encrypted_attribute(attribute) + encrypted_attribute_name = encrypted_attributes[attribute.to_sym][:attribute] + instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name))) + end + + def write_encrypted_attribute(attribute, value) + encrypted_attribute_name = encrypted_attributes[attribute.to_sym][:attribute] + send("#{encrypted_attribute_name}=", encrypt(attribute, value)) + instance_variable_set("@#{attribute}", value) + end + def load_iv_for_attribute(attribute, options) encrypted_attribute_name = options[:attribute] encode_iv = options[:encode_iv] diff --git a/lib/attr_encrypted/adapters/active_record.rb b/lib/attr_encrypted/adapters/active_record.rb index a22108e4..deae2981 100644 --- a/lib/attr_encrypted/adapters/active_record.rb +++ b/lib/attr_encrypted/adapters/active_record.rb @@ -4,6 +4,7 @@ module Adapters module ActiveRecord def self.extended(base) # :nodoc: base.class_eval do + include InstanceMethods # https://github.com/attr-encrypted/attr_encrypted/issues/68 alias_method :reload_without_attr_encrypted, :reload @@ -130,6 +131,19 @@ def method_missing_with_attr_encrypted(method, *args, &block) end method_missing_without_attr_encrypted(method, *args, &block) end + + module InstanceMethods + def read_encrypted_attribute(attribute) + encrypted_attribute_name = encrypted_attributes[attribute.to_sym][:attribute].to_s + + # If the class does have the encrypted attribute, but this record doesn't (partial SELECT), + # return early as fetching the encrypted attribute will fail + return if self.class.column_names.include?(encrypted_attribute_name) && + !attributes.include?(encrypted_attribute_name) + + super + end + end end end end diff --git a/test/active_record_test.rb b/test/active_record_test.rb index 89134548..df059cac 100644 --- a/test/active_record_test.rb +++ b/test/active_record_test.rb @@ -335,4 +335,9 @@ def test_should_evaluate_proc_based_mode refute_equal address.encrypted_zipcode, zipcode assert_equal address.zipcode, zipcode end + + def test_should_read_attribute_without_encrypted_column_present + address = Address.create!(street: '123 Elm') + assert_nil Address.where(id: address.id).select(:id).first.street + end end