Is it just the crude title or is it true…? What makes Helpers Are Shit my most popular post ever? I felt like I have to write a follow-up now that Cells view models are replacing more and more helpers in many production apps out there.
Right, the last post is from 2011, that is old. What’s bad about this is that the write-up is still 100% accurate about the mess helpers and partials create in Rails apps. Nothing has changed in the Rails view layers for many years.
Helpers Are Shit.
The way helpers are being used in Rails encourages users to write “script-like” code using global variables and procedural functions in an environment that calls itself “object-oriented”.
Whenever I am refactoring complex Rails apps, the view layer is the worst part with hundreds of redundant helper methods accessing instance variables, calling other helpers, passing around models and options, and rendering random partials from a global directory.
Again, this mess is a result from the lack of a view abstraction layer in Rails: Helper invocations and partial rendering all happens in one monolithic ActionView instance – this is failure by design.
View Models.
Cells “view models” allow you to replace helpers entirely and forever. Instead of a global pile of mud you encapsulate parts of your view into a class. A class that can render templates, can inherit or mixin other methods, allows view inheritance, if needed.
This is faster than what you had before: In a view model, no data is copied between controller and view – the view model is the view context. Let me demonstrate that.
I will use the term “view model” and “cell” interchangeably (yepp, I had to look up that word).
Encapsulation Via Classes.
Encapsulating view logic happens in cell classes.
class CommentCell < Cell::ViewModel def show render end end
Think of a cell as you think of a Rails controller (without all the HTTP overhead, of course). Per design, a cell has one public method #show
. This is a convention. You can expose as many methods as you like from that cell. More on that later.
Where Is The Model?
I’ll walk you through that now. First, we need to render that cell somewhere in our app.
.sidebar = cell(:comment, Comment.last).show
Suppose this is somewhere in an application layout (it could be any view or in a controller). This will instantiate a CommentCell
and execute the show method. This is a bit like calling a helper.
Note that I pass in a Comment
model instance as the 2nd argument. This is the cell’s “model”, making the former a genuine view model™.
Encapsulated Rendering.
Let’s have a look again at the cell’s show method.
class CommentCell < Cell::ViewModel def show render end end
Once the show
method is called, it hits render
. That will simply invoke the cell’s rendering as known from a Rails controller. With one exception: the view is located in a separate directory. Here’s how a cell directory looks.
app ├── cells │ ├── comment_cell.rb │ ├── comment │ │ ├── show.haml
This is the old layout soon to be superceded by the Trailblazer-style “concept layout”. That doesn’t matter right now.
Good Old Views.
Now, let me show you how that show.haml
could look like.
%h1 Comment = model.body By = link_to model.author.name, model.author
Two things.
- The cell’s model (the comment itself) is accessible via the
#model
method. Wow. - You can still use helpers in a view. In this example, I use
link_to
.
No Code In My View!
We’ve learned that logic in views is bad. And this is exactly where view models start to be fun to work with.
class CommentCell < Cell::ViewModel def show render end private def body model.body end end
Remember: as the view model is the view context itself, any method called in the view will be simply called on the view model. That reduces complexity in the view a bit.
%h1 Comment = body By = link_to model.author.name, model.author
Embracing Helpers.
Still hating the link line. Why not move that into the cell, too?
class CommentCell < Cell::ViewModel def show render end private def body model.body end def author_link link_to model.author.name, model.author end end
Now, what? We can call helpers in the view model? Seerriously?
Well, I never said that helpers are a bad thing. They’re just shit when used to “encapsulate”. I personally love link_to
, sanitize
and form_for
(sometimes). They save me a lot of work. I just don’t like the fact they all play together in a big monolithic class.
Here, the link_to
can’t mess up anything as it is running in a limited scope – the cell instance.
Logicless Views.
The second refactoring reduces our view to a dumb template, as it should be.
%h1 Comment = body By = author_link
I love it already. What’s next?
Simple Decorating.
The task of delegating methods to the model is so tedious that it is built into cells. We can get rid of the body
method.
class CommentCell < Cell::ViewModel property :body property :author def show render end private def author_link link_to author.name, author end end
Declaring body
and author
as property
s automatically delegates to the model, making it a no-brainer. It also states your dependencies to the model in a very clear way. Hooray to explicit code!
Object-Oriented Helpers.
What if we had to customise the body
method, e.g. apply a markdown filter on the content?
class CommentCell < Cell::ViewModel include MarkdownHelper property :body # ... private def body markdown super end end
First, I include the magic markdown helper to import the markdown
method. Secondly, I can override the body
method and call super
to access the original property. Object-orientation back in the view land!
More Helpers!
A cell is not limited to one public method. It’s good to have only one but sometimes it comes in handy to not having to introduce a new class for every “helper”.
class CommentCell < Cell::ViewModel # ... def show render end def related render end private # ...
Here, I add a second public method related
. This will render another view, the related.haml
. Dump it into the cell’s directory and you’re good to go.
app ├── cells │ ├── comment_cell.rb │ ├── comment │ │ ├── show.haml │ │ ├── related.haml
You can now use this “helper” anywhere you want.
= cell(:comment, Comment.last).related
Isn’t that nice? We encapsulated a huge part of a mess into a clean, reusable component. This feels good.
Don’t Stop Me Now!
I want to talk more about the benefits of cells, like view inheritance, testability, reusing components, hooking them directly to routes, sharing decorators between cells and other layers, but.. I’m writing this blog post at work and feel like I should stop now.
And, did you know? Recent surveys have concluded that 79.3% of all cells users did not regret their choice and can’t do without cells anymore.
Let me know if you have any questions.
Here another Helpers Hater 🙂
I love the ideas behind the Cell concept, I think that adds REAL object orientation to views. My only objection is about the cell calling:
Seems too complicated for me, I will prefer something more encapsulated like:
Assuming .show call by default or something like that. What do you think?
LikeLike
can’t wait to try it out.
LikeLiked by 1 person
To start with helpers are not shit, they allow you to do a million of things, where you use it right or not is up to you.
Then adding yet another gem to do something you could do with a Presenter is just lame.
Keep your helpers clean, add view logic in Presenters, that way you will have clean views and all that shit you talking about in helpers will be gone.
LikeLike
I like this idea. One thing I will like to improve is the need of calling render in every defined method. If it’s default action, it should be called by default. Or even better, if method calls only render method, I might not have to declare it at all in the ViewModel class. This is similar to how controllers works. You don’t have to declare empty method in the controller, if the only thing it does is rendering view.
LikeLike
Like the concept. Having a hard time seeing how your example of the view using “author_name” corresponds to the author_link method you added. How does the view model connect the two?
LikeLike
This is interesting idea, I really like the encapsulation and extensibility the view model offers. But there are a few things that just feel wrong with the implementation.
1. I agree with Olga that if show is a default method then it should be called by default. I’d go further and say that naming the method show is just confusing (Imagine you’re in edit view and have a show view). Why not just call render by default.
2. The view model should be inferred from the input model. If it’s cell(@comment) then by default it will load CommentCell. If you have another implementation for comment then you can specify it with another method. cell(@comment, :related)
3. Like rails partial the view model should be smart enough to take a collection input. In fact, I think there’s nothing wrong with rails partial so I don’t think there’s a need to introduce a new set of template nested under the view model.
4. This is not a big issue, but the cell name is so arbitrary, why not just call it something more sensible like render_view or even implementing a new method for rails render.
LikeLike
Nosolopau, Quan: Love the idea of having a shortcut that infers the cell from the model. However, often you want to have several cells for one model, that’s why it is only explicit atm.
David: I explicitely say that I find helpers a good thing, I use form_for and friends everywhere. I do like the encapsulation that cells gives me, though, and also that cells does not allow me “a thousand things”. If you have a clean, maintainable way to implement helpers, please please do share it with me. I have seen a lot of horrible view code in the last decade, which indicated that something in Rails itself is wrong. I’m eager to see how you do that!
Olga: The render call can be made implicit, I agree. Also, the entire method could be infered. Keep in mind that you sometimes need to setup code in your cell methods. But the more I think about it, the more I agree.
Tim: Maaaan, thanks! I totally misspelled that, it’s fixed now. Sorry!
Quan: There is render_cell as a helper for rendering a cell. Pushing the cell views into a separate directory makes it extremely simple to move them around, track what belongs to who and integrates with the trailblazer concept layout. Give it a try, it feels better.
LikeLike
Great article, Nic. My view code currently makes me sad so I will definitely be trying these.
Is that a typo in the “Logicless Views” view code where you call ‘author_name’ but define author_link in your cell?
LikeLike
Ritchie: Thanks Ritchie, I already fixed that 🙂
LikeLike
Neat idea! In fact at Envato there has been similar pattern to your View model.
Good work mate
LikeLike
Great article! I’m going to put in practice this pattern. I have been started to use presenters and I want to use other patterns like this 😉
LikeLike
Thanks for the well-written article. While I like the idea at first glance, here are two things that come to mind:
– What is the advantage over presenters? You are basically encapsulating the model, just like a presenter would to. The only real difference I can see is that you make that new object the views default scope. However I agree that these patterns (presenters or “cells”) should be adopted more widely and people are often using helpers wrong.
– If your goal is to improve Rails (i.e. make sth like this the default) please keep in mind that it adds further complexity at a very basic layer that will make learning and teaching Rails harder. If Rails is supposed to remain popular (and thus maintained) in the future, people need to be able to grasp the basics quickly.
LikeLike
Pascal: Thanks for those points.
1. Good question! I didn’t illustrate that in this post on purpose: The advantage comes when using view inheritance, nested cells, caching, packaging, and so on. I’ve seen a lot of home-made solutions that poorly implement view rendering. Also, a cell completely encapsulates assets and code in one place whereas in Rails default stack this is spread across the entire framework.
2. Rails is on its way of becoming more and more unpopular because of the lack of abstraction layers – thousands of apps are at a point where they’re becoming too complex to be implemented the conventional Rails way.
Beginners don’t have to learn Cells, Reform, Roar or other additional layer gems right away. Nevertheless, the complexity this gem adds usually balances the complexity it reduces in your monolithic stack and makes it more maintainable. I absolutely keep that in mind, but thanks for pointing that out.
LikeLike
I already use this gem, plus Reform and I just love them 🙂 Great work, really.
However, instead of supporting helpers directly in the cell, why not use the `context` itself?
My case: render different forms for different kinds of products. I’ve setup a Cell::ViewModel that know how to render each product, but each form is implemented by a custom form builder based on `simple_form_for`.
Instead of clog up my class with other methods, I think it’s better reuse what we already have!
# products/_form.html.haml
= cell(:product_form, @form).show(self)
Here, `self` is packed with all our nice helpers, and is more than happy to help our little cell.
# cell
class ProductFormCell < Cell::ViewModel
attr_reader :helper
def show(helper)
@helper = helper
# render logic
end
end
This setup a nice method to expose helpers in a clean way to our template, that know can use all of them without performance penalty.
= helper.custom_form_builder(stuffs)
Hope you like this approach! Maybe it should be called `container`?
LikeLike
PS: rails lacks a clean template manager that offers a nice template inheritance system.
I work primly in PHP, that offers two really nice template manager: Smarty and Twig. The latter is more flexible and the first is faster, but both have a great template inheritance system, based on blocks.
You can basically replace or extend part of the page as you wish and save a lot of copy/paste. If you are curios, just google it 😛
LikeLike
lazel: That is a good idea to pass helper objects into the cell – I am a big fan of encapsulating behaviour. Thanks, this gave me some inspiration for a new form builder.
I absolutely agree on the PHP template systems – they have way better support, Rails view layer is too primitive. My plan was to blog about view inheritance in Cells soon, and hopefully, people will come up with ideas for simplifications, as this is still a bit clumsy. Thanks, lazel!
LikeLike
Glad to have inspired you, now I’m really curious to see the new form builder *-*
However you have also inspired me for template inheritance, I think thanks to ViewModel it should be quite easy. I’ll show you what I’ve come up as soon as I’ve time to write it 🙂
LikeLike
lazel: You know there’s template inheritance built into cells? When deriving a cell from a parent cell, views will be inherited if not found in the derived’s views directory. Since you can encapsulate any fragment of the widget into a separate method/view, you can partially override.
Let me know how you go!
LikeLike
Yep, I was thinking just about that, and it’s quite flexible, but having each piece of code in it’s own file… I don’t know, I don’t like it much. Now I’m experimenting with the good old `content_for`…
LikeLike
Nosolopau, Quan: isn’t defining #to_s method should just work?
LikeLike
Reblogged this on RoR.
LikeLike