It’s easy to find information online regarding how to use ViewComponent. What’s not as easy to find is an explanation of why a person might want to use ViewComponent or what problem ViewComponent solves.
Here’s the problem that ViewComponent solves for me.
If you just stuff all your domain logic into Active Record models then the Active Record models grow too large and lose cohesion.
A model loses cohesion when its contents no longer relate to the same end purpose. Maybe there are a few methods that support feature A, a few methods that support feature B, and so on. The question “what idea does this model represent?” can’t be answered. The reason the question can’t be answered is because the model doesn’t represent just one idea, it represents a heterogeneous mix of ideas.
Because cohesive things are easier to understand than incohesive things, I try to organize my code into objects (and other structures) that have cohesion.
There are two main ways that I try to achieve cohesion in my Rails apps.
The first way, the way that I use the most, is by organizing my code into plain old Ruby objects (POROs). For example, in the application I maintain at work, I have objects called
InsuranceBalance which are responsible for the jobs of calculating the balances for various amounts that are owed.
I’m not using any fancy or new-fangled techniques in my POROs. I’m just using the principles of object-oriented programming. (If you’re new to OOP, I might recommend Steve McConnell’s Code Complete as a decent starting point.)
Regarding where I put my POROs, I just put them in
app/models. As far as I’m concerned, PORO models are models every bit as much as Active Record models are.
Sometimes I have a piece of code which doesn’t quite fit in with any existing model, but it also doesn’t quite make sense as its own standalone model.
In these cases I’ll often use a concerns or a mixin.
But even though POROs and concerns/mixins can go a really long way to give structure to my Rails apps, they can’t adequately cover everything.
I’ve found that I’m able to keep the vast majority of my code out of controllers and views. Most of my Rails apps’ code lives in the model.
But there’s still a good amount of code for which I can’t find a good home in the model. That tends to be view-related code. View-related code is often very fine-grained and detailed. It’s also often tightly coupled (at least from a conceptual standpoint) to the DOM or to the HTML or in some other way.
There are certain places where this code could go. None of them is great. Here are the options, as I see them, and why each is less than perfect.
Perhaps the most obvious place to try to put view-related code is in the view itself. Most of the time this works out great. But when the view-related code is sufficiently complicated or voluminous, it creates a distraction. It creates a mixture of levels of abstraction, which makes the code harder to understand.
The controller is also not a great home for this view-related code. The problem of mixing levels of abstraction is still there. In addition, putting view-related code in a controller mixes concerns, which makes the controller code harder to understand.
Another poorly-suited home for this view-related code is the model. There are two options, both not great.
The first option is to put the view-related code into some existing model. This option isn’t great because it pollutes the model with peripheral details, creates a potential mixture of concerns and mixture of levels of abstraction, and makes the model lose cohesion.
The other option is to create a new, standalone model just for the view-related code. This is usually better than stuffing it into an existing model but it’s still not great. Now the view-related code and the view itself are at a distance from each other. Plus it creates a mixture of abstractions at a macro level because now the code in
app/models contains view-related code.
Lastly, one possible home for non-trivial view-related code is a helper. This can actually be a perfectly good solution sometimes. I use helpers a fair amount. But sometimes there are still problems.
Sometimes the view-related code is sufficiently complicated to require multiple methods. If I put these methods into a helper which is also home to other concerns, then we have a cohesion problem, and things get confusing. In those cases maybe I can put the view-related code into its own new helper, and maybe that’s fine. But sometimes that’s a lost opportunity because what I really want is a concept with meaning, and helpers (with their
-Helper suffix) aren’t great for creating concepts with meaning.
No good home
The result is that when I have non-trivial view-related code, it doesn’t have a good home. Instead, my view-related code has to “stay with friends”. It’s an uncomfortable arrangement. The “friends” (controllers, models, etc.) wish that the view-related code would move out and get a place of its own, but it doesn’t have a place to go.
How ViewComponent provides a home for view-related code
A ViewComponent consists of two entities: 1) an ERB file and 2) a Ruby object. These two files share a name (e.g.
save_button_component.rb and sit at a sibling level to each other in the filesystem. This makes it easy to see that they’re closely related to one another.
Ever since I started using ViewComponent I’ve had a much easier time working with views that have non-trivial logic. In those cases I just create a ViewComponent and put the logic in the ViewComponent.
Now my poor homeless view-related code can move into a nice, spacious, tidy new house that it gets all to its own. And just as important, it can get out of its friends’ hair.
And just in case you think this sounds like a “silver bullet” situation, it’s not. The reason is because ViewComponents are a specific solution to a specific problem. I don’t use ViewComponent for everything, I only use ViewComponent when a view has non-trivial logic associated with it that doesn’t have any other good place to live.
- If you just stuff all your domain logic into Active Record models, your Active Record models will soon lose cohesion.
- In my Rails apps, I mainly achieve cohesion through a mix of POROs and concerns/mixins (but mostly POROs).
- Among the available options (views, controllers, models and helpers) it’s hard to find a good place to put non-trivial view-related code.
- ViewComponent provides (in my mind) a reasonable place to put non-trivial view-related code.
Side note: if you’re interested in learning more about ViewComponent, you can listen to my podcast conversation with Joel Hawksley who created the tool.