Skip to content

View Components

Dany Marcoux edited this page Jul 28, 2021 · 34 revisions

Approved by: @dmarcoux, @danidoni, @vpereira, @rubhanazeem

Problem

A partial with questionable code quality. It is perhaps complex or huge. It's unclear what data it needs since it doesn't have a clear interface. On top of this, it is probably barely tested and even if it is, it's only through slow integration tests of pages in which it is used.

Solution

Replace the partial by a view component located under src/api/app/components. View components are plain old Ruby objects (PORO). It makes them reusable, easy to test through unit tests, encapsulated and they have a clear interface defined by their initialize method. Replacing pretty much any partial by a view component is beneficial, unless a partial is super simple without any logic and used in multiple views. This is a perfect use case for a partial and using a view component won't be very helpful in this case.

Details

Why We Chose The view_component Gem

There are many view component implementations in Ruby, but we decided to use the view_component gem since it integrates nicely with Rails and has increasing support throughout the Rails community. Its open-source development is also backed by GitHub.

Conventions

  • All view components must inherit from ApplicationComponent. This parent class has methods to prevent potential errors in common scenarios when using view components.
  • View component names end with Component.
  • Name view components after what they render, not what they accept. (Example: AvatarComponent instead of UserComponent if the view component is rendering the avatar of a user.)

How to Write View Component Specs

Specs of view components are located under src/api/app/spec/components. They are typical unit tests with a few extra methods and Capybara matchers like have_text, have_css (and much more) are available.

Specs can be split in two common scenarios:

  1. A single expectation

In the code below, the view component is rendered with render_inline(...).to_html(). We use the have_text matcher from Capybara to test the rendered component.

require 'rails_helper'

RSpec.describe ExampleComponent, type: :component do
  context 'for anonymous user' do
    it do
      expect(render_inline(described_class.new(title: 'Everything is fine')).to_html).to have_text('Everything is fine')
    end
  end
end
  1. Multiple expectations

The view component is rendered with render_inline(...).to_html once in a before block to avoid wasting time by rendering it multiple times. The rendered component is stored in rendered_component, a method provided by the view_component gem. Use Capybara matchers like have_text for your expectations. Setting User.session = create(:admin_user) in a before block allows you to test how the view component renders for various users based on their permissions.

require 'rails_helper'

RSpec.describe ExampleComponent, type: :component do
  context 'for admin user' do
    before do
      User.session = create(:admin_user)
      render_inline(described_class.new(title: 'Everything is fine for an admin')).to_html
    end

    it do
      expect(rendered_component).to have_text('Everything is fine for an admin')
    end

    it do
      expect(rendered_component).to have_text('Delete this example?')
    end
  end
end

How to Preview View Components in Your Browser

Previews of view components are located under src/api/app/spec/components/previews. They are accessible at https://$HOST:$PORT/rails/view_components/$COMPONENT_NAME/$METHOD_NAME. Previews are enabled by default in development and test environments.

class ExampleComponentPreview < ViewComponent::Preview
  # Accessible at https://my_app.com/rails/view_components/example_component/with_a_title
  def with_a_title
    render(ExampleComponent.new(title: "This is my example"))
  end
end

How to Use Helpers in View Components

In view components, helpers can be used through the helpers proxy. So if you have a helper named user_icon, use it inside a view component with helpers.user_icon.

Authorization inside View Components with Pundit

To authorize users inside view components or their Haml template, use the policy method provided by ApplicationComponent. It is exactly the same as the policy method you already know from Pundit. Beside the argument provided to the policy method, nothing should change when migrating authorization code from a partial to a view component.

For example, to check in a view component if the current user can create the package my_package, write policy(my_package).create?.

Links

Frequently Asked Questions

Why view_component Instead of a Frontend Framework like React/Vue?

TODO

Should We Only Use View Components For New Features?

Yes! View components and partials serve the same purpose. Unlike partials though, view components are easy to test through unit tests, encapsulated and they have a clear interface defined by their initialize method.

Should We Migrate Existing Partials to View Components?

Yes! However, a super simple partial without any logic doesn't benefit much from being migrated to a view component since in this case, tests aren't needed and the clear interface of a view component through its initialize method isn't helpful if it's empty.

Clone this wiki locally