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.
"Code Block" can be one other alternative way.