How to ignore a gem per computer? - ruby-on-rails

By some reason some of the developers use a gem and the others don't.
Sometimes I met code like that:
if File.exist?('USE_MY_GEM')
gem 'my_gem'
end
and USE_MY_GEM written in .gitignore. But I believe this is not very good practice.
How to turn a gem off / on for a specific machine?

I would go with Bundler groups feature. This way the Gemfile might contain the group, containing these superfluous gem[s] some developers use.
From the documentation:
Restrict the groups of gems that you want to add to the load path. Only gems in these groups will be require'able:
require 'rubygems'
require 'bundler'
Bundler.setup(:default, :ci)
require 'nokogiri'

Well, the solution was obvious.
We can manage gems in a way we usually manage groups.
Just wrap gem in a group and add --without option:
Gemfile
group :my_gem do
gem 'my_gem'
end
In console:
$ bundle install --without my_gem
Ensure that .gitignore contains:
/.bundle

Related

Bundler and bundler-only - suppress duplicate gem warning

I am using bundler on my gemfile for executing the application, and found it convenient to use bundler-only to be able to pick only a subset of gems that are needed for deployments only.
So on my deployment machine, I use bundle-only that will only install gems namespaced under the deploy group.
However, because of this I need to duplicate some gem (eg. my deployments need to send a notification to slack, so I have some notifier gems both in my global namespace and my :deploy-only namespace.
This cause several warnings
Your Gemfile lists the gem slack-notifier (>= 0) more than once. You
should probably keep only one of them. While it's not a problem now,
it could cause errors if you change the version of one of them later
Is there a way to suppress the warnings ? (if possible those gems only)
Don't list gems more than once. That warning is there for good reason.
You can group gems under multiple namespaces at once within a Gemfile, like so:
group :deploy, :somethingelse do
gem 'slack-notifier'
end
group :deploy do
# Deploy-ONLY gems
end
group :somethingelse
# Somethingelse-ONLY gems
end
Or if you prefer, you can do this grouping inline:
gem 'slack-notifier', group: [:deploy, :somethingelse]
For more information, read the bundler documentation on Gemfile groups.
One alternative is to maintain a list of all groups, and systematically include , groups: groups
# Gemfile
groups = [:deploy, :x, :y, :z, ...] # Maintain this list as you add groups
# Gems needed except in deploy
gem :a
gem :b
...
# Gems that are also required for deploy
gem :d1, groups: groups
gem :d2, groups: groups
# Gems that are required ONLY in deploy
group :deploy do
gem :dep_only1
gem :dep_only2
end
So the following works without warning
bundle --without deploy # Will ignore deploy group
bundle-only deploy # Will install only deploy gems including those that are also needed by the app

Bypassing bundler for auxiliary development gems

In the Gemfile of my Rails project, I am starting to have auxiliary gems like "ruby-debug19", "perftools.rb", or "irbtools". All of these really have nothing to do with the project, but rather are part of my local development setup. But since I'm using bundler, I cannot load these gems (even though they are installed system-wide) unless I add them to the Gemfile. In my view that is a bit of a code smell.
For example, I would like to be able to require 'irbtools' in rails console without adding "irbtools" to my Gemfile.
Is there a way to keep auxiliary gems out of the Gemfile and still be able to load them for debugging, profiling, etc. when I need them?
Actually, you can create a group in you Gemfile like:
group :auxiliary do
gem 'irbtools'
end
And then use bundle install --without auxiliary if you don't want to use irbtools. Why do you think adding them to Gemfile is a code smell? And if it possible to do this without adding gems to the Gemfile it will be many more code smell I think.
Thanks to this post I have a great solution.
Add this line at the end of your Gemfile:
eval(File.read(File.dirname(__FILE__) + '/Gemfile.local'), binding) rescue nil
Create a file called Gemfile.local.
Add your development gems to Gemfile local. For example:
group :development do
gem 'cucumber'
end
Add Gemfile.local to .gitignore.
Now you can add your auxiliary development gems without changing the Gemfile for other folks on the team. Very cool.
I put the code below in a file in my app root, so it's easy to load from irb.
If you want it in something like a rails server, you probably need to add the load statement to environments/development.rb etc. That still creates problems if you accidentally check that in, but it's less annoying than having to add it to the Gemfile and causing your Gemfile.lock to change also.
# Example usage:
# add_more_gems("ruby-debug-base19-0.11.26", "linecache19-0.5.13")
# or
# add_more_gems(%w(ruby-debug-base19-0.11.26 linecache19-0.5.13))
#
# Note that you are responsible for:
# - adding all gem dependencies manually
# - making sure manually-added gem versions don't conflict with Gemfile.lock
# To list deps, run e.g. "gem dep ruby-debug-base19 -v 0.11.26"
#
def add_more_gems(*gem_names_and_vers)
gem_names_and_vers.flatten!
gem_base = File.expand_path(Gem.dir)
gem_names_and_vers.each do |gem_name_and_ver|
# uncomment if desired
###puts "Adding lib paths for #{gem_name_and_ver.inspect}"
spec_file = File.join(gem_base, 'specifications', "#{gem_name_and_ver}.gemspec")
spec = Gem::Specification.load spec_file
this_gem_dir = File.join(gem_base, 'gems', gem_name_and_ver)
spec.require_paths.each {|path|
dir_to_add = File.join(this_gem_dir, path)
$: << dir_to_add unless $:.member?(dir_to_add)
}
end
end
# put your often-used gems here
add_more_gems(
%w(
ruby-debug-base19-0.11.26
ruby-debug-ide19-0.4.12
linecache19-0.5.13
)
)
Not sure if this would work for you. It depends on whether or not you're using RVM. If you are, then you could install those auxiliary gems into the #global gemset that is created automatically for every Ruby interpreter. The gems in the #global gemset are available to all project-specific gemsets by default. This way you won't need to clutter up your Gemfiles.

How does Bundler know what environment to use?

Here's probably a very "newbieish" question on Bundler, but I'm wondering how bundle install knows what environment to use or how to set it? Or do I even need to? My problem is that I've grouped my gems (in Gemfile) by environments and when deploying now I only want production gems to be installed.
At the top of the application.rb file you can see
# If you have a Gemfile, require the gems listed there, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env) if defined?(Bundler)
When Rails is booted, Bundler automatically loads all the dependencies for the :default group and current environment.
Please note that when you run bundle install, Bundler resolves and install dependencies for all the environments, unless you specify a --without option
$ bundle install --without staging development test
In production, you might also want to add the --deployment flag.
More info about bundle install.
You can use the "group" option in the gem deependency declaration. Check this ASCIICast: http://asciicasts.com/episodes/201-bundler

Rails optional gem config

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.
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.
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
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.
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
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.
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.
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

config.gem in environment.rb

Let's say in a Rails app you have some gems that you use in your app (we'll call them "primary gems") and you have vendored them for portability.
Let's say that those "primary gems" also require gems of their own - we'll call these "secondary gems".
When you are setting up your environment.rb, you have to say:
config.gem 'primary-gem'
for any of the gems you are using directly.
But, do you also need to say . . .
config.gem 'secondary-gem'
even if you are not using that gem explicitly in your app?
Or is it simply enough to include the gem in your vendor/gems directory for it to get picked up by your app?
At deploy time rails knows about your dependencies, so if you want to freeze your gems then you can run
rake gems:unpack:dependencies
to freeze them into the vendor directory.
At runtime however it's the gems job to load it's dependencies, and usually the gems do this, so a config.gem 'primary' should work.
No, you don't or at least you shouldn't. Each GEM specification should include it's own list of dependencies. When primary gem is installed, RubyGems automatically will install each gem dependency on cascade.
In other words, if A requires B that requires C+D, you only need to write
config.gem 'A'
When the command
gem install A
is run, RubyGems will resolve all the dependencies and install them.
You can view all A dependencies running (from a Rails project)
rake gems
Sometimes, a GEM author may forget to include some GEM dependencies in the specification. In this case you should specify them in your environment.rb to force the application to install them. Off course, it's also a good idea to contact the GEM maintainer so that it can fix the problem.

Resources