I have an application that requires the use of encryption for some fields in the database. This is currently implemented using a concern which handles the fine details of encrypting and managing fields on included classes. It is important that the system be able to determine programmatically which classes are including this concern, but even more specifically programmatically there needs to be a good way to determine which fields are encrypted.
Today this is implemented, working, using class variables like this:
module EncryptedFields
extend ActiveSupport::Concern
included do
@@encrypted_attributes ||= {}
@@encrypted_attributes[self.to_s] ||= []
def self.encrypted attribute, options={}
@@encrypted_attributes[self.to_s] << attribute
### Other stuff
end
end
end
Included in a class, like this:
class SomeEcryptedModel
include EncryptedFields
encrypted :field_name, options
encrypted :other_field_name, options
#etc
end
The class variable @@encrypted_attributes
will correctly capture a hash of key-value pairs with the including class name as a key and the array of encrypted attributes as the value. I have considered using a registration system for encrypted models to use and 'register' themselves and their attributes but there is a lot more overhead associated with this and on my timeline I would like to start with something simpler, if it isn't too unsafe.
This is actually working fine in my current application, but I do not have a lot of experience with concerns or class variables, so I am concerned about whether or not I have made a serious miscalculation on how this will behave. Where are the gotchas here? I have been programmed since I started working with ruby (not that long ago) that class variables are in general to be avoided.
I was bitten once by this already because initially I thought that the class variable @@encrypted_attributes
would be a class variable on the included class; this apparently was not so. Each new model which included it would overwrite it, so I came to the conclusion that this class variable was apparently on the concern itself. At least, this is the behavior I appear to be witnessing. This turned out to be a more desirable behavior in the end because now I can get the full list of encrypted models. This has the obvious limitation that it only can return the list of encrypted models and attributes for models that have been loaded.
So, my question:
Is this a proper use case for class variables, or is there an alternative (better?) way to capture this same information? If this is an acceptable class variable use case, what are some of the pitfalls and/or protections I should add to ensure the code works as I intend it to?
Maybe I'm just trying to be too clever and I should just hard code my list? Thanks for the help!
You can go this way alternatively to concern approach:
module EncryptedFields
@encrypted_attributes ||= {}
def self.included(klass)
@encrypted_attributes[klass.name] ||= []
klass.extend(ClassMethods)
end
def self.add(class_name, attribute)
@encrypted_attributes[class_name] << attribute
end
module ClassMethods
def encrypted(attribute, options={})
EncryptedFields.add(name, attribute)
# Other stuff
end
end
end
class Stuff
include EncryptedFields
encrypted :ololo
encrypted :new_name
end
EncryptedFields.instance_variable_get(:@encrypted_attributes)
=> {"Stuff"=>[:ololo, :new_name]}
You don't need class variables here. Module instance variable will be enough. You don't have any inheritance and you can't actually create an instance of the module, so you will always have only a single place where @encripted_attributes
variable will be defined and it will be your EncriptedFields
module.
Good article regarding the difference between class variables and class instance variables: http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/