# (c) 2017 Ribose Inc. # module AttrMasker # Holds the definition of maskable attribute. class Attribute attr_reader :name, :model, :options def initialize(name, model, options) @name = name.to_sym @model = model @options = options end # Evaluates the +:if+ and +:unless+ attribute options on given instance. # Returns +true+ or +false+, depending on whether the attribute should be # masked for this object or not. def should_mask?(model_instance) not ( options.key?(:if) && !evaluate_option(:if, model_instance) || options.key?(:unless) && evaluate_option(:unless, model_instance) ) end # Mask the attribute on given model. Masking will be performed regardless # of +:if+ and +:unless+ options. A +should_mask?+ method should be called # separately to ensure that given object is eligible for masking. # # The method returns the masked value but does not modify the object's # attribute. # # If +marshal+ attribute's option is +true+, the attribute value will be # loaded before masking, and dumped to proper storage format prior # returning. def mask(model_instance) value = unmarshal_data(model_instance.send(name)) masker = options[:masker] masker_value = masker.call(value: value, model: model_instance, attribute_name: name, masking_options: options) model_instance.send("#{name}=", marshal_data(masker_value)) end # Returns a hash of maskable attribute names, and respective attribute # values. Unchanged attributes are skipped. def masked_attributes_new_values(model_instance) model_instance.changes.slice(*column_names).transform_values(&:second) end # Evaluates option (typically +:if+ or +:unless+) on given model instance. # That option can be either a proc (a model is passed as an only argument), # or a symbol (a method of that name is called on model instance). def evaluate_option(option_name, model_instance) option = options[option_name] if option.is_a?(Symbol) model_instance.send(option) elsif option.respond_to?(:call) option.call(model_instance) else option end end def marshal_data(data) return data unless options[:marshal] options[:marshaler].send(options[:dump_method], data) end def unmarshal_data(data) return data unless options[:marshal] options[:marshaler].send(options[:load_method], data) end def column_names options[:column_names] || [name] end end end