Wednesday, April 15, 2009

Implementing Template Method in Rails Controllers Using Module and Mixin

All rails controllers are subclasses of the ApplicationController class. A typical controller class declaration will look like the following-

class LoginController < ApplicationController
#the actions go here
end

With this basic information, I would like to state the problem first.


The Problem/Job in Hand

Add an action switch_project to all the controllers (> 20) of the ScrumPad code base. The implementations of the switch_project method will be same for all the controllers only other than the fact that, the switching destination will be different.


Analysis

Placing the switch_project action in the ApplicationController would be the best option. But, the methods of application controller are not accessible as actions. So, the following won’t work


class ApplicationController
def switch_project
if(is_valid_switch?)
logger.debug(“A switch is taking place”)
destination = get_destination(new_project_id)
redirect_to destination
end
end
end

if you hit http://server/login/switch_project you will get a server side error. However, if you instead place the switch_project inside the LoginController, it will work fine. But, of course at a cost. You need to copy/paste this method 20+ times as there are 20+ controllers with the same need! Horrible!


Again, as I said, the get_destination(new_project_id) is the only part that will be different for each of the controllers. So, we definitely find a template method here.


The Solution

If you need an introduction about Module and Mixin in Ruby, please read here at ruby-doc. We are going to use Mixin to implement the desired solution, efficiently.

So, I put the switch_project method in a module called, ProjectSwitchModule inside a new file at app/controllers/project_switch_module.rb like this-


module ProjectSwitchModule
def switch_project
if(is_valid_switch?)
logger.debug(“A switch is taking place”)
destination = get_destination(new_project_id)
redirect_to destination
end
end
def is_valid_switch?
#I determine if the switch is valid at this method and return boolean
end
end

To make it available to all my controllers, I include this module in just in the ApplicationController in the following way-


require ‘project_switch_module’
class ApplicationController
include ProjectSwitchModule
end

Also, to provide controller specific implementation of the get_destination(project_id) method, I am just writing this method in each of the controllers in the following way-


class LoginController
private
def get_destination(project_id)
#custom destination logic for LoginController
end
end
class MessageController
private
def get_destination(project_id)
#custom destination logic for MessageController
end
end

Now, if I invoke http://server/login/switch_project or http://server/message/switch_project I will get the result of the switch_project action. So, this gives us an elegant way to follow design patterns for efficient implementation. It will save a lot of my time in future when I need to change the switch_project method, since I just need to change at a single place instead of 20s.


Afterthoughts

If, for a controller the switch_project needs to be different from the template at the module, it is achieved simply by overriding the switch_project inside the controller. No worries!


I will appreciate any feedback on this article.