Rails View Components
Ruby on Rails is a framework that delivers a tremendous amount of developer productivity and happiness. Unsurprisingly, Rails application also go through growing pains as they mature. Models and controllers expand until small objects are extracted to keep them under control. The same happens with Rails views; they start out powerful and easy to use and slowly grow out of control. The views become hard to reason about and maintain. Views are also inherently hard to test, so they become the riskiest part of a Rails application.
Decorator
Decorators are added to contain the situation. Gems exist to help with this, but we don’t need them. SimpleDelegator
is built into Ruby so we can make use of it like so:
Decorators are great, and they help for a while. The problem is that they are tightly coupled to models. Models tend to describe big ideas in the system that are displayed in many different ways. Decorators, therefore, get bloated as they start describing numerous UIs.
View Component
Applying basic OOP principles we can break a decorator into smaller objects. Instead of a CommentDecorator
we can build several components: CommentForm
, CommentBox
, CommentThread
, ReactionButtons
, etc. We don’t need a fancy gem to do this. A plain old ruby object will do:
The view component objects can be put under app/view_components
and their templates can be placed in app/views/components/
. The template is just a rails partial that gets a component
local variable.
The view components will each inherit from the main ViewComponent
class:
We’ll add a helper to facilitate the rendering of a view component:
Now anywhere in our views we can render a component with:
View components are ideally minimal as they have a single and focused responsibility. This makes them easy to test and reason about. We can use a real rendering context if we wanted to in tests or mock it and do isolation testing.
Interactivity
View components implemented in Ruby are a good solution until interactivity is required. If for example, our user interfaces need to respond to mouse events then the Ruby classes become a dead-end. Vue.js or Backbone.js are excellent JavaScript libraries for adding interactivity on-top of server-rendered views. To pass properties from the ruby side to the JavaScript side, we just have to make sure they exist in the HTML data attribute:
This is, in my opinion, a great solution for applications that just need a sprinkle of interactivity. Of course a component that is spread over four languages (Ruby, JavaScript, HTML, and CSS) becomes harder to test and contribute to. For new projects I recommend switching entirely to front-end components. Backbone.js, React, or Vue.js can be used to build both the template and behavior of complex UI components. This is a topic I want to explore more in the future on this blog.