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