Saturday, September 17, 2011

More Productive Rails Engine Development via Symlinking

I have been using Rails Engines in Rails 3 quite successfully recently on a client project, enabling the team to reuse functionality that cuts through the MVC layers including front-end Javascript while still being able to customize it for the different web apps we are building.

The price we pay for such reuse though is having to keep updating the git repo reference in Gemfile for the web app on every Rails engine update.

Here is an example of that reference line in Gemfile:

gem 'national_search_map', :git => 'ssh://our_git_account@git.our_project_repo.org/var/git/national_search_map.git', :ref => 'f11671a'

Fortunately, I have found a remedy for that challenge by writing a rake task that creates a symlink for the Rails engine project within the gemset of the web app. That enables developers to make back-and-forth changes in both application and engine with no interruption until completely done working on their feature. Only then do they have to commit the engine code, get a new revision ref number, and then insert it in the web app Gemfile to ensure linkage to a fixed version of the engine code upon deployment.

Here is the code of the rake task I wrote:


namespace "engine" do
 desc "Symlinks an engine gem into a local project for development productivity purposes"
 task :symlink, :gem_name do |t, args|
   gem_name = args[:gem_name]
   gem_file_path = `bundle exec gem which #{gem_name}`
   gem_file_path_match = gem_file_path.match(/(.*)\/lib.*/)
   gem_path = gem_file_path_match[1]
   system "rm -rf #{gem_path}"
   project_gem_path = File.expand_path("#{Rails.root}/../#{gem_name}", __FILE__)
   system "ln -s #{project_gem_path} #{gem_path}"
   puts "Symlinking Complete: #{gem_path} now points to #{project_gem_path}"
 end
end

The rake task can be invoked via: "rake engine:symlink[engine_project_gem_name]". I have it saved under lib/tasks/engine.rake. If you put it in a Rails engine or gem that is reused across all your web apps, then all of them inherit that rake task and you would not have to duplicate it across projects (meta meta!)

Once you are done working in both engine and web app, have all tests passing, and are ready to deploy, make sure to follow these steps to lock a specific version of the gem in the web app for a safe predictable deployment:

  • commit engine code, obtaining a new git ref revision number
  • update Gemfile in web app to point to the new engine git ref revision number
  • bundle install the web app (this automatically gets rid of the rake task symlink) and then run rake again to ensure all tests are passing after linking to the engine via the git repository
  • commit web app code and deploy
That's all folks. You are welcome to share questions and experiences via comments.

1 comment:

OJ said...

See my post for an alternative way to develop gems via environmental variables rather than symlinking,

In brief, you can use logic in the Gemfile and load the gem from a local path

Thanks Andy for you post