That was a good idea (though I'm using the MYAPP_TARGET environment variable to identify the flavor of the web server.
Finally, I got it working. Here is how:
1) At the top of environment.rb I have this code:
MYAPP_TARGET = ENV['MYAPP_TARGET']
case MYAPP_TARGET
when 'FOO'
when 'BAR'
else
raise "Environment variable MYAPP_TARGET missing or invalid!"
end
2) Also in environment.rb (somewhere in the global namespace) I make sure that the sessions don't get mixed up:
ActionController::Base.session_options[:session_key] = "#{MYAPP_TARGET}_#{RAILS_ENV}"
3) Still in environment.rb, I make sure that logging goes to the correct place and that the database.yml is picked up from the correct folder. The folders I want to use are:
RAILS_ROOT/log/FOO
RAILS_ROOT/log/BAR
RAILS_ROOT/config/FOO
RAILS_ROOT/config/BAR
I placed these statements inside the block that is invoked with Rails::Initializer.run do |config|:
config.log_path = "log/#{MYAPP_TARGET}/#{RAILS_ENV}.log"
config.database_configuration_file = "config/#{MYAPP_TARGET}/database.yml"
4) The routes.rb file is a bit tricky.
What I wanted to achieve was to define common routes in the standard RAILS_ROOT/config/routes.rb file, and routes specific to either FOO or BAR in separate files. Ultimately, I decided to do this by adding customroutes.rb files to the target specific config folders:
RAILS_ROOT/config/FOO/customroutes.rb
RAILS_ROOT/config/BAR/customroutes.rb
In environment.rb, I needed this line of code inside the block that is invoked with Rails::Initializer.run do |config| to ensure that the correct customroutes.rb can be found:
config.load_paths += %W( #{RAILS_ROOT}/config/#{MYAPP_TARGET} )
The customroutes.rb files are trivial. They look somewhat like this:
module CustomRoutes
def self.addCustomRoutes(map)
# Add your target specific routes here
map.connect "dashboard", :controller => "FOO/dashboard", :action => 'show'
# ...
end
end
I modified the standard routes.rb file like this:
require 'customroutes'
ActionController::Routing::Routes.draw do | map |
map.connect ':controller/service.wsdl', :action => 'wsdl'
# Add custom routes
CustomRoutes::addCustomRoutes(map)
# default routes to follow
end
5) To allow implementing generic code as well as describing the subtle little differences, you should read this article.
Applying this technique, I can implement controllers like these:
RAILS_ROOT/app/controllers/dashboard_controller.rb: Generic DashboardController
RAILS_ROOT/app/controllers/FOO/dashboard_controller.rb: FOO spezialised DashboardController
RAILS_ROOT/app/controllers/BAR/dashboard_controller.rb: BAR spezialised DashboardController
I can also implement view templates in the correct location:
RAILS_ROOT/app/views/: Generic DashboardController templates
RAILS_ROOT/app/views/FOO/: FOO spezialised DashboardController templates
RAILS_ROOT/app/views/BAR/: BAR spezialised DashboardController templates
All this gives me a very powerful mechanism to overload precisely what needs overloading and no more. Of course, a bit of care needs to be taken to setup the routes correctly, but you'll no doubt find a clever way to do this yourself.
6) I had another small issue with my own vendor plugin that had slightly different flavours for the two targets FOO and BAR. I simply created target specific directories for target specific files:
RAILS_ROOT/vendor/plugins/mylib/lib/FOO
RAILS_ROOT/vendor/plugins/mylib/lib/BAR
In the RAILS_ROOT/vendor/plugins/mylib/lib/mylib.rb (which includes my library startup code) I added the following code:
MYLIB_BASE = File.expand_path(File.dirname(__FILE__))
MYLIB_CUSTOM = "#{MYLIB_BASE}/#{MYAPP_TARGET}"
$:.unshift MYLIB_CUSTOM
I can now have identically named files in RAILS_ROOT/vendor/plugins/mylib/lib/FOO and RAILS_ROOT/vendor/plugins/mylib/lib/BAR. When a file is referenced elsewhere with the require statement, it will load the file from the correct location.
7) In my case I wrote two command files (I'm a windows user) to start my server.
foo.cmd:
set MYAPP_TARGET=FOO
ruby script/server webrick -p 3000
bar.cmd:
set MYAPP_TARGET=BAR
ruby script/server webrick -p 3100
8) I did not address issues such as different graphic files, default templates, scripts, style sheets, etc. for different targets. I'm pretty certain though that similar techniques as applied and described above can be applied for that purpose.
Summary
Using the above technique, you can implement two or more slightly different front ends using the same code. The advantages are:
1) Enables you to describe the differences instead of copying and pasting nearly identical code resulting in reduced code and easier maintenance.
2) Allows you to test all versions concurrently so that you can verify a generic change has no adverse effect on one of the target implementations.
I hope this helps somebody.
Last edited by petehug (2010-03-24 18:18:59)