lib/lockbox/model.rb in lockbox-0.3.4 vs lib/lockbox/model.rb in lockbox-0.3.5

- old
+ new

@@ -9,20 +9,22 @@ # options[:type] = :datetime # when JSON # options[:type] = :json # when Hash # options[:type] = :hash + # when Array + # options[:type] = :array # when String # options[:type] = :string # when Integer # options[:type] = :integer # when Float # options[:type] = :float # end custom_type = options[:type].respond_to?(:serialize) && options[:type].respond_to?(:deserialize) - raise ArgumentError, "Unknown type: #{options[:type]}" unless custom_type || [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :binary, :json, :hash].include?(options[:type]) + raise ArgumentError, "Unknown type: #{options[:type]}" unless custom_type || [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :binary, :json, :hash, :array].include?(options[:type]) activerecord = defined?(ActiveRecord::Base) && self < ActiveRecord::Base raise ArgumentError, "Type not supported yet with Mongoid" if options[:type] && !activerecord attributes.each do |name| @@ -110,11 +112,11 @@ # 2. existing virtual attribute # 3. default to string (which can later be overridden) if options[:type] attribute_type = case options[:type] - when :json, :hash + when :json, :hash, :array :string when :integer ActiveModel::Type::Integer.new(limit: 8) else options[:type] @@ -122,10 +124,11 @@ attribute name, attribute_type serialize name, JSON if options[:type] == :json serialize name, Hash if options[:type] == :hash + serialize name, Array if options[:type] == :array elsif !attributes_to_define_after_schema_loads.key?(name.to_s) # when migrating it's best to specify the type directly # however, we can try to use the original type if its already defined if attributes_to_define_after_schema_loads.key?(original_name.to_s) attribute name, attributes_to_define_after_schema_loads[original_name.to_s].first @@ -221,22 +224,26 @@ private :"lockbox_direct_#{name}=" define_method(name) do message = super() - unless message + # possibly keep track of decrypted attributes directly in the future + # Hash serializer returns {} when nil, Array serializer returns [] when nil + # check for this explicitly as a layer of safety + if message.nil? || ((message == {} || message == []) && activerecord && @attributes[name.to_s].value_before_type_cast.nil?) ciphertext = send(encrypted_attribute) message = self.class.send(decrypt_method_name, ciphertext, context: self) if activerecord - # set previous attribute on first decrypt - if @attributes[name.to_s] - @attributes[name.to_s].instance_variable_set("@value_before_type_cast", message) - end + # set previous attribute so changes populate correctly + # it's fine if this is set on future decryptions (as is the case when message is nil) + # as only the first value is loaded into changes + @attributes[name.to_s].instance_variable_set("@value_before_type_cast", message) # cache - if respond_to?(:_write_attribute, true) - _write_attribute(name, message) if !@attributes.frozen? + # decrypt method does type casting + if respond_to?(:write_attribute_without_type_cast, true) + write_attribute_without_type_cast(name, message) if !@attributes.frozen? else raw_write_attribute(name, message) if !@attributes.frozen? end else instance_variable_set("@#{name}", message)