My original plan to not blog about conceptual problems in Rails for the next months has failed.
Too many discussions about presenters, decorators, object-oriented helpers and “oh-so-awesome-and-new” form objects I had to overhear. With Aaron’s great keynote and many comments on the aforementioned concepts, I feel the urge to clarify what’s a presenter, a view model, and a form object.
I completely agree with tenderlove when he says there doesn’t have to be a presenter library. Presenters (or decorators?) are usually composition objects that add presentation logic to attributes.
However, what many people ask for is the ability to map widgets, or fragments, or parts of their UI, to something in Rails. And this something has turned out to be a mix of controller code, before_filter, partials and helpers. And many people are not happy with this, as their widget is not encapsulated and not reusable across their app.
To summarize: what people want in order to implement widgets is
- A place in the file system for code and templates.
- An asset where to put that Ruby code.
- The ability to render partials in order to present their widget.
Especially the latter one is important and is what makes the difference between presenters and widgets: I want to render templates in order to present an arbitrary object in my UI. And I don’t want to hack ActionView in any way to achieve that.
This was my motivation to write Cells a good while ago. Instead of cluttering widget logic across the entire framework, there’s a new abstraction layer to solve this. It gives you a view model class where you put presentation logic, but it also lets you render templates.
app ├── cells │ ├── comment │ │ ├── cell.rb │ │ ├── show.haml │ │ ├── grid.haml │ │ ├── comment.coffee
I am not gonna argue any more whether or not you need Cells. Some people like it, some prefer using POROs and hack Rails’ rendering into that object to achieve what Cells does.
My point is that Cells view models give developers a defined structure and standard how to implement view fragments (not to speak about how Cells handles view inheritance, polymorphic views, caching, and more, hahahaha).
So next time you talk about presenters, ask yourself: Am I talking about a strictly attributes-decorating thing? Then that’s a decorator. As soon as this involves rendering of views, you might want to checkout view models.
The other thing I need to clarify is form objects.
We all know that the way validations are handled in Rails models is a mess. It breaks down as soon as you need to use a model in two different contexts, for two different forms. Everyone reading this blog post has felt the pain with
accepts_nested_attributes, which is supposed to handle deserialization of nested forms.
And this brings me to the point of this section. One job of a form object is validating an object graph (e.g. an album composed of songs with artists) and collect validation errors in the top object.
The other job is the deserialization of the incoming hash. And this is completely underestimated by Rails core. Deserialization is the actual problem of forms. Validating and bubbling up errors is easy.
How do I parse a hash into an object? Where do I attach this object? Do I create a new object for that hash fragment, or do I need to find an associated object in the database? How do I handle additional semantics like deleting objects and save? And, how do I prevent the persistence layer to get involved until validation is done?
This is the real issue a form object (the way we expect it) has to solve. Again, I’d like to point you to another gem called Reform. In Reform, a separate class takes care of that. You define validations and properties in a new class.
class AlbumForm < Reform::Form property :name validates :name, presence: true collection :songs do property :title validates :title, presence: true end end
Deserialization and validation are done with separated entities. While a representer internally takes care of deserializing the incoming hash, validation is handled by the form itself. Usually, you don’t have to worry about this as it happens automatically.
The upcoming Reform 2.0 is doing this in a very neat way, where you can use your Roar representer for parsing, and the Reform object for validation, making it reusable for both document APIs and UI forms. It’s possible to completely replace deserialization with your own code without losing the decoupling from the persistence that Reform gives you.
This is the result of years of work, running into problems, taking a step back, reconsidering, collecting feedback from hundreds of use cases, and so on.
Please don’t brand a form object as a validation, only. There’s more to it to solve the actual problem we have.
And, yes, ActiveForm started as a pure copy of Reform, and then got “re-implemented”. Let’s not fight.
Rails 5: Stillstand
One last thing: Rails 5 comes with a new “render anywhere” feature where
ApplicationController.render lets you render partials from virtually everywhere. While this might look startling first, this is the exact wrong way to go.
A globally accessable renderer is the lowest-level tool you can give a developer. Instead of providing new, object-oriented, abstraction layers to solve problems, Rails core resists the idea of introducing new concepts for the sake of Basecamp-compati.. sorry, backward-compatibility.
The result will be render calls from models, hundreds of different “presenter” implementations across Rails projects, and confused people who don’t know where to put their code.
I hope I managed to point out what’s a presenter, a view model and a form object.
My message is: There is gems to help you solving a lot of problems that have been around since Rails’ inception. These solutions are mature and used in thousands of production apps. Many people have put a lot of work into them.
The fact that Rails core now, after almost 10 years, slowly starts to pick up ideas like form objects, is a good sign. However, I am skeptical if view models and real form objects will ever make it into Rails core. Luckily, we got gems to fill the gap.