Skip to content

Fixed read_attribute for encrypted attributes - #216

Open
mshibuya wants to merge 1 commit into
ankane:masterfrom
mshibuya:read-attribute
Open

mshibuya wants to merge 1 commit into
ankane:masterfrom
mshibuya:read-attribute

Conversation

@mshibuya

@mshibuya mshibuya commented Oct 12, 2026 •

Copy link
Copy Markdown

A virtual attribute is only populated once its reader has run, so read_attribute returns nil for an encrypted attribute, while every other attribute-level reader returns the value:

User.create!(email: "test@example.org")

User.last[:email]                 # => "test@example.org"
User.last.attributes["email"]     # => "test@example.org"
User.last.read_attribute(:email)  # => nil

This is the same bug as #82 ([], fixed in 95ad80e) and #43 (attributes, fixed in d8ed71f), one accessor over.

Why override read_attribute rather than add to []

In Active Record, [] is read_attribute:

def [](attr_name)
  read_attribute(attr_name) { |n| missing_attribute(n, caller) }
end

— activerecord/lib/active_record/attribute_methods.rb#L415-L417

So moving the warming down to read_attribute covers both paths with one method — test_keyed_getter passes unchanged — and anything else reading an attribute through it is covered as well. The write side needs nothing: it is already synced centrally in _create_record/_update_record.

Why a library reads the attribute instead of calling the reader

For every other Active Record attribute the two are equivalent — including Rails' own encrypts, where the decryption lives in the attribute's type, so reading the attribute returns the decrypted value. Lockbox is the one case where they differ, which is why it already bridges the gap in [], attributes, attribute_names, inspect and serializable_hash. read_attribute is the one that was left.

What it affects

As in #82, anything that goes through an attribute name rather than a method name — asking has_attribute? and then reading the attribute is a common shape. That is how it reached me: an encrypted attribute rendered blank in a form, and submitting that form wrote the blank back over the ciphertext (railsadminteam/rails_admin#3241).

A virtual attribute is only loaded when its reader runs, so reading it
through read_attribute returned nil. Overriding read_attribute rather
than [] covers both, since [] reads through it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant