Calling Ruby method from specific ancestors
Recently, I wanted to use attr_encrypted
method from symmetric-encryption gem in one of the models in my Rails application.
Because of devise (which relies on attr_encrypted gem) being used in the codebase, whenever I used attr_encrypted
in the model, I called method from attr_encrypted
gem instead of symmetric-encryption
. It was caused by higher position of AttrEncrypted
module in the eigenclass’s included_modules
.
> MyModel.superclass.included_modules
=> [AttrEncrypted::Adapters::ActiveRecord, AttrEncrypted, ..., SymmetricEncryption::ActiveRecord::AttrEncrypted::ClassMethods, ...]
EDIT: This applies to Rails version 6 and below which doesn’t include ActiveRecord Encryption.
Since SymmetricEncryption::ActiveRecord::AttrEncrypted
is already injected, extending it in the model (or including it on MyModel.supperclass
) again wouldn’t move it to the top of the list. To work around it, I decided to use an alternative solution based on an unbound method to execute a method from a given object in the context of that class:
SymmetricEncryption::ActiveRecord::AttrEncrypted.instance_method(:attr_encrypted).bind(self).call
To make it easier to use from the models, helper class method can be added to ApplicationRecord
:
class ApplicationRecord
def self.attr_encrypted_symmetrically(*args, **kwargs)
SymmetricEncryption::ActiveRecord::AttrEncrypted::ClassMethods.instance_method(:attr_encrypted).bind(self).call(*args, **kwargs)
end
end
EDIT: Unbound method can be passed directly to define_method
/ define_singleton_method
which makes it even more compact:
class ApplicationRecord
define_singleton_method :attr_encrypted_symmetrically, SymmetricEncryption::ActiveRecord::AttrEncrypted::ClassMethods.instance_method(:attr_encrypted)
end
Then:
class MyModel
attr_encrypted_symmetrically :password
end