Rails optional gem config - ruby-on-rails

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

Related

Why does Bundler.require not require dependencies?

I am developing a gem at the moment. Here's how the .gemspec looks like:
gem.add_dependency 'activerecord', '~> 4.2'
...
gem.add_development_dependency 'rails', '4.2.5'
...
and here's my Gemfile:
source 'https://rubygems.org'
gemspec
I am setting up my main file, lib/my_gem.rb like so:
module MyGem
module Lib
end
end
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
Bundler.require
However, if I start bundle console from my gem's folder, the dependencies are not required:
$ bundle console
Resolving dependencies...
irb(main):001:0> Rails
NameError: uninitialized constant Rails
...
irb(main):002:0> ActiveRecord
NameError: uninitialized constant ActiveRecord
...
What am I doing wrong?
I believe dependencies included via the gemspec command in the Gemfile are not automatically required by Bundler.require. Only gems listed directly in the Gemfile itself are.
Additionally, gems included in only certain Bundler groups, like 'development', may not be required by a Bundle.require even if they were included directly in the Gemfile, you need to tell bundler to require groups other than default.
You can always require gems manually though, like require 'rails'. Bundle.require doesn't do anything but require gem for each gem in your Gemfile (or at least default group in your Gemfile), it's doing any magic other than looking up all the gems in your Gemfile and requiring them. Bundle.require is considered by some to be a bad practice anyway, you should just require the dependencies you need in the files you need them, some say. Although Rails doesn't agree, and Rails apps have their own somewhat complicated way of auto-loading things.
But if you are in a Rails app, as your example dependencies suggest... why are you doing any require 'bundler/setup' or Bundle.require yourself at all though, instead of letting Rails boot process take care of it? Rails boot process will take care of requiring the Bundler groups you probably expect too (like the "development" group when in Rails.env == 'development').
You can use the bundler api yourself directly like you're doing, it's not too hard. But Rails ordinarily takes care of this for you, and if you are using Rails, rails probably already has done a Bundler.setup and Bundler.require as part of Rails boot process.

require 'pg' returns true but fails to define PGconn and PG

In my TestLogger class, require 'pg' (version 0.16.0) returns true, but PGconn is not registered.
So far, only the server has this problem, and only in the TestLogger.
Other ruby code that does require 'pg' has no problem getting PG and PGconn.
And everything works on my developer machine (Mac).
So the problem only happens on the server (Debian), and only in TestLogger.
The failing environment is built like this: I have a folder containing autorun.rb, Gemfile (+fresh Gemfile.lock generated by running bundle install), and testlogger.rb. The autorun.rb script is run sometimes manually, sometimes from cron, but always doing cd $some_folder; cd autorun_folder before starting thus ensuring that the proper environment (rvm and Gemfile) gets loaded.
TestLogger looks like this:
# encoding: UTF-8
class TestLogger
def initialize(opt = {})
require 'pg'
##pg = PGconn.open :dbname => 'statuslog'
end
end
autorun.rb begins like this:
require_relative './testlogger'
$runlog = TestLogger.new
...
and the code dies with "PGconn missing", so it seems that require 'pg', while returning true, actually didn't really load PG and PGconn as it usually does.
The strange thing is that tasks that are started by autorun does also use postgres (require 'pg' and PGconn.open ...) and there it works. It uses the same table 'statuslog' and is run by the same user.
I've also checked $PATH and $: and my .rvm folders, looking for any pg.rb that could be found by require instead of the one want.
I found activerecord-jdbc-adapter-1.2.9/lib/pg.rb (which in turn loads PG) and heroku-2.35.0/lib/heroku/command/pg.rb -- could this be the issue and how could I check?
(copy & paste as per Simon B.'s answer for attribution - thanks Simon)
In testlogger.rb:
before = $LOADED_FEATURES.dup
require 'pg'
p $LOADED_FEATURES-before
This prints which files are actually loaded by require 'pg'
Since it was activerecord-jdbc-adapter, and since jdbc is for java/jruby, and since Simon uses MRI, the solution was to uninstall the jdbc adapter.
Uninstalling with
gem uninstall activerecord-jdbc-adapter
Worked

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.

Rails 3, Bundler, LoadError

I'm writing an app that will run scripts in a specified folder, and then record the numbers and graph them.
My problem is that if the script is a ruby file, the require statements fail inside the script because bundler seems to have done something funky with the load path.
Running rails runner Datasource.run_jobs fails:
class Datasource < ActiveRecord::Base
def self.run_jobs
require 'aws_sdb'
access_key_id = "REDACTED"
secret_key = "REDACTED" # In all actuality these woudln't be here.
#sdb = AwsSdb::Service.new(:access_key_id => access_key_id, :secret_access_key => secret_key)
Datasource.all.each do |ds|
puts "Updating #{ds.name}..."
unless #sdb.list_domains.include? ds.name
puts "#{ds.name} doesn't exist in SDB, creating a domain for it..."
#sdb.create_domain ds.name
end
#data = "TEST"
data = `#{RAILS_ROOT}/lib/plugins/#{ds.name}`
#sdb.put_attributes(ds.name, Time.now.to_i, data)
puts "Data Collected: #{data.inspect}"
end
end
has_many :graphs
end
(Basing this off your comments on the question)
You will need to add hpricot (and any other gem this needs) to your Gemfile so that they are made available by Bundler. Bundler is by far the easiest way to avoid gem conflicts and tomfoolery.
Imagine this situation: You somehow lose the gems that you have currently. Be this happening through a format or system change or any other reason. Whatever it is, you've lost your gems. How are you going to re-install all your gems? You could keep a list of them somewhere else yourself, but is this truly likely?
Bundler solves this problem by making you state what gems your application requires and only adding those gems to the load path, which is why you can't find hpricot. When you run bundle install the first time, this creates a Gemfile.lock which contains something like this:
GEM
remote: http://rubygems.org/
specs:
abstract (1.0.0)
actionmailer (3.0.0)
...
Because you commit this file to your source control "solution" of choice (be it Git, SVN, FTP, whatever, it's not important) you have a solid way of specifying the precise gems and precise versions of those gems that your application uses.
When/If your gems are wiped, you can simply clone your project again and run bundle install. Because the Gemfile.lock file exists, you'll have exactly the same gems you had originally, even if there were updates.
If you don't want the exact same gems, just run bundle update and this will ignore the specifications in Gemfile.lock and instead revert to depending on Gemfile to define them. This will check for new versions of gems and install them, updating the Gemfile.lock when it's done.
Honestly, I don't understand the Bundler hate. If you could explain in wider terms than "OMG IT SUCKS YEHUDA IS SATAN", I'd be much obliged.
Edit: WedTM asked for a sample Gemfile and related code:
In the Gemfile you'd have this:
group(:scripts) do
gem 'gem1'
end
To require these gems for your scripts:
require 'bundler'
Bundler.require(:scripts)
You may also wish to require the default gems too which you can do by just adding default anywhere to the arguments of require:
Bundler.require(:default, :scripts)
If this for some reason doesn't work I would imagine it would be because it can't locate the Gemfile. This can be fixed by setting the ENV['BUNDLE_GEMFILE'] to the path to the Gemfile.
I wonder if you might be able to use RVM to set up the ruby environment before running your scripts. Maybe something with a gemset like:
data = `rvm gemset use scripts; #{RAILS_ROOT}/lib/plugins/#{ds.name}`

How do I get an installed ruby gem included in Rails?

I am attempting to get a gem I installed working in a Rails application. I can require the gem just fine in a Ruby program that I run from the command line using:
require 'nokogiri'
But when I attempt to do the same in one of my Rails controllers it errors saying "no such file to load -- nokogiri".
I tried using the full path to the lib/nokogiri.rb file, but that fails because it cannot find "nokogiri/native".
Better, place the following in your environment.rb file:
Rails::Initializer.run do |config|
...
config.gem :nokogiri
...
end
This will tell Rails that you depend on that particular gem. It also allows you to specify particular versions, and it will automatically keep all your gems synched, or unpack them into vendor/gems if you so wish.
I had a similar error but simply forgot to put the following in my environment.rb file: (note the quoted "nokogiri")
Rails::Initializer.run do |config|
...
config.gem "nokogiri"
...
end
Ok I figured it out. This is going to sound pretty stupid...but oh well...
It turns out I had two installations of ruby on my machine. I use InstantRails to serve my test applications and it comes prepackaged with an installation of ruby. I had another installation however outside of this and it was here that nokogiri had been installed, not in the installation in InstantRails.
In any case they were looking in different spots for the gems.
Try the following
require 'rubygems'
gem 'nokogiri'
If you are on some form of *nix then did you get any errors when you installed the gem, particularly errors stating that the gem was not on the path. This may happen if you have installed the gem as yourself rather than as root and you do not have your personal gem library in your gem path.
If you always install your gems using
sudo gem install some_gem_name
then you should not get that problem.

Resources