Sunday, August 21, 2011

Genericizing Rails view templates

In keeping with my previous post, I have put together a way of greatly reducing the redundant code in Rails views. On the HTML side, this really only applies to the simple case of rendering the default REST views provided by Rails' scaffolding mechanism.

Rather than have an index.html.haml (or erb if you're a bad person ;) for every model you have, why not consolidate all of those into a single template? They all do the same thing anyway: render a table of objects along with a few actions. Using the wonderful render_inheritable gem, you can define a single index.html.x file in a central location (such as app/views/application) and have all of the customization occur at runtime. Thanks to render_inheritable, the 4 index files for my 4 models can be reduced to this:

You'll need to define a entry_class method in each of your controllers which returns the model's class. So in the UsersController you would have:

The same applies to the _form.html.x file. Using the wonderful formtastic gem, creating a form which works for all of the models is as simple as this:

These are all pretty trivial and contrived; in any real application you wouldn't reuse the exact same view for all of your models. Where it becomes more useful is when crafting API responses, which tend to have more regular formats. Using the great rabl gem, you can create view templates for JSON responses like you would for HTML responses. Instead of mucking about with to_json in your models or controllers, define the format of responses in a view, where it belongs.

Used with render_inheritable, you can do some truly awesome things with your JSON (or XML) output. To create an index template which returns a per-model customizable collection of objects, simply create a file at e.g. app/views/application/index.json.rabl with the following contents:

You'll need to define a function called entry_views_folder_path in your ApplicationController which returns e.g. users for responses concerning User objects endpoint or groups for responses with Group objects.

Doesn't look like much, but mix that together with base.json.rabl files for each one of your models, such as:

you now have a set of templates which render a collection of models, specialized for each class. That's pretty cool if you ask me.

Stay tuned for a way to render these customized API templates when errors are encountered, while still preserving the original error codes. It took me hours of digging through the Rails source to figure out.