My team used Draper a few years back ago and its design inspired us to make poor decisions that we’ve come to regret.
If you want presenters in Rails (or “view models”, or “decorators” as Draper calls them), just use a plain old Ruby object. Pass in the view context when you need it. Don’t decorate. Don’t delegate all the things.
The problem with Draper
Draper encourages you to do things like
1 2 3 4 5 6 7
1 2 3 4 5
I think this is problematic.
Using the name
@article for what is actually an
ArticleDecorator instance muddies the object responsibilities.
If you want to grep for where a certain presenter (“decorator”) method is called, that’s a bit difficult.
If you look at a view or partial and want to know what object you’re actually dealing with, that’s not clear.
Draper jumps through hoops so that Rails and its ecosystem will accept a non-model in forms, links, pagination etc. This is complexity that can break with new gems and new versions of Rails. I can’t speak to the current state of things, but this abstraction had a number of leaks back when we used it.
For you to be able to use helpers and routes with Draper, there’s some fairly dark magic going on. That’s also complexity that might break with new versions of Rails, and no fun debugging if there are issues. This magic made it difficult for us to test in isolation, but perhaps that has improved since.
On a side note, I really disagree with the “decorator” naming choice. The decorator pattern is applicable to any component in a MVC system: you can decorate models, controllers, mailers and pretty much anything else. Using that wide name to mean only a narrow type of model decorator seems like a bad idea.
Draper also makes some poor choices in the small: accessing the decorated model as
object instead of
article hurts readability.
What should you do instead? Just use plain old Ruby objects.
1 2 3 4 5 6 7 8 9 10 11 12 13
1 2 3 4 5 6
This is not much more code than a Draper decorator.
With our attr_extras library you could even get rid of the initializer and reader boilerplate:
1 2 3 4 5 6 7
And you can of course add whatever convenience methods you like, by inheritance or otherwise.
The model is available to the presenter as
article rather than by a generic name like
You no longer have that complex, black magic dependency.
Forms and plugins just use regular ActiveRecord objects.
When you use a presenter, that’s made perfectly clear. If it makes sense to do some limited delegation, that’s just a
delegate :title, to: :article away.
It’s obvious where the view context comes from. If you use it a lot, feel free to pass it into the initializer instead of into each method (e.g.
ArticlePresenter.new(@article, view_context) in the controller).
I can’t see why anyone would prefer Draper to this, but I’m looking forward to discussion in the comments.