View objects in Ruby with SimpleDelegator

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.

Example of simple delegator

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

Links

Simon van Dyk

Read more posts by this author.

Johannesburg, South Africa