View objects in Ruby with SimpleDelegator
View objects are given little love in Ruby because of Rails' lack of a true view layer in it's architecture. Let's look at some view object patterns that can help you scale your server rendered views.
Rails is a fantastic framework but I often find myself wondering where certain logic for the view should belong in a Rails project. I didn’t like how easy it was to write code in a global scope with Rails helpers and once I realized that Rails controllers and templates share the same context, I decided to introduce some intermediate view layer.
Enter, SimpleDelegator from Ruby’s standard library. I don’t want to start a naming war here so I’m going to call these objects ‘view objects’, and you’re free to interpret them as decorator/presenter/helper objects as you wish.
Basic example of how to use SimpleDelegator
:
SimpleDelegator works by implementing the method_missing
method that ruby calls when it can’t find a variable or method name. It then asks the delegate object if it responds to the apparently missing method implementation and if it does it calls it.
This means when we call first_name
in the UserView
, SimpleDelegator delegates the method invocation to it’s delegate object - the object you created the UserView with, which is the user object.
In the real world this is almost never sufficient, I need to pass in additional arguments/state to use in the view.
Example of simple delegator with additional arguments:
These are quite purely decoratoring the objects that are passed in, but are not yet doing much view specific work. Your web framework undoubtedly provides handy url and view helpers. So, what if I need to use a Rails url helper or link_to, you ask?
Cherry picking view helpers in your view objects:
Warning: If you need to use _url
helpers you need to provide the context with a default_url_options
method implementation. This can be done by using active support with the following change to the module, and then including Rails.application.routes.url_helpers
into the UserView
class instead of using helper methods.
I don’t use _url
routes very often so I opt for the simpler approach, and I like this approach opposed to passing in the view context as a parameter because you completely decouple these objects from the view and can instantiate and use them anywhere where there is no view context to pass in, like the Rails console, or a service object for example.
A pattern my friend @franks921 likes to use is encapsulating the entire interface for the view in a single object that then calls into view objects. I like this pattern because it reduces the mental overhead of keeping track of if the method being called or instance variable being referenced is defined in the controller or some random Rails helper. It also makes your view layer more isolated making it easier to compose, test and so helps me sleep at night.
Encapsulating the template layers interface to an object
If you get tired of writing ModelDecorator.new(model_instance)
you can use some metaprogramming to define a decorate
method passing in a model. Personally I prefer the code to be simpler, even if it is a little more verbose, so I opt for a little trick @frank921 on twitter uses.
Getting fancy with the spices
A simpler approach even if slightly more verbose
References
- Article by thoughtbot comparing view object strategies
- SimpleDelegator documentation
- Nick Sutterer on helpers - old but good rant
Questions? I’d be humbled and happy to help.