class ARMS::MultiCoder

Public Class Methods

new(coders, model: nil, attr_name: nil) click to toggle source

loads and dumps between database column and model attribute, using any number of coders.

the first coder is closest to the loaded model attribute. the last coder is closest to the dumped database column.

each coder must respond to load and dump. such a coder can be passed directly, or as a shortcut consisting of a key registered with ARMS.register_coder_shortcut and optional arguments (using an array). each of the following is a valid coder (an element of the coders array):

# direct reference to the coder
::ActiveRecord::Coders::YAMLColumn.new('foo')

# shortcut, equivalent to the above
:yaml

# shortcut passing optional `object_class` argument to yaml coder.
# the first element of this array is the shortcut key, and the remainder
# is arguments passed to instantiate the coder.
[:yaml, Array]

here are a few example invocations that instantiate a MultiCoder:

# two coders: indifferent hashes, YAML with argument Hash (the object_class)
MultiCoder.new([:indifferent_hashes, [YAML, Hash]], attr_name: 'preferences', model: Foo)

# two coders: struct coder with argument Preference (the struct class), JSON coder
MultiCoder.new([[:struct, Preference], :json], attr_name: 'preferences', model: Foo)

load goes like:

database column -> coderN.load -> … -> coder1.load -> model attribute

dump goes like:

model attribute -> coder1.dump -> … -> coderN.dump -> database column

@param coders [Array] an array of coders (which respond to load and dump) or coder shortcuts @param model [Class] the model on which the attribute is being serialized @param attr_name the attribute name being serialized on the model

# File lib/arms/multi_coder.rb, line 43
def initialize(coders, model: nil, attr_name: nil)
  @coders = coders.each_with_index.map do |coder, i|
    shortcut_invocation = ShortcutInvocation.new
    shortcut_invocation.model = model
    shortcut_invocation.attr_name = attr_name

    if coder.respond_to?(:to_ary)
      shortcut_invocation.args = coder[1..-1]
      coder = coder[0]
    end

    if ARMS.instance_exec { @coder_shortcuts }.key?(coder)
      ARMS.instance_exec { @coder_shortcuts }[coder].(shortcut_invocation)
    elsif coder.respond_to?(:load) && coder.respond_to?(:dump)
      if shortcut_invocation.args.nil? || shortcut_invocation.args.empty?
        coder
      else
        raise(InvalidCoder.new("given shortcut arguments are not passed to the coder at index #{i} which responds to #load and #dump. coder: #{coder.inspect}; shortcut args: #{shortcut_invocation.args.inspect}").tap { |e| e.coder = coder })
      end
    else
      raise(InvalidCoder.new("given coder at index #{i} is not a recognized shortcut and does not respond to #load and #dump. coder: #{coder.inspect}; shortcut args: #{shortcut_invocation.args.inspect}").tap { |e| e.coder = coder })
    end
  end
end

Public Instance Methods

dump(object) click to toggle source

@param object [Object] object on the model attribute @return [Object] dumped (serialized) data

# File lib/arms/multi_coder.rb, line 78
def dump(object)
  @coders.inject(object) do |data, coder|
    coder.dump(data)
  end
end
load(column_data) click to toggle source

@param column_data [Object] data hot off the database column @return [Object] loaded (deserialized) data

# File lib/arms/multi_coder.rb, line 70
def load(column_data)
  @coders.reverse.inject(column_data) do |data, coder|
    coder.load(data)
  end
end