I set these methods to automatically encrypt values.
class User < ApplicationRecord
def name=(val)
super val.encrypt
end
def name
(super()
While you can work around this by overloading name_before_type_case
, I think this is actually the wrong place to be doing this kind of transformation.
Based on your example, the requirements here appear to be:
So if we move the encrytion/decryption transformation to the Ruby-DB boundary, this logic becomes much cleaner & reusable.
Rails 5 introduced a helpful Attributes API for dealing with this exact scenario. Since you have provided no details about how your encryption routine is implemented, I'm going to use Base64
in my example code to demonstrate a text transformation.
app/types/encrypted_type.rb
class EncryptedType < ActiveRecord::Type::Text
# this is called when saving to the DB
def serialize(value)
Base64.encode64(value) unless value.nil?
end
# called when loading from DB
def deserialize(value)
Base64.decode64(value) unless value.nil?
end
# add this if the field is not idempotent
def changed_in_place?(raw_old_value, new_value)
deserialize(raw_old_value) != new_value
end
end
config/initalizers/types.rb
ActiveRecord::Type.register(:encrypted, EncryptedType)
Now, you can specify this attribute as encrypted in the model:
class User < ApplicationRecord
attribute :name, :encrypted
# If you have a lot of fields, you can use metaprogramming:
%i[name phone address1 address2 ssn].each do |field_name|
attribute field_name, :encrypted
end
end
The name
attribute will be transparently encrypted & decrypted during roundtrips to the DB. This also means that you can apply the same transform to as many attributes as you like without rewriting the same code.