DEV Community

loading...

Rails ViewComponent helper

scottbarrow profile image Scott Barrow ・2 min read

Ruby Dev's like me don't like verbosity no matter how small, syntactic sugar is one of the underlying attractions of Ruby.

Rails ViewComponents are a superb addition to Rails especially when paired with utility style CSS like Tailwind.
However, repeatedly writing these render's gnaws at my OCD.
Constants in a view template, :shock:, the overloaded render method, all those parens!

render(SomeAwesomeComponent.new) do |component|
  Render(AlertComponent.new(type: 'success') do |component|
    render(ButtonComponent.new(classes: 'justify-end ml-2 items-start')
  end
end
Enter fullscreen mode Exit fullscreen mode

What if I told you, you could create a helper, obvious right?

more awesome, more lowercase, less parens, easier on the eyes

component :some_awesome_component do |awesome|
  component :alert, type: 'success' do |alert|
    component :button, classes: 'justify-end ml-2 items-start'
  end
end
Enter fullscreen mode Exit fullscreen mode

I chose to use component but if you want to go super succinct, you could even do

vc :some_awesome_component do |awesome|
  vc :alert, type: 'success' do |alert|
    vc :button, classes: 'justify-end ml-2 items-start'
  end
end
Enter fullscreen mode Exit fullscreen mode

This could be yours for the low low price of... reading below.

  def component(name, context: nil, **args, &block)
    return render_component_in(context, name, **args, &block) if context

    render component_class_for(name).new(args), &block
  end

  def render_component_in(context, name, **args, &block)
    component_class_for(name).new(args).render_in(context, &block)
  end

  private

  def component_class_for(path)
    name, namespace = path.to_s.split('/').reverse

    file_name = "#{name}_component"
    component_name = file_name.classify
    namespace ||= namespace(file_name)
    return "#{namespace.capitalize}::#{component_name}".constantize unless namespace == 'components'

    component_name.constantize
  end
Enter fullscreen mode Exit fullscreen mode

What's more if you follow some design pattern like atomic, and structure your components as such, you can create a separate namespace method to discover the component path allowing you to communicate your design system like

organism :some_awesome_component do |awesome|
  molecule :alert, type: 'success' do |alert|
    atom :button, classes: 'justify-end ml-2 items-start'
  end
end
Enter fullscreen mode Exit fullscreen mode

You can find the RailsByte here https://railsbytes.com/templates/xjNsDY

Discussion (2)

pic
Editor guide
Collapse
muriloime profile image
Murilo Vasconcelos

Hello @scottbarrow , Great article, although I dont think the rails bytes code works when passing blocks. However, the article's code render component_class_for(name).new(args), &block does the trick .
cheers

Collapse
leastbad profile image
leastbad

You can keep your Tailwind classes, but I'm totally using these helpers. Great work.