Search code examples
rubyclass-variablesactivesupport-concern

Ruby Alternative to use of class variables in ActiveSupport::Concern


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!


Solution

  • 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/