7

What do you do when you want to use a gem for development/testing that you don't want to force other devs to use? Right now I have

begin
  require 'redgreen'
rescue LoadError
end

in test_helper.rb and no gem config, but that seems like a clumsy approach, albeit a functional one. I'd like to do something like the following:

config.gem "redgreen", :optional => true

Any other suggestions? Or should I just vendor those pretty superficial gems...?

EDIT

To be clear, I am only talking about those specific gems, like redgreen, which aren't actually used in the functional code, but only in the coding process. There is no need to vendor these at all, except to avoid the conditional require.

tfwright
  • 2,844
  • 21
  • 37

7 Answers7

7

Gems that are specific to your development environment should be installed in your gemset or local gems, but not in the Gemfile.

A classic example is the ruby-debug-base19x which Rubymine needs for debugging. This is installed in your local gemset, but not in the Gemfile because not all coders use Rubymine.

[EDIT]

Indeed, everything is run in the context of the bundle, and outside gems are not reachable. There do exist some workarounds indeed. Most of them are dirty :)

I found a lot of good solutions in this bundler issue.

The nicest solution was to add this to your .irbrc :

# Add all gems in the global gemset to the $LOAD_PATH so they can be used even
# in places like 'rails console'. 
if defined?(::Bundler)   
  global_gemset = ENV['GEM_PATH'].split(':').grep(/ruby.*@global/).first 
  if global_gemset
    all_global_gem_paths = Dir.glob("#{global_gemset}/gems/*")
    all_global_gem_paths.each do |p|
      gem_path = "#{p}/lib"
      $LOAD_PATH << gem_path
    end   
  end 
end

require 'irb/completion' 
require 'rubygems' 
require 'wirble'

Wirble.init 
Wirble.colorize

If you then install wirble to the global gemset, it can then be found. Original source: https://gist.github.com/794915

Hope this helps.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • This is the behavior I see: 1. `gem install wirble` (installed to project RVM gemset). 2. `rails console` (wirble not loaded) 3. `gem list` (from within console, wrapped in backticks--wirble not shown 4. exit and `gem list` from CLI: wirble shown. Perhaps RVM can be configured to sidestep this. If you can figure that out the bounty is yours – Eric Hu Nov 10 '11 at 22:16
  • I see what you mean. I found a working solution, hope it can help you as well. – nathanvda Nov 11 '11 at 10:47
  • This works for me. I'm running into an IRB specific issue with the `wirb` gem, but that could be setup-specific. I'm going to wait until the last 24 hours to award the bounty, just in case some super-clean non-obvious solution comes up. – Eric Hu Nov 15 '11 at 05:24
  • Bundler groups is exactly what we need for Development dependencies. – phil pirozhkov Nov 16 '11 at 08:40
2

I answered a similar question of my own here

User-level bundler Gemfile

One way to do this is to create different environments:

group :scott do 
end

Then

bundle --with-env=scott
Aryan Beezadhur
  • 4,503
  • 4
  • 21
  • 42
Scott Schulthess
  • 2,853
  • 2
  • 27
  • 35
  • Thanks for your answer. Wouldn't this alter the Gemfile.lock so that I have to re-bundle before pushing? – Eric Hu Nov 15 '11 at 05:22
  • Good question - yes it alters Gemfile.lock. But I don't think thats a problem. For example, when you add rspec in test mode that's added to the gemfile.lock but it doesn't affect prod – Scott Schulthess Nov 15 '11 at 15:36
  • It's an issue for Heroku users such as myself, since Gemfile.lock just lists all gems the machine depends on without separating them into dev/production/test. Heroku throws an error when the uploaded Gemfile.lock doesn't match its current version. Deploying this way means that I have to `bundle install -with-env=production` every time before pushing to production, and `bundle install` prior to pushing to our dev repo (otherwise my coworkers would have to `bundle install` after pulling to get _shared_ dev/test gems into Gemfile.lock) – Eric Hu Nov 15 '11 at 21:51
  • 1
    "Using the "test" group in this case allows you to specify the gems that are needed to test your application. Since you won’t need these gems in production, you can speed up installation by ignoring the "test" group. Bundler provides this ability through the --without option: bundle install --without test You can currently access this functionality on Heroku by setting the BUNDLE_WITHOUT config var in your application." – Scott Schulthess Nov 17 '11 at 21:46
  • The Heroku option is news to me, thanks for sharing that. The problem with using a bundler group of any kind is that it modifies `Gemfile.lock`, requiring more work out of me or my coworkers to share our changes amongst each other. It does solve the Heroku issue, though, so thanks for sharing this information though, I'm sure it'll help others who have different needs. – Eric Hu Nov 18 '11 at 00:06
1

Ok, I think I've come up with something. Basically, the idea is to only execute a secondary Gemfile when a Rails app is executing. To do this we add two things:

First, we alter the rails script a little:

# in ./script/rails

Kernel::IN_RAILS_APP = true

APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'

Second, we tell bundler to pull in the secondary Gemfile if we're in a rails app and a secondary file exists:

# Gemfile

if Kernel.const_defined?(:IN_RAILS_APP)
  local_gemfile = File.dirname(__FILE__) + "/Gemfile.local"
  if File.exists?(local_gemfile)
    puts 'using local gemfile'
    self.instance_eval(Bundler.read_file(local_gemfile))
  end
end

Now you can add a Gemfile.local to your project and run specific gems on a per-machine basis. bundle install works normally since the IN_RAILS_APP constant doesn't exist.

** Make sure to add Gemfile.local to your .gitignore.

mnelson
  • 2,992
  • 1
  • 17
  • 19
  • 1
    BTW, Gemfile inspiration taken from: [here](http://stackoverflow.com/questions/4244182/how-to-customize-gemfile-per-developer) – mnelson Nov 10 '11 at 06:50
  • This is similar to the solution I posted. The problem with both of ours is that my `Gemfile.lock` is different from that of my coworkers. Pulling each others' changes means that we have to do `bundle install`. The same is true for when we both push out to Heroku (it rejects pushes with differing `Gemfile.lock`). I made a script to take care of some of this when pushing out changes, but ideally I'd like to use something a little more elegant – Eric Hu Nov 10 '11 at 22:08
  • Upvote for the link though--good to know this problem has been asked elsewhere and doesn't have a simple, obvious solution – Eric Hu Nov 10 '11 at 22:21
  • The gemfile.lock wouldn't be different with this solution since bundle install wouldn't deal with the local gemfile at all. So all locks would be the same across all machines, but the rails app wouldn't start without the gems added to the gemfile.local (on the local machine) – mnelson Nov 10 '11 at 22:25
  • Hm, just tried it out and it didn't load the gems for me. You were right that Gemfile.lock wasn't affected though :P – Eric Hu Nov 10 '11 at 22:36
  • `rails s` allows the app to start even if the gems in gemfile.local aren't installed? – mnelson Nov 10 '11 at 22:45
  • I only tested it with `rails c`, but I assume that it would have the same lockout. – Eric Hu Nov 11 '11 at 02:00
1

In my opinions this is what environments are for. Fortunately there is also a way provided to do it with what is in your Gemfile, this is also how rails use it: groups

Pretty much use the environments the same way rails use it. Here is what you could find in your Gemfile:

group :test do
  # Pretty printed test output
  gem 'turn', :require => false
end

And here is what you can find in your config/application.rb

Bundler.require(:default, Rails.env) if defined?(Bundler) 

All you would need to do is to change your local environment settings and the others working with you won't be affected unless they decide to. Everything gets committed and nothing gets lost.

Here some links : http://yehudakatz.com/2010/05/09/the-how-and-why-of-bundler-groups/ http://gembundler.com/groups.html

Thierry
  • 339
  • 1
  • 8
0

I solved it by putting this in my gem file:

$gem_names ||= ENV['GEM_PATH'].split(':').map{|g| Dir.glob("#{g}/gems/*").map{|p|p.split('/gems/').last}}.flatten

gem 'redgreen' if $gem_names.any?{|n| n=~/redgreen/ }

That way the gem will only be used if you manually installed it on your system.

This works well but has the downside that it puts the gem name in the Gemfile.lock. This is of little consequence because the gem does not get installed with bundle install but it does make your lock file a bit messy and can cause the lock file to change a bit from one developer to the next.

If that is an issue for you another option is to keep the gemfile clean and require the gem by its full path, or you can add the path for just that gem. Like this:

$gem_paths ||= ENV['GEM_PATH'].split(':').map{|g| Dir.glob("#{g}/gems/*")}.flatten
$gem_paths.grep(/redgreen/).each {|p|$LOAD_PATH << p+'/lib'}
require 'redgreen' rescue nil
Jan M
  • 2,205
  • 21
  • 14
0

If you want it to be optional, it's better to freeze the gem as a plugin. However, it's not a good idea to use different gems than the rest of a development team, as it creates some inconsistencies in the codebase that can be hard to track down later. I would say add it to config.gem, and just tell the other developers to do:

rake gems:install

And you're done.

Mike Trpcic
  • 25,305
  • 8
  • 78
  • 114
  • How would using redgreen lead to inconsistencies in the code base? I'm really just thinking of benchmarkers, output sugar etc, not coding tools. Obviously code you use should be specd and/or frozen. – tfwright Jan 22 '10 at 19:00
  • I think regardless of what gems you use, the entire dev team should have the same suite, to ensure that bugs and issues are consitent across platforms. For example, I used Newrelic RPM on a project, and it caused bugs that nobody else encountered. – Mike Trpcic Jan 22 '10 at 23:05
  • I don't really see how this is a solution at all considering gems need to work with bundler – Scott Schulthess Nov 20 '11 at 00:18
0

This is how I tackled the same problem under Rails 3.1. In my Gemfile:

if File.exists? './tmp/eric_dev_gems'
  gem 'redgreen'
  gem 'awesome_print'
  gem 'wirble'
  gem 'wirb'
  gem 'hirb'
end

Create a file in ./tmp/ (or in some folder which is in your .gitignore) of your choosing. I used eric_dev_gems. This should be ignored by git, and will only exist on your system unless one of your teammates decides he wants to create that file too.

Eric Hu
  • 18,048
  • 9
  • 51
  • 67
  • I've discovered this method can be annoying because my Gemfile.lock will always be different from coworkers. There's also one gotcha: when we were deploying to Heroku, I couldn't push my changes because of the Gemfile.lock difference. In this case, I just renamed the file, ran `bundle install`, then pushed (and renamed the file back). It's possible to make a script to do this for me, but I'll look for a cleaner solution. – Eric Hu Oct 20 '11 at 01:31
  • Why do you list `wirble, wirb, hirb,...` in your Gemfile? Why not just install them locally or in the gemset? There is no dependency for the code, it is just your (and only your) development machine that needs them. – nathanvda Nov 10 '11 at 21:51
  • I tried installing them to the gemset. They're not available to `rails server` and `rails console`, as both are executed with bundle exec--correct me if I'm wrong, though – Eric Hu Nov 10 '11 at 21:59