lib/mountain_view/presenter.rb
module MountainView
  class Presenter
    class_attribute :_properties, instance_accessor: false
    self._properties = {}

    attr_reader :slug, :properties

    def initialize(slug, properties = {})
      @slug = slug
      @properties = default_properties.deep_merge(properties)
    end

    def render(context)
      context.extend ViewContext
      context.inject_component_context self
      properties[:yield] ||= yield if block_given?
      context.render partial, partial: partial
    end

    def partial
      "#{slug}/#{slug}"
    end

    private

    def default_properties
      self.class._properties.inject({}) do |sum, (k, v)|
        sum[k] = v[:default]
        sum
      end
    end

    class << self
      def component_for(*args)
        klass = "#{args.first.to_s.camelize}Component".safe_constantize
        klass ||= self
        klass.new(*args)
      end

      def properties(*args)
        opts = args.extract_options!
        properties = args.inject({}) do |sum, name|
          sum[name] = opts
          sum
        end
        define_property_methods(args)
        self._properties = _properties.merge(properties)
      end
      alias_method :property, :properties

      private

      def define_property_methods(names = [])
        names.each do |name|
          next if method_defined?(name)

          define_method name do
            properties[name.to_sym]
          end
        end
      end
    end

    # :nodoc:
    module ViewContext
      attr_reader :_component
      delegate :properties, to: :_component

      def inject_component_context(component)
        @_component = component
        protected_methods = MountainView::Presenter.public_methods(false)
        methods = component.public_methods(false) - protected_methods
        methods.each do |meth|
          next if self.class.method_defined?(meth)

          self.class.delegate meth, to: :_component
        end
      end
    end
  end
end
