Reusable UI components in Rails
A component-based approach for organizing view partials
Have you ever worked in a web application with many custom user interface (UI) components? How were they structured? In this article, I will share some tips and tricks on how to organize them in a Ruby on Rails application.
In the front-end development world, a component-based approach for building web applications has been gaining popularity since the spread of libraries and frameworks such as React and Vue for JavaScript. In React applications, components manage their own state and auto-update when data changes. They are then composed together to build complex UIs.
Prior to that, many CSS methodologies such as SMACSS, BEM, and Atomic Design, were created to help organizing the styles in a sane way. Usually by splitting them into modular components, elements, and their variations.
In Rails applications, pages are organized in layout and view files that can be further broken down into more manageable and reusable chunks, called partials. Due to their limited server-side rendering nature, expressing components in Rails applications is not as powerful as with modern JavaScript libraries. However, there can still be a lot of benefits from structuring them, especially on medium and large projects with nonconventional components:
- Consistent use: each component exposes a public interface that is used to render itself in different ways, depending on the given parameters. This helps to avoid cases of misspelled CSS classes or misplaced HTML elements.
- Easier maintenance: each component definition is in a distinct file, so there's a single source of truth. This helps with maintenance since there is only one place you need to modify to update them all.
- Abstraction from implementation details: complex components with a heavy HTML structure and the right combination of CSS classes can be abstracted into simple function calls that hide unnecessary details.
The usual way for a developer to reuse a component is by copying it from anywhere it is already being used. Then pasting in the desired location and doing some tweaking to match the current needs. This manual process is error-prone, especially if the developer is not familiar with how everything is organized inside the component (e.g., which particular classes to use in different states) or is not fond of front-end technologies (some back-end developers consider CSS to be a black magic box).
In order to demonstrate how to apply the following tips, I will use some components from Twitter’s Bootstrap, a popular front-end library. This kind of library is very effective for quickly setting up a new application's UI. Therefore, it is common to find them being used in Rails applications as the base structure with overridden styles and custom themes.
By the end of this article, you will learn how to create specific helpers for rendering components of your application, such as this alert:
With the fundamentals, you can expand the ideas to build more complex components such as cards, modals, or whatever your application needs.
Identify reusable components
The first step is to identify which elements are worth turning into components, as not everything is worth the time and effort.
Avoid going too far as turning simple things like buttons, links, headings, or other single elements into components. In these cases, it’s easier to just use the HTML version with classes.
If your project does not utilize something like a components style guide, you can use other ways to identify them. You can try to detect similar components being reused while browsing your codebase and start with the ones that are used the most or are more complex.
Front-end style guides are a modular collection of all the elements in your product’s user interface, together with code snippets for developers to copy and paste as needed to implement those elements. They include common UI components like buttons, form-input elements, navigation menus, modal overlays, and icons.
Lean UX by Jeff Gothelf and Josh Seiden
For example, form elements are heavily used in most applications and can be easily simplified if you use gems like Simple Form or Formtastic. These tools allow your inputs to be easily customized so you don't have to manually include the labels, repeat the same CSS classes every time, or deal with displaying validation errors. Consider organizing your form fields using these gems, or simply create your own form builder with Rails.
Directories structure
The recommended directory for placing partials that are shared with more than one view is app/views/application/
. Although the components would also fit in that category, I'd rather recommend for them to be stored in a more specific directory, such as app/views/components/
, because the application
directory may contain other reusable partials that are not considered components.
Complex components with multiple inner elements, or that belong to the same category, can be grouped together in their own directories:
|-- views
| |-- components
| | |-- _alert.html.erb
| | |
| | |-- charts
| | | |-- _bar.html.erb
| | | `-- _radial.html.erb
| | |
| | |-- _modal.html.erb
| | |-- modal
| | | |-- _header.html.erb
| | | `-- _footer.html.erb
Public Interface
The component’s API is the public interface it exposes to the clients. By clients here I mean the developers that are going to consume/render the component.
What is the simplest way your component can be rendered? What is the minimum number of required parameters? Which of them are optional and what values do they fall back on?
Check out the documentation on Action View Partials for a detailed explanation on the multiple ways of rendering a partial.
In the case of our alert component, the most essential parameter is the message to be displayed:
Or something more flexible:
Here is the simplest implementation of the above:
We want the flexibility of using either a block or a parameter for the message. If a block is used, we use its value (stored in yield
). Otherwise, we try to fetch it from the message
parameter.
In this particular case of rendering a partial with a block without the
layout
option, we cannot simply check that a block has been used withblock_given?
inside the partial. It returnstrue
andyield
returns""
. That's why we check if the block has a real content withpresence
.
All the parameters passed along when rendering the partial are stored in the local_assigns
hash and transformed into local variables. We can use this hash to help define required and optional parameters.
Required parameters
If the client does not provide the expected parameters used within the partial, it will fail to render because it will be considered undefined. A better error strategy, in this case, is to use the Hash#fetch
method. It will throw an error if the key is not provided:
key not found: :message
Sometimes it might not make sense to render the component at all unless some condition is met. In such cases, we can use a guard clause and return earlier from our component.
We can verify whether the alert’s message is either nil
or an empty string ""
using Rails' blank
method:
Optional parameters
These parameters help to modify the component in different ways. They can be used to style it differently, include inner elements, apply different behaviors, you name it.
You must check for their keys' presence and fall back to some default value (or just nil
) if they are not present. That can be done either using the ||
operator or Hash#fetch
. I prefer the latter since it works better for boolean values.
Let's see how we can enable our alerts to be dismissible by default:
Since the parameters are optional, there is no need to modify existing render
calls when adding them. These work great for expanding the component without affecting the existing clients.
Documentation
There isn't a built-in way to document the partials, so I rely on the ERB comment tag and the concept of tags from YARD (a superset of RDoc) to write the documentation for the components. This way, clients of your component will have an easier time figuring out how to use it.
Below is an example of how I would document the alert component:
Here I'm describing two parameters:
message
should be a String and is a required parameter (as expressed by the*
, commonly used in web forms to denote obligatory fields).dismissible
should be a Boolean, and it defaults to true.
The structure is simple: the component name with a brief description, followed by some YARD tags describing the parameters.
Refer to YARD's tags documentation to get an idea of how to use them. There are explicit ways to describe types such as arrays and hashes (useful for options). The most interesting tags for me are @param, @return, @option, @example and @yield.
Style variations
Our alert component is currently set to be styled only as a primary alert, but there are many possible different color styles it can use. We can introduce a new optional type
parameter to enable different styles.
When the type
param is not provided, we can default its value to the primary style:
If we want, we can also enforce the value to be one among a list of possible valid values and throw an error otherwise:
In this specific case, the type value maps directly to the expected CSS classes (e.g., :primary
=> .alert-primary
, :success
=> .alert-success
, etc). However, this mapping might not always be so straightforward like this. In such situations, we can use a hash to create our desired mapping:
Finally, we can use the resulting class in the component:
Inner elements
We might want to conditionally display some internal elements for our component.
Our alert component can display a heading text if one is provided:
Which can be implemented as simply as:
In the above example, we used a simple string to specify the heading, but if we need something more elaborate we can use the capture
helper to store a block with normal HTML in a variable:
Finally, here is the final version for our alert component:
Although it got quite long because of all the rendering setup, it does the job. What I personally don’t like about this approach is the excess of logic required, mixed inside the view file. I prefer my views to be as logic-less as possible. One way to remove the logic from inside the partial is to use custom Rails helpers.
Helpers
Some components, or behaviors triggered by JS, can be applied just by adding some extra attributes to an existing element. Bootstrap’s tooltips and popovers are examples of that. With some helper methods, we can simplify the way they're used in our views.
With the help of Ruby’s double splat operator **
, we can merge the component's options hash returned by the helpers with the options hash from the tag helper. This way, you only need the bare minimum to use them.
You can also use helpers to create shortcut versions for the render component calls:
Or you can be more explicit and shorter:
Advanced use cases
In this article, I showed a couple of ways you can organize your components using only Rails partials and helpers. Although they work for simpler components, as previously explained, the partials can get quite cluttered with the excess of rendering logic.
It would be much better if we could encapsulate all the logic we need in a simple PORO (Plain Old Ruby Object) and use it inside our partial, without leaking implicit dependencies. Fortunately, there is a way: the Cells gem.
This gem was born out of frustration with Rails’ view layout. A cell is an object that represents a fragment in the UI, or the whole view, and can render a template.
In this gist, you can check out how the alert component would be implemented with Cells. Although considerably more verbose, it does a better job in separating the data and the presentation layers. Besides a more organized structure, this gem offers a lot more interesting features such as working with collections, nesting, context options, caching, and much more.
Let me know if you're interested to hear about Cells in a follow-up article!
Conclusion
Prior to working with Rails applications, I had the opportunity to work on a project to develop a front-end components library, similar to Twitter’s Bootstrap. This experience has given me insights on how to use these concepts in my views with Rails.
Today, when I'm developing an application, I am constantly looking for opportunities to extract reusable components in order to make life easier for me and other developers. As not many back-end developers are familiar with HTML & CSS, this way of rendering components is much simpler for them to use. Your users will also thank you for maintaining a consistent interface, as it will help them to gain familiarity and confidence with your application.
I hope this article has given you some ideas on how you can better organize your views around UI components, or just your regular partials. It may feel like overkill for simple components, but it surely pays off for more complex ones.