Why does Bundler.require not require dependencies? - ruby-on-rails

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.

Related

Rails 5 Require a dependent gem conditionally in a gem

We are writing a gem that includes multiple common gems used in a couple of our apps. We want to be able to have some kind of config to require or not require a certain gems. But the application.rb or enviroment.rb/*rb is loaded after gems is loaded. So we can not set a config there. The following way works but its not a good idea and I was wondering if there is a cleaner way to do this
bin/rails . (In consuming app)
#!/usr/bin/env ruby
DISABLE_TRANSPORT = true
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
Gemfile (In consuming app)
gem 'fruit_chain'
fruit_chain/lib/fruit_chain.rb (Our gem)
require analytic
require transport unless defined?(DISABLE_TRANSPORT) && DISABLE_TRANSPORT
require marketing
...
module FruitChain
end
I'm not sure to understand what you want exactly... however, use require: false in the Gemfile may help you ?
# Gemfile
gem 'my_super_gem', require: false
And in an initializer you require them:
# config/initializer/fruit_chain.rb
require 'my_super_gem'
I've encountered the same problem and have only thought to include the gems explicitly as a responsibility of the app, and then injecting that gem into the parent gem through a configuration, and ensuring they conform to the same interface.

Ruby gems in stand-alone ruby scripts

This is a really basic ruby gems question. I'm familiar with writing simple ruby scripts like this:
#!/usr/bin/ruby
require 'time'
t = Time.at(123)
puts t
Now I'd like to use my own ruby gem in my script. In my rails project I can simply require 'my_gem'. However this doesn't work in a stand-alone script. What's the best/proper way to use my own gem in a stand-alone ruby script?
You should be able to simply require it directly in recent versions of Ruby.
# optional, also allows you to specify version
gem 'chronic', '~>0.6'
# just require and use it
require 'chronic'
puts Chronic::VERSION # yields "0.6.7" for me
If you are still on Ruby 1.8 (which does not require RubyGems by default), you will have to explicitly put this line above your attempt to load the gem:
require 'rubygems'
Alternatively, you can invoke the Ruby interpreter with the flag -rubygems which will have the same effect.
See also:
http://docs.rubygems.org/read/chapter/3#page70
http://docs.rubygems.org/read/chapter/4
You could use something like this. It will install the gem if it's not already installed:
def load_gem(name, version=nil)
# needed if your ruby version is less than 1.9
require 'rubygems'
begin
gem name, version
rescue LoadError
version = "--version '#{version}'" unless version.nil?
system("gem install #{name} #{version}")
Gem.clear_paths
retry
end
require name
end
load_gem 'your_gem'
It is to be noted that bundler itself can deal with this. It's particularly interesting since bundler ships with Ruby by default since version 2.6, and you don't need to install it manually anymore.
The idea is:
to require bundler/inline at the top of your script,
to use the gemfile method, and declare the gems you need inside a block, like you'd do in a Gemfile,
after the end of this section, your gems are available!
For instance:
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'rainbow'
end
# From here on, rainbow is available so I can
# print colored text into my terminal
require 'rainbow'
puts Rainbow('This will be printed in red').red
The official documentation can be found on bundler website: bundler in a single file ruby script
Installing gems with something like the following should work. Be mindful of whether gems should be installed as part of system ruby or a user's.
#!/usr/bin/env ruby
require 'rubygems'
def install_gem(name, version=Gem::Requirement.default)
begin
gem name, version
rescue LoadError
print "ruby gem '#{name}' not found, " <<
"would you like to install it (y/N)? : "
answer = gets
if answer[0].downcase.include? "y"
Gem.install name, version
else
exit(1)
end
end
end
# any of the following will work...
install_gem 'activesupport'
install_gem 'activesupport', '= 4.2.5'
install_gem 'activesupport', '~> 4.2.5'
# require as normal (since not all gems install & require with same name) ...
require 'active_support/all'
...
I'm not sure if I understood your question right, but perhaps you don't have a gem, even if you write it (you are a beginner, so perhaps you misunderstood the concept of gems).
Just to be sure: You have a gemspec for your gem? If not, then you have no gem, but a single script.
When you want your own script inside another script, you may just do:
require 'my_script'
With ruby 1.8 this works fine, if my_script.rb is in the same folder as your main script. With ruby 1.9+ you can use:
require_relative 'my_script'
There is no need of a gem in this case.

Loading gem at runtime in Rails 3

I have Rails 3.0.x application. I would like to load gems on runtime, without using Gemfile.
What I would like to accomplish is to load my application as usual, with regular gems being loaded by Bundler. After that I would like to load all gems (Rails Engines) located in a specific directory (but until runtime, I don't know what gems will that be).
Does anybody know if this is possible in Rails, maybe using Bundler API?
What you're trying to do is dangerous. If each of your Rails Engines are also gems - then they would also have Gemfiles with other dependencies, and those would in turn have other dependencies, etc. If you allow Bundler to resolve those, then you would have lesser problems at runtime.
Here's how you would do it without any hacks. Remember that your Gemfile is just Ruby code, and you can have gems which are not loaded by default.
# In your Gemfile, add at the end:
Dir[YOUR_RAILS_ENGINES_SUBFOLDER + "/*/*.gemspec"].each do |gemspec_file|
dir_name = File.dirname(gemspec_file)
gem_name = File.basename(gemspec_file, File.extname(gemspec_file))
# sometimes "-" and "_" are used interchangeably in gems
# for e.g. gemspec_file is "engines/my-engine/my_engine.gemspec"
# dir_name will be engines/my-engine
# gem_name will be my_engine
# Register that engine as a dependency, *without* being required
gem gem_name, :path => dir_name, :require => false
# e.g. this is similar to saying
# gem 'my_engine', :path => 'engines/my-engine', :require => false
end
Now you have all your dynamic Rails engines registered as gem dependencies. Bundler will resolve them, and all their sub-dependencies, so you don't have to worry about anything. Just run bundle install once before running the application, or whenever you add/remove any engine in that folder.
The good thing is, these gems will just be registered, and not loaded. So in your production code, you can now load whatever gem that you choose at runtime simply by saying require <your-engine-name>
Edit: Extra code comments
Try this:
Bundler.with_clean_env do
# require gems...
end

Bundler.require does not work for ActiveRecord in my gem

I just created a new gem (using bundler) and want to add Active Record support. So I added s.add_dependency "activerecord", "~> 3.0" to my gemspec. Then I use Bundler.setup and Bundler.require and thought that I have access to Active Record now, but I haven't. I have to explicitly use require "active_record". Any idea why Bundler.require does not work for me in that case?
Firstly, if you're packaging a gem, do not use Bundler.require. Bundler.require is for apps not gems.
In .gemspec, specify the dependencies of your deployed gem.
In your Gemfile, include the line gemspec to automatically include the dependencies listed in your .gemspec in your Gemfile.
You may also optionally create gem groups for dev and test.
In your code, explicitly require any libraries you need.
I lost a couple of hours on this today so I hope this helps.
(Sources 1, 2)
Secondly, though the ActiveRecord gem is called "activerecord", the lib is called "active_record". This is what you would need in Gemfile.
gem 'activerecord', :require => "active_record"
Unless you include the :require option, ActiveRecord won't be loaded correctly and you won't know about it until you try to use it.
If you want use Bundler you need define your Gemfile with Activerecord
gem 'activerecord', "~> 3.0.0"
Or you need define bundler to use your gemspec with adding gemspec in your Gemfile
gemspec
See http://gembundler.com/rubygems.html
I had this problem, and the issue in my case was that I was naming a directory in my gem active record, as in:
lib ->
active_record ->
base.rb <- containing some monkey patches to base
This was causing mass confusion including sweet error messages like:
Gem Load Error is: uninitialized constant ActiveRecord::Base
Did you mean? ActiveRecord::Base
Simply moving changing the file from lib/active_record/base.rb to lib/active_record_base.rb fixed it for me.

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

Resources