Skip to content

Commit 65ec470

Browse files
committed
Fix exception in attr reader without selecting encrypted column
When using AR's .select to retrieve a subset of columns for a model, calling the encrypted attribute reader results in an exception: ActiveModel::MissingAttributeError: missing attribute: encrypted_street gems/activerecord-5.1.5/lib/active_record/attribute_methods/read.rb:71:in `block in _read_attribute' gems/activerecord-5.1.5/lib/active_record/attribute_set.rb:45:in `block in fetch_value' gems/activerecord-5.1.5/lib/active_record/attribute.rb:219:in `value' gems/activerecord-5.1.5/lib/active_record/attribute_set.rb:45:in `fetch_value' gems/activerecord-5.1.5/lib/active_record/attribute_methods/read.rb:71:in `_read_attribute' gems/activerecord-5.1.5/lib/active_record/attribute_methods/read.rb:36:in `__temp__56e636279707475646f5374727565647' lib/attr_encrypted.rb:161:in `block (2 levels) in attr_encrypted' The virtual attribute `email` is defined in the test, but the reader tries to access the encrypted column which wasn't selected. This is a problem when rendering a model `#to_json` with the default serialiser, as it tries to read all defined columns. Allow the reader to return nil in the case when the encrypted attribute column isn't available.
1 parent 55839af commit 65ec470

File tree

3 files changed

+32
-3
lines changed

3 files changed

+32
-3
lines changed

lib/attr_encrypted.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,11 @@ def attr_encrypted(*attributes)
158158
end
159159

160160
define_method(attribute) do
161-
instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
161+
read_encrypted_attribute(attribute)
162162
end
163163

164164
define_method("#{attribute}=") do |value|
165-
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
166-
instance_variable_set("@#{attribute}", value)
165+
write_encrypted_attribute(attribute, value)
167166
end
168167

169168
define_method("#{attribute}?") do
@@ -395,6 +394,17 @@ def evaluate_attr_encrypted_option(option)
395394
end
396395
end
397396

397+
def read_encrypted_attribute(attribute)
398+
encrypted_attribute_name = encrypted_attributes[attribute.to_sym][:attribute]
399+
instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
400+
end
401+
402+
def write_encrypted_attribute(attribute, value)
403+
encrypted_attribute_name = encrypted_attributes[attribute.to_sym][:attribute]
404+
send("#{encrypted_attribute_name}=", encrypt(attribute, value))
405+
instance_variable_set("@#{attribute}", value)
406+
end
407+
398408
def load_iv_for_attribute(attribute, options)
399409
encrypted_attribute_name = options[:attribute]
400410
encode_iv = options[:encode_iv]

lib/attr_encrypted/adapters/active_record.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Adapters
44
module ActiveRecord
55
def self.extended(base) # :nodoc:
66
base.class_eval do
7+
include InstanceMethods
78

89
# https://github.com/attr-encrypted/attr_encrypted/issues/68
910
alias_method :reload_without_attr_encrypted, :reload
@@ -130,6 +131,19 @@ def method_missing_with_attr_encrypted(method, *args, &block)
130131
end
131132
method_missing_without_attr_encrypted(method, *args, &block)
132133
end
134+
135+
module InstanceMethods
136+
def read_encrypted_attribute(attribute)
137+
encrypted_attribute_name = encrypted_attributes[attribute.to_sym][:attribute].to_s
138+
139+
# If the class does have the encrypted attribute, but this record doesn't (partial SELECT),
140+
# return early as fetching the encrypted attribute will fail
141+
return if self.class.attribute_names.include?(encrypted_attribute_name) &&
142+
!attributes.include?(encrypted_attribute_name)
143+
144+
super
145+
end
146+
end
133147
end
134148
end
135149
end

test/active_record_test.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,4 +335,9 @@ def test_should_evaluate_proc_based_mode
335335
refute_equal address.encrypted_zipcode, zipcode
336336
assert_equal address.zipcode, zipcode
337337
end
338+
339+
def test_should_read_attribute_without_encrypted_column_present
340+
address = Address.create!(street: '123 Elm')
341+
assert_nil Address.where(id: address.id).select(:id).first.street
342+
end
338343
end

0 commit comments

Comments
 (0)