class Mimi::Struct

A Struct that can be initialized from a Hash or a PORO.

A Struct declares its attributes and rules, which define how its attributes are mapped from input data.

Constants

DEFAULT_ATTRIBUTE_MAPPER

Default attribute mapper

Maps value of the source attribute to the target attribute. Calculates a default value if the source attribute is not set.

DEFAULT_IF_FOR_OPTIONAL

Default :if block for an optional attribute

Skips the attribute if the source attribute is not set.

VERSION

Public Class Methods

<<(obj_or_collection) click to toggle source

Converts a single object or collection to Struct.

# File lib/mimi/struct.rb, line 135
def self.<<(obj_or_collection)
  if obj_or_collection.is_a?(Array)
    obj_or_collection.map { |o| self << o }
  else
    new(obj_or_collection)
  end
end
attribute(name, params = {}) click to toggle source

An attribute definition

Possible params:

from: <Symbol>
using: <proc,Mimi::Struct>
if: <proc>
default: <proc,Object>
optional: <true,false>

@param name [Symbol] @param params [Hash]

# File lib/mimi/struct.rb, line 90
def self.attribute(name, params = {})
  name = name.to_sym
  raise ArgumentError, "Attribute '#{name}' is already declared" if attribute_definitions.key?(name)
  defaults = group_params.reduce(:merge).merge(
    from: name,
    using: DEFAULT_ATTRIBUTE_MAPPER
  )
  params = defaults.merge(params)
  if params.key?(:if) && params.key?(:optional)
    raise ArgumentError, "Keys :if and :optional cannot be used together"
  end
  params[:if] = DEFAULT_IF_FOR_OPTIONAL if params[:optional]
  add_attribute_definition(name, params)
end
group(params) { || ... } click to toggle source

Declare a group of parameters with common options

E.g.

Class User < Mimi::Struct
  attribute :id
  attribute :type
  attribute :name
  group if: -> (o) { o.type == 'ADMIN' } do
    attribute :admin_role
    attribute :admin_domain
  end
  group default: -> { Time.now.utc } do
    attribute :created_at
    attribute :updated_at
  end
end

NOTE: Not reentrable.

@param params [Hash]

# File lib/mimi/struct.rb, line 126
def self.group(params, &block)
  group_params << params
  yield
  group_params.pop
end
new(source = {}) click to toggle source

Creates a mapped Struct object from another object

@param source [Hash,Object]

Calls superclass method
# File lib/mimi/struct.rb, line 52
def initialize(source = {})
  source = Mimi::Core::Struct.new(source) if source.is_a?(Hash)
  attributes = self.class.transform_attributes(source)
  super(attributes)
rescue StandardError => e
  raise e.class, "Failed to construct #{self.class}: #{e}", e.backtrace
end
transform_attributes(source) click to toggle source

Transform attributes according to rules

@param source [Struct] @return [Hash] map of attribute name -> value

# File lib/mimi/struct.rb, line 185
def self.transform_attributes(source)
  result = attribute_definitions.map do |k, params|
    if params[:if].is_a?(Proc)
      next unless call_as_proc(params[:if], source, params)
    end
    [k, transform_single_attribute(source, k, params)]
  end.compact.to_h
  result
end
value_to_h(value) click to toggle source

Map value or values to Hash

@param value [Object]

# File lib/mimi/struct.rb, line 230
def self.value_to_h(value)
  case value
  when Struct
    value.to_h
  when Array
    value.map { |v| value_to_h(v) }
  else
    value
  end
end

Protected Class Methods

attribute_definitions() click to toggle source
# File lib/mimi/struct.rb, line 152
def attribute_definitions
  @attribute_definitions ||= {}
  # merge with superclass attribute definitions
  superclass_attribute_definitions =
    superclass < Mimi::Struct ? superclass.attribute_definitions : {}
  superclass_attribute_definitions.merge(@attribute_definitions)
  # defined?(super) ? super.merge(@attribute_definitions) : @attribute_definitions
end

Private Class Methods

add_attribute_definition(name, params) click to toggle source

Adds a new attribute definition

@param name [Symbol] @param params [Hash]

# File lib/mimi/struct.rb, line 167
                     def self.add_attribute_definition(name, params)
  @attribute_definitions ||= {}
  @attribute_definitions[name] = params.dup
end
call_as_proc(proc, *args) click to toggle source

Calls a lambda as a proc, not caring about the number of arguments

@param proc_or_lambda [Proc] @param *args

# File lib/mimi/struct.rb, line 217
                     def self.call_as_proc(proc, *args)
  raise ArgumentError, "Proc is expected as proc" unless proc.is_a?(Proc)
  if proc.lambda?
    proc.call(*args.first(proc.arity))
  else
    proc.call(*args)
  end
end
group_params() click to toggle source

Returns current stack of group parameters

@return [Array<Hash>]

# File lib/mimi/struct.rb, line 176
                     def self.group_params
  @group_params ||= [{}]
end
transform_single_attribute(source, key, params) click to toggle source

Transforms a single attribute value according to rules passed as params

@param source [Struct] @param key [Symbol] @param params [Hash] transformation rules @return [Object]

# File lib/mimi/struct.rb, line 202
                     def self.transform_single_attribute(source, key, params)
  return call_as_proc(params[:using], source, params) if params[:using].is_a?(Proc)
  if params[:using].is_a?(Class) && params[:using] < Mimi::Struct
    return params[:using] << source.send(params[:from])
  end
  raise "unexpected :using type: #{params[:using].class}"
rescue StandardError => e
  raise Error, "Failed to transform attribute :#{key} : #{e}"
end

Public Instance Methods

attributes() click to toggle source

Returns attributes of this Structs as a Hash

@return [Hash]

# File lib/mimi/struct.rb, line 74
def attributes
  @attributes
end
to_h() click to toggle source

Presents this Struct as a Hash, deeply converting nested Structs

@return [Hash]

# File lib/mimi/struct.rb, line 64
def to_h
  attributes.map do |k, v|
    [k, self.class.value_to_h(v)]
  end.to_h
end