May 9, 2007

:partials are objects - too..

Posted by Sudhindra Rao

My recent adventures with ruby and rails have been on an actual application that will be used by 1000s of users. This application seems to suit perfectly for the design of rails. The good thing is that there are not many database transactions, mostly the users are expected to read and search for stuff on this website.

Many parts of this application are reusable. What changes most of the times is the way the parts look like when they are appear in different parts of the website. Also some times the details of those parts behave in different ways on different views(ajax/non-ajax, popup/tab-module, associated/unassociated, etc.).

This behaviour actually make them perfect candidates to be partials so that they can be programmed once and then the view can overlay and change what and how it shows up.

There are some things that really impress me about ruby - especially the part where everything is an object and the duck typing. Also having started doing real object oriented programming since I joined ThoughtWorks ( the other way of doing "OOP" is where I have a Universe class which knows about everything - that was so easy to do in those days), my mindset of trying to find objects has changed the way I write software.

With ruby - when used correctly - all these concepts fit perfectly. Ruby by design allows you excellent encapsulation - a very important feature of OO Design( eg. the duck typing - u only need to know whether it quacks like a duck - no internals are exposed when responding to a message). Rails tries to take these constructs in ruby one level further to the web application level, where the views also can be treated as objects(another important layer that can be treated like an object is the database layer using ActiveRecord).

What I like about the views in rails is that the views can be directly rendered from the actions(no clumsy xml mapping required). Also, the flexibility of using partials in my rhtml code.

A little bit about :partials - partials are something similar to 'include' tags in html where one can create parts of a webpage and bring them together using 'include file'. Creating multiple html files helps reuse of contents on a website - so that one can show a consistent header menu for example all through the website - and also keeps the html code DRY.

:partial builds on this idea. In rails one can create an rhtml file(which is actually interpretable ruby) that rails automagically handles for you to create html content. What can further be done is separate code that one wants to reuse in a file that starts with an underscore eg. _photos.rhtml.

There are multiple benefits of this breaking of code

  • code is now more readable - when u see a partial that you are not concerned about u can just ignore it and focus on the task u are doing(say u have _photos and _videos partials and you are currently working on the videos part - you dont have to worry about what the _photos partial is doing)

  • code is much more unit testable - rspec is excellent at testing :partials (My colleagues Mike and Jake can help you with that)

  • code is much more testable as a whole - mocking out partials at a time can greatly simplify testing - again rspec is your friend.

  • code is DRY

In your 'my multimedia page' (multimedia.rhtml) can look like
<% @some_variables %>
<% some_other_variables %>
render :partial => 'path/to/photos/photos.rhtml'
<% some_more_ruby_code %>

when _photo.rhtml looks like
<% @some_variable1 manipulation %>Tags: , , , , ,
<% @some_variable2 manipulation %>

The beauty here is the simplicity with which the _photos.rhtml can be separated. What rails does more is that the '@some_variables' that showed up in  multimedia.rhtml is automatically available to the :partial. Isn't that awesome one would say - and it is for a start.
But when there are many of these rhtml file that render more of these partials and all the variables are now the @variables one can get lost. Not only that the application is now getting too open - Let me explain.
With a tree of these partials multimedia -> photos -> photo -> photo_detail,  if we follow the above pattern, the @variables from multimedia would now be available in photo_detail and this is when everything starts falling apart. Now when you refactor photo_detail you have to worry about how the @variables.
This becomes more painful if you want to split the photo_detail and use the split portions together and separately at the same time. Then the problem of every :partial knowing too much about every other :partial - soon becoming a hairball so big that even cats will choke on them.
To prevent this from happening one should think about :partials as objects - this also goes well with the ruby philosophy of everything being an object.
So when I say :partials = objects all the laws of encapsulation apply.
Having said that the multimedia.rhtml now looks like so
<% @some_variables %>
<% some_other_variables %>
render :partial => 'path/to/photos/photos.rhtml', :locals => {:photo_variable => @some_variable1, :photo_caption => @some_variable2, :photo_credit => some_other_variable1 }
<% some_more_ruby_code %>

Notice that now the :partial can only care about the local variables that are passed to them it can be simplified to look like
<% photo_variable manipulation %>
<% photo_caption manipulation %>
<% photo_credit manipulation %>

Notice how all the @ signs disappeared in the :partial. Also note how I could pass in a local variable from the rhtml to the :partial.

This resolves a few issues :
- Unit testing becomes a breeze - Since the :partial is encapsulated testing the :partial in isolation is easy.
- Reusing the :partial becomes easy  - No more grand_parent to grand_son global variables
- refactoring within the :partial - not a problem - because you can write tests for this new functionality and refactor to create new partials - total insulation from the rest of the code.
- refactoring the caller of the :partial - even better since now you do not have to be afraid of breaking something because you removed an @variable.

This technique did help us refactor and increase our testing levels drastically.
Hope it helps you too.