Dividing Our Views and Templates in Rails
TL;DR: I created a gem called simplest_view that makes it easy to move logic out of your Rails templates and into a class, in a conventional way.
You can see it on Github at tpitale/simplest_view
Feel free to start using it, but read on to see how I made it.
Is it a View or a Template?
Rails views are actually ERB templates. However, they're found under app/views. I think this manifests as a problem when our ERB templates grow in complexity as:
- more data (in more instance variables) is passed to the ERB template
- logic is embedded within the template
- none of this is sufficiently tested
My goal was to be able to put my code in views, rather than letting my templates handle logic. That meant I needed to split the view from the ERB templates, so I could have a real view object. The template's only responsibility should be the display of markup, json, xml, etc.
Moving the Templates
The easy part is the existing templates.
I started by moving any existing "view" templates from app/views into app/templates. To make my app work I added self.view_paths = ['app/templates'] to my ApplicationController.
If you're following along, you could run your tests and this should work.
A Little Spelunking
Now, for the fun part: views!
If you've ever done something like <% puts self.inspect %> inside of an ERB template, you'll see that it's just a class. To know what functionality it has and where it comes from, I had to dig into Rails just a bit.
The first thing I saw was that the class's ancestors include some interesting things like ActionView::Base and, of course, my ApplicationHelper that gets included. The real question is, 'Where does this view get made?'
And the Views
The answer is found within the rendering module that is mixed in with AbstractController. Within, I found two methods: view_context and view_context_class. An anoymous class is created, which inherits from ActionView::Base and is used as the context for any ERB templates.
I made some small changes to view_context_class to make it use my own view classes.
To start I made up a convention for the view class that mirrors the templates. Something like app/views/posts/index_view.rb will have the view for PostsController#index defined as class Posts::IndexView < ActionView::Base.
Using that class name, I just did what Rails 3 does, which is to create an anonymous class, and include the routing and misc helpers. This class then becomes the context for my ERB templates.
It will have access to all the instance variables from the controller, and the ERB template will have access to any methods defined on the view.
What I've got now is the simplest split between Rails templates and views. This lets me clean up any logic that may have crept into my ERB templates over many months or years of development.
You can see the completed code module on Github.
Test Drive
If you would like to try this out in your project, please give SimplestView a spin.
Next Steps
- It would definitely be a huge help if you could try it out, see how the approach feels in your project.
- If you can spot a better way to hook into the view_context, let me know.
- And lastly, open an issue if you find anything wrong or broken!
Thanks for reading!