Skip to content

Friendly syntax to build components in pure Ruby #2170

@rainerborene

Description

@rainerborene

Feature request

We could introduce a ViewComponent::Metal class for developers who prefer building components entirely in Ruby, while still keeping the library’s promise of working smoothly with ERB and other template languages. While ViewComponent::Base already supports the call method for simple inline components, there’s room to make it even better. By providing a more friendly syntax, we can avoid the need for repetitive use of concat and capture, making the process more straightforward. If introducing a special class doesn't seem like a good idea, we could instead provide a concern module to add this syntax sugar for pure Ruby components.

Example

class ExampleComponent < ViewComponent::Metal
  def template
    button(
      type: "button", 
      class: "inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm"
    ) do
      plain "This works"

      em do
        span "nesting"
        strong do
          plain "also works"
        end
      end
    end
  end
end

Implementation

class ViewComponent::Metal
  undef :p

  def self.tag_elements
    @tag_elements ||= ActionView::Helpers::TagHelper::TagBuilder.instance_methods(false)
  end

  def template
    raise NotImplementedError
  end

  def render_in(view_context)
    raise ArgumentError, "#{self.class} does not support passing a block" if block_given?
    @view_context = view_context
    @view_context.with_output_buffer { template }
  end

  def plain(string)
    @view_context.concat(string)
  end

  def method_missing(method, ...)
    if self.class.tag_elements.include?(method)
      @view_context.concat @view_context.tag.__send__(method, ...)
    elsif @view_context.respond_to?(method, false)
      @view_context.__send__(method, ...)
    else
      super
    end
  end

  def respond_to_missing?(method, include_all = false)
    return true if super
    return true if self.class.tag_elements.include?(method)
    @view_context.respond_to?(method, false)
  end
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions