icelab/formalist

View on GitHub
lib/formalist/definition.rb

Summary

Maintainability
A
25 mins
Test Coverage
module Formalist
  class Definition
    DuplicateElementError = Class.new(StandardError)

    attr_reader :form
    attr_reader :config
    attr_reader :elements

    def initialize(form, config, &block)
      @form = form
      @config = config
      @elements = []

      instance_eval(&block) if block
    end

    def with(**new_options, &block)
      new_config = new_options.each_with_object(config.dup) { |(key, value), config|
        config.send :"#{key}=", value
      }

      self.class.new(form, new_config, &block)
    end

    def method_missing(name, *args, &block)
      if element_type?(name)
        add_element(name, *args, &block)
      elsif form.respond_to?(name, include_private = true)
        form.send(name, *args, &block)
      else
        super
      end
    end

    private

    def respond_to_missing?(name, _include_private = false)
      element_type?(name) || form.respond_to?(name, _include_private = true) || super
    end

    def element_type?(type)
      config.elements_container.key?(type)
    end

    def add_element(type, *args, &block)
      element_name = args.shift unless args.first.is_a?(Hash)
      element_attrs = args.last.is_a?(Hash) ? args.last : {}

      if element_name && elements.any? { |element| element.name == element_name }
        raise DuplicateElementError, "element +#{element_name}+ is already defined in this context"
      end

      element_class = config.elements_container[type]
      element_children = with(&block).elements

      element = element_class.build(
        name: element_name,
        attributes: element_attrs,
        children: element_children,
      )

      elements << element
    end
  end
end