Components on Rails?
Tags: rails, components, github
Published: 2021-03-24 04:37:03

As we know, Rails has its own approach to re-use some parts of the view, you can extract this HTML in modules called **partials** and you can use them as many times as you need and it's ok, actually, partials are an excellent option because you are using this feature out of the box without the need of install extra gems and the goal is accomplished, but, **Github** provides us another solution called **ViewComponents**.

## What are ViewComponents?

[Github page](https://github.com/github/view_component) says:

> ViewComponent is a framework for building reusable, testable & encapsulated view components in Ruby on Rails.

and they define their components [here](https://viewcomponent.org/) like:

> Ruby objects that output HTML. Think of them as an evolution of the presenter pattern, inspired by React.

but what benefits can they give us?

## Features

*ViewComponents* have some interesting features and that can be the param to decide between them or *Partials*, for example, these are my favorites features:

### Testing

*ViewComponents* **can be unit-tested** 💪 and yes, this is crazy! because Rails traditional views are usually tested with *Integration Tests* but now you'll have some covered views parts with this fast kind of tests.

### Isolation

This is awesome! You can have all about your components organized into their own folder, in other words, **isolated components**, for example, suppose you have a **Comment** component, so your `app/components/comment` folder will look like this:

```bash
├── component.html.erb
├── component.js
├── component.rb
└── component.scss
```

At the end, you have an isolated and portable component, with its own structure, styles, and behavior 🤟🏼 ready to use in many parts of your application.

### Readability

I love how you can organize your components as you saw in the above point, but this is not everything, a component is a *Ruby Class*, so you can see what each component needs to be rendered, into the `initialize` method mainly, you'll see all needed data to build your view.

## Components in action!

For this code example I'll use the project created in the [past post](https://dev.to/sanchezdav/reactivity-rails-with-stimulus-reflex-320h), so if you want to follow this implementation, just clone it from [here](https://github.com/Sanchezdav/tasky) 👌.

### Installing ViewComponents

The first thing we need to do is install the gem, we just open the **Gemfile** and we add this line:

```ruby
gem 'view_component', require: 'view_component/engine'
```

finally, run this command on console:

```bash
> bundle install
```

### Let's add more information to Tasks

Before creating our component, I want to add more details to our Tasks to do it more interesting. Open the `app/views/projects/show.html.erb` file and update the tasks list 👇🏼:

```erb
<% @project.tasks.each do |task| %>
  <li class="border-2 bg-gray-200 hover:bg-gray-300 rounded-lg p-2 mb-2">
    <h3 class="text-lg font-bold">
      <%= task.content %>
    </h3>
    <div class="flex justify-between">
      <span class="text-sm font-light">
        <%= time_ago_in_words(task.created_at) %> ago
      </span>
      <a href="#" class="text-red-500">Delete</a>
    </div>
  </li>
<% end %>
```

You'll see something like this:

![Tasks List](https://res.cloudinary.com/codeando/image/upload/v1614665366/ViewComponents/Screen_Shot_2021-03-02_at_00.09.03_ngtc54.png)

### Our first component!

To create our **Task** component is necessary run the next command:

```bash
> rails generate component Task task

Running via Spring preloader in process 40952
  create  app/components/task_component.rb
  invoke  test_unit
  create    test/components/task_component_test.rb
  invoke  erb
  create    app/components/task_component.html.erb
```

Running that command, we are just telling rails that we want to create a component with the name **Task** and it will receive a task as param, so the next step is to copy the HTML within the `li` element from `app/views/projects/show.html.erb`, I mean, the code that renders each task item and pastes it into `app/components/task_component.html.erb` replacing `task` variable with `@task` that it's the instance variable from our `TaskComponent` class, like this:

```erb
<section class="border-2 bg-gray-200 hover:bg-gray-300 rounded-lg p-2 mb-2">
  <h3 class="text-lg font-bold">
    <%= @task.content %>
  </h3>
  <div class="flex justify-between">
    <span class="text-sm font-light">
      <%= time_ago_in_words(@task.created_at) %> ago
    </span>
    <a href="#" class="text-red-500">Delete</a>
  </div>
</section>
```

and let's render the `TaskComponent` into the tasks lists

```erb
<% if @project.tasks.any? %>
  <ul>
    <% @project.tasks.each do |task| %>
      <li><%= render(TaskComponent.new(task: task)) %></li>
    <% end %>
  </ul>
<% else %>
  <h3 class="text-md">Add your first task ☝️</h3>
<% end %>
```

and voila! **Your first component is working** 🤟🏼, you should see the same view as before

![Tasks List](https://res.cloudinary.com/codeando/image/upload/v1614665366/ViewComponents/Screen_Shot_2021-03-02_at_00.09.03_ngtc54.png)

### Refactor and test

The first thing I want to do is order our `/components` dir, so let's do these changes 👇🏼

1. Create a folder called `/task` within `/components` dir.
2. Rename `task_component.rb` to `component.rb` and move it to `/task` folder.
3. Rename `task_component.html.erb` to `component.html.erb` and move it to `/task` folder.
4. Rename the class `TaskComponent` to `Task::Component`, this because we have a `task` module now.
5. Rename `TaskComponent.new(task: task)` to `Task::Component.new(task: task)`

at the end, you should see this structure of folders 👇🏼 and the HTML view should be the same as the above screenshot

```bash
> tree app/components

app/components
└── task
    ├── component.html.erb
    └── component.rb
```

with this, we are ready to test our component 💪 but before continuing, let's change our file `test/components/task_component_test.rb` to `test/components/task/component_test.rb` and add these small changes 👇🏼

1. Add this CSS class `task__content` to the `h3` element where we have the **Task** content in `app/components/task/component.html.erb`, just for the purpose of this example and you can see how we can identify our HTML elements in the test:

```erb
<section class="border-2 bg-gray-200 hover:bg-gray-300 rounded-lg p-2 mb-2">
  <h3 class="task__content text-lg font-bold">
    <%= @task.content %>
  </h3>
  ...
```

2. Go to `test/fixtures/tasks.yml` and create a dummy Task for our test:

```yml
first_task:
  project: one
  content: This is my first task
```

and finally you are ready to write the test into this file `test/components/task/component_test.rb` 🎉🎉🎉

```ruby
require 'test_helper'

class Task::ComponentTest < ViewComponent::TestCase
  def test_render_component
    task = tasks(:one)
    render_inline(Task::Component.new(task: task))

    assert_selector('h3.task__content', text: 'This is my first task')
  end
end
```

Let´s review each line together:

- In this line `task = tasks(:one)` we are creating the dummy Task we added in the tasks fixture
- With `render_inline` we are asserting against the rendered output.
- and the last line `assert_selector` we are just comparing that `h3` with the CSS class `task__content` has the expected content we added into the tasks fixture: `This is my first task`.

You can run your test in this way

```bash
> rails test test/components/task/component_test.rb

Running via Spring preloader in process 54617
Run options: --seed 60900

# Running:

.

Finished in 0.444012s, 2.2522 runs/s, 2.2522 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
```

**Your test is passed!** 😃

Now you know how to create your own `ViewComponents` and test them, I hope this post is helpful, see you next! 👋🏼

**Read the original post on [codeando.dev](https://codeando.dev/posts/rails-view-components/)**