class ConfigMapper::ConfigStruct

A set of configurable attributes.

Public Class Methods

attribute(name, type = nil, default: :no_default, description: nil, &type_block) click to toggle source

Defines reader and writer methods for the specified attribute.

A `:default` value may be specified; otherwise, the attribute is considered mandatory.

If a block is provided, it will invoked in the writer-method to validate the argument.

@param name [Symbol] attribute name @param default default value @yield type-coercion block

# File lib/config_mapper/config_struct.rb, line 39
def attribute(name, type = nil, default: :no_default, description: nil, &type_block)

  attribute = attribute!(name)
  attribute.description = description

  if default == :no_default
    attribute.required = true
  else
    attribute.default = default.freeze
  end

  attribute.validator = Validator.resolve(type || type_block)

  define_method("#{attribute.name}=") do |value|
    if value.nil?
      raise NoValueProvided if attribute.required
    else
      value = attribute.validator.call(value) if attribute.validator
    end
    instance_variable_set("@#{attribute.name}", value)
  end

end
attributes() click to toggle source
# File lib/config_mapper/config_struct.rb, line 116
def attributes
  attributes_by_name.values
end
component(name, type: ConfigStruct, description: nil, &block) click to toggle source

Defines a sub-component.

If a block is be provided, it will be `class_eval`ed to define the sub-components class.

@param name [Symbol] component name @param type [Class] component base-class

# File lib/config_mapper/config_struct.rb, line 71
def component(name, type: ConfigStruct, description: nil, &block)
  type = Class.new(type, &block) if block
  attribute = attribute!(name)
  attribute.description = description
  attribute.factory = type
end
component_dict(name, type: ConfigStruct, key_type: nil, description: nil, &block) click to toggle source

Defines an associative array of sub-components.

If a block is be provided, it will be `class_eval`ed to define the sub-components class.

@param name [Symbol] dictionary attribute name @param type [Class] base-class for component values @param key_type [Proc] function used to validate keys

# File lib/config_mapper/config_struct.rb, line 87
def component_dict(name, type: ConfigStruct, key_type: nil, description: nil, &block)
  type = Class.new(type, &block) if block
  component(name, type: ConfigDict::Factory.new(type, key_type), description: description)
end
component_list(name, type: ConfigStruct, description: nil, &block) click to toggle source

Defines an array of sub-components.

If a block is be provided, it will be `class_eval`ed to define the sub-components class.

@param name [Symbol] list attribute name @param type [Class] base-class for component values

# File lib/config_mapper/config_struct.rb, line 100
def component_list(name, type: ConfigStruct, description: nil, &block)
  type = Class.new(type, &block) if block
  component(name, type: ConfigList::Factory.new(type), description: description)
end
config_doc() click to toggle source

Generate documentation, as Ruby data.

Returns an entry for each configurable path, detailing `description`, `type`, and `default`.

@return [Hash] documentation, keyed by path

# File lib/config_mapper/config_struct.rb, line 112
def config_doc
  each_attribute.sort_by(&:name).map(&:config_doc).inject({}, :merge)
end
each_attribute(&action) click to toggle source
# File lib/config_mapper/config_struct.rb, line 120
def each_attribute(&action)
  return enum_for(:each_attribute) unless action
  ancestors.each do |klass|
    next unless klass.respond_to?(:attributes)
    klass.attributes.each(&action)
  end
end
from_data(attribute_values) click to toggle source

Instantiate from data.

@param attribute_values [Hash] attribute values @raise [ConfigMapper::MappingError] on error

# File lib/config_mapper/config_struct.rb, line 20
def from_data(attribute_values)
  new.tap do |instance|
    errors = instance.configure_with(attribute_values)
    raise MappingError, errors if errors.any?
  end
end
new() click to toggle source
# File lib/config_mapper/config_struct.rb, line 141
def initialize
  self.class.each_attribute do |attribute|
    instance_variable_set("@#{attribute.name}", attribute.initial_value)
  end
end

Private Class Methods

attribute!(name) click to toggle source
# File lib/config_mapper/config_struct.rb, line 134
def attribute!(name)
  attr_reader(name)
  attributes_by_name[name] ||= Attribute.new(name)
end
attributes_by_name() click to toggle source
# File lib/config_mapper/config_struct.rb, line 130
def attributes_by_name
  @attributes_by_name ||= {}
end

Public Instance Methods

config_errors() click to toggle source
# File lib/config_mapper/config_struct.rb, line 151
def config_errors
  immediate_config_errors.merge(component_config_errors)
end
configure_with(attribute_values) click to toggle source

Configure with data.

@param attribute_values [Hash] attribute values @return [Hash] errors encountered, keyed by attribute path

# File lib/config_mapper/config_struct.rb, line 160
def configure_with(attribute_values)
  errors = ConfigMapper.configure_with(attribute_values, self)
  config_errors.merge(errors)
end
immediate_config_errors() click to toggle source
# File lib/config_mapper/config_struct.rb, line 147
def immediate_config_errors
  missing_required_attribute_errors
end
to_h() click to toggle source

Return the configuration as a Hash.

@return [Hash] serializable config data

# File lib/config_mapper/config_struct.rb, line 169
def to_h
  {}.tap do |result|
    self.class.each_attribute do |attribute|
      value = send(attribute.name)
      if value && value.respond_to?(:to_h) && !value.is_a?(Array)
        value = value.to_h
      elsif value && value.respond_to?(:to_a)
        value = value.to_a
      end
      result[attribute.name.to_s] = value
    end
  end
end

Private Instance Methods

component_config_errors() click to toggle source
# File lib/config_mapper/config_struct.rb, line 212
def component_config_errors
  {}.tap do |errors|
    components.each do |component_path, component_value|
      next unless component_value.respond_to?(:config_errors)
      component_value.config_errors.each do |path, value|
        errors["#{component_path}#{path}"] = value
      end
    end
  end
end
components() click to toggle source
# File lib/config_mapper/config_struct.rb, line 185
def components
  {}.tap do |result|
    self.class.each_attribute do |a|
      next unless a.factory
      result[".#{a.name}"] = instance_variable_get("@#{a.name}")
    end
  end
end
missing_required_attribute_errors() click to toggle source
# File lib/config_mapper/config_struct.rb, line 202
def missing_required_attribute_errors
  {}.tap do |errors|
    self.class.each_attribute do |a|
      if a.required && instance_variable_get("@#{a.name}").nil?
        errors[".#{a.name}"] = NoValueProvided.new
      end
    end
  end
end