class ARMS::StructCoder

this is a ActiveRecord serialization class intended to serialize from a Struct class on the loaded ruby side to something JSON-compatible on the dumped database side.

This coder relies on ‘loaded_class`, the Struct class which will be used to instantiate the column data. properties (members) of the loaded class will correspond to keys of the dumped json object.

the data may be either a single instance of the loaded class (serialized as one hash) or an array of them (serialized as an array of hashes), indicated by the boolean keyword argument ‘array`.

the column behind the attribute may be an actual JSON column (postgres json or jsonb - hstore should work too if you only have string attributes) or may be a string column with a string serializer after StructCoder.

Public Class Methods

new(loaded_class, array: false) click to toggle source

@param loaded_class [Class] the Struct class to load @param array [Boolean] whether the column holds an array of Struct instances instead of just one

# File lib/arms/struct_coder.rb, line 19
def initialize(loaded_class, array: false)
  @loaded_class = loaded_class
  # this notes the order of the keys as they were in the json, used by dump_object to generate
  # json that is equivalent to the json/jsonifiable that came in, so that AR's #changed_attributes
  # can tell whether the attribute has been changed.
  @loaded_class.send(:attr_accessor, :arms_object_json_coder_keys_order)
  @array = array
end

Public Instance Methods

dump(object) click to toggle source

@param object [loaded_class, Array] @return [Hash, Array<Hash>]

# File lib/arms/struct_coder.rb, line 45
def dump(object)
  return nil if object.nil?
  jsonifiable = begin
    if @array
      unless object.respond_to?(:to_ary)
        raise DumpError, "expected array-like attribute; got: #{object.class}: #{object.inspect}"
      end
      object.map do |el|
        dump_object(el)
      end
    else
      dump_object(object)
    end
  end
  jsonifiable
end
load(data) click to toggle source

@param data [Hash, Array<Hash>] @return [loaded_class, Array]

# File lib/arms/struct_coder.rb, line 30
def load(data)
  return nil if data.nil?
  object = if @array
    unless data.respond_to?(:to_ary)
      raise LoadError, "expected array-like column data; got: #{data.class}: #{data.inspect}"
    end
    data.map { |el| load_object(el) }
  else
    load_object(data)
  end
  object
end

Private Instance Methods

dump_object(object) click to toggle source

@param object [loaded_class] @return [Hash]

# File lib/arms/struct_coder.rb, line 84
def dump_object(object)
  if object.is_a?(@loaded_class)
    keys = (object.arms_object_json_coder_keys_order || []) | @loaded_class.members.map(&:to_s)
    keys.map { |member| {member => object[member]} }.inject({}, &:update)
  else
    raise TypeError, "expected instance(s) of #{@loaded_class}; got: #{object.class}: #{object.inspect}"
  end
end
load_object(data) click to toggle source

@param data [Hash] @return [loaded_class]

# File lib/arms/struct_coder.rb, line 66
def load_object(data)
  if data.respond_to?(:to_hash)
    data = data.to_hash
    good_keys = @loaded_class.members.map(&:to_s)
    bad_keys = data.keys - good_keys
    unless bad_keys.empty?
      raise LoadError, "expected keys #{good_keys}; got unrecognized keys: #{bad_keys}"
    end
    instance = @loaded_class.new(*@loaded_class.members.map { |m| data[m.to_s] })
    instance.arms_object_json_coder_keys_order = data.keys
    instance
  else
    raise LoadError, "expected instance(s) of #{Hash}; got: #{data.class}: #{data.inspect}"
  end
end