Posts Tagged bundler

Using 2 sources for a gem in different environments with Bundler

Bundler makes life much easier for us Rails devs, but there is currently one thing on the wishlist that I would like to see implemented.  Bundler already lets you specify a local path for a gem and also lets you choose github as the source of the gem.  My problem is that Bundler does not allow you to add the same gem in development and production with different sources.  Why do I need multiple sources?  I work with quite a few custom gem engines in my rails apps and sometimes I run into issues that I need to debug or I find new features that I want to add to one of my gems.  It would be a real pain to git commit, push, bundle update and then restart my server every time I need to change something in one of my custom engines and view it in my rails app.

Here is what I was trying to do initially before I understood how Bundler worked with environment groups:

group :production do
  gem 'mygem', :git => 'git@github.com:me/mygem.git'
end

group :development do
  gem "mygem", :require => "mygem", :path => "/gem_dev/mygem"
end

Bundler no likey: You cannot specify the same gem twice coming from different sources.

I suspected I was not the only one that wanted something like this, so after looking at the github issue list I found a simple solution in the comments for this wishlist issue .  Use an environment variable and put a condition in your Gemfile like so:

if ENV['MY_BUNDLE_ENV'] == "dev"
  gem "mygem", :require => "mygem", :path => "/gem_dev/mygem"
else
  gem 'mygem', :git => 'git@github.com:me/mygem.git'
end

Don’t forget to set your environment variable in you command shell:

export MY_BUNDLE_ENV='dev'
bundle install

Now I can work with the gem from my local path. But, lets say I want to deploy to production. There are 2 ways you can approach the production deployment:
Option 1: For those that live on the edge, you will keep your Gemfile pointing to your master github repo
Option 2: For those who are scared to live on the edge, you should probably tag your code in github for your latest stable version and add your tag , :tag=>'v1.0.0' to the end of the production gem line.

Either way, you will need to install your bundle locally and then commit your Gemfile.lock (and Gemfile if changed) to github.  I tried a rake task to do this at first, but since rake loads your app, it will crap out and ask you to bundle install before the task can actually change the ENV var (I think that was the problem??).  I wasted quite a bit of time trying to figure out a workaround using rake with no luck, so against my will I created a simple shell script to speed up the process.

APP_PATH="/path/to/your/rails_app"
cd $APP_PATH
export MY_BUNDLE_ENV='prod'
bundle install
git commit -m 'new Gemfile' $APP_PATH/Gemfile
git commit -m 'new Gemfile.lock' $APP_PATH/Gemfile.lock
git push
export MY_BUNDLE_ENV='dev';
bundle update

This script sets your environment variable to “prod”, runs a bundle install and then commits and pushes your Gemfiles to github. Then it sets the environment var back to dev and runs a bundle update (don’t need install since you are using local paths) so you can keep working locally after the push.  Now your app has the proper gems in Gemfile.lock in your github repo and the last step is to deploy it to prod… I use Capistrano and my app server is Passenger.  Here is my recipe for the bundler step (ripped off from somewhere else of course ;-) )

namespace :bundler do
  task :create_symlink, :roles => :app do
    shared_dir = File.join(shared_path, 'bundled_gems')
    release_dir = File.join(current_release, 'vendor', 'bundle')
    run("mkdir -p #{shared_dir} && ln -s #{shared_dir} #{release_dir}")
  end

  task :bundle_new_release, :roles => :app do
    bundler.create_symlink
    run "cd #{release_path} && bundle install --deployment"
  end
end

after "deploy:update_code" do
  bundler.bundle_new_release
end

UPDATE: Looks like a deployment cap recipe is included in Bundler now, so you should probably use that instead. MAN, THESE GUYS MOVE FAST!!!
This recipe will run Bundler deployment after the code has been updated (so it can see the new Gemfile). Works well for me so far. It would be easier if Bundler supported the first syntax I tried, but this ain’t to difficult to add.

As always, comments and corrections are welcome.

UPDATE 2: This issue will be fixed in version 1.1 according to @wycats

mrreynolds: @wycats Any chance Bundler could support multiple sources for one gem per env? Otherwise we need such hacks:http://bit.ly/bjdk0C

wycats: @mrreynolds the :git/:path issue is scheduled for 1.1

Tags: ,

Working with private RubyGems in Rails 3

I have several gems that contain proprietary code that I do not want public to have access to.    Rubygems.org is great for gem hosting, but they do not allow security restricted gems, since it defeats the purpose of having an open community for ruby gems.  There are several options if you want to host your own gems and restrict access to them.  Here are some of the options that I considered

1. Install gemcutter and use that for a gem server.

2. Install the geminabox sinatra app.  This is a lightweight gem server with a web interface.

3. Use github.  They no longer build gems, but you can still install from github using Bundler (or a plain rake task).

I started playing around with the first 2 options, but I ultimately decided to just use the github solution.  Why waste resources setting up a private server when my code is already stored on github with security.  I am assuming you know how to set up a project on github, password protect it and push your gem source to github.

Git’r tagged

Probably one of the coolest parts of working with gems is being able to install specific versions of a gem in your project.. which is much easier than using vendor plugins.  In order to keep track of your versions you need to work with git tags.  You could also do this with branches, but it would get a bit hairy if you created a new branch for every patch version.  A good example of the proper way to set this up is the Ruby on Rails source on github – > http://github.com/rails/rails.  Click the drop-down where it says “Switch Branches”.  You can see they have a branch for every minor & major version 1.2, 2.0, 2.1, 2.2, 2.3, and 3.0 (master).  But notice that you do not see branches for minor versions like 2.3.8.  They use git tags to keep track of each version instead.  Click “Switch Tags” on the rails source github page.  Here you can see they created a separate tag for each version.  You will want to do the same with your private gems that you host on github.

Jeweler to the rescue

Luckily, jeweler has already done most of the work for us.  Install jeweler first:

gem install jeweler

Jeweler provides some nice shortcuts for creating, building and installing your gems. Take a look at the documentation for more info. I am only going to touch on the versioning that is included with this gem. After you create a project with Jeweler, you get a rakefile that gives you building and versioning tasks. Lets say you make a bunch of changes in your gem project, commit and push them to github and you are ready to bump this gem to a new version. You can use these Jeweler rake tasks to accomplish that:

rake version:bump:patch
#you can also use version:bump:minor and version:bump:major.. 1.2.3 -> 1=major, 2=minor, 3=patch
rake github:release
rake git:release

Jeweler also has a rake task called “release”. This will accomplish the 3 tasks above, but it also tries to push the gem to gemcutter. I could not find a way to turn that off in the documentation (although it may be possible.. I have not looked at the source in depth). I opted to write some custom tasks that combine these 3 tasks into a single task. So I added this to my rakefile:

namespace :version do
  desc "create a new version, create tag and push to github"
  task :github_and_tag do
    Rake::Task['github:release'].invoke
    Rake::Task['git:release'].invoke
  end

  task :patch_release do
    Rake::Task['version:bump:patch'].invoke
    Rake::Task['version:github_and_tag'].invoke
  end

  task :minor_release do
    Rake::Task['version:bump:minor'].invoke
    Rake::Task['version:github_and_tag'].invoke
  end

  task :major_release do
    Rake::Task['version:bump:major'].invoke
    Rake::Task['version:github_and_tag'].invoke
  end
end

The first tasks takes care of the git tag and github commit/push. The other 3 tasks use the first task and add the version bump for major, minor or patch. So now if I want to do a new patch version, I just have to execute one rake task.

rake version:patch_release

That is half of the equation. The other half is installing the gem from github in your Rails 3 project with Bundler.

Bundler takes care of the rest

Bundler ships with Rails 3 and its purpose is gem dependency management. I really like this tool so far, and the development seems to be moving along very fast. I learned a little trick when I was trying to see if something was fixed in Rails Edge. You can specify github as the source of the gem and it will pull the source code from github, build it and install it for you. You can even specify the tag which makes life even easier. So all I have to do is add one line to the Gemfile in my Rails 3 project and install the bundle. Check this out:

Gemfile

gem ‘mygem’, :git => ‘git@github.com:myuserid/mygem.git’, :tag=>”v1.0.2″
bundle install

Now that is easy!

Tags: , , , , ,