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.
Related
I want to use awesome print without putting it in my rails 5 app. Just in the console. The documentation for requiring it in irb is not working.
That's because bundler isolates the gems available to load to what's in your Gemfile.
The best way to get around this is to add the gem to your Gemfile
gem 'awesome_print', require: false, group: :development
And in your .irbrc, you can require it, so that it is only enabled for you:
begin
require 'awesome_print'
rescue LoadError => err
warn "could not require awesome_print: #{err}"
end
However, if you aren't permitted to add awesome_print to your repository for whatever reason, there are a few hacks to get gems installed, but not in your Gemfile to load in this GitHub Gist.
One such example that could be placed at the top of 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
$LOAD_PATH.concat(Dir.glob("#{global_gemset}/gems/*/lib")) if
global_gemset
end
cd your/rails/project
irb
inside irb, run:
require 'awesome_print'
require './config/environment'
and you have both rails console and awesome_print gem while the gem is installed outside of the bundler.
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.
for a Ruby standalone script what Rails like deployment features such as Gemfile / "bundle install" etc
that is assuming you are developing a Ruby script that you want to test and then deployment, and perhaps ship to others, what Rails like deployment approach would you use for say:
a) GEM - marking GEM requirements & having them installed as required - e.g. Rails "Gemfile" where you mark what gems you need and then "bundle install" to install them
b) File Require - automatically loading *.rb files if they are in your script directory (I'm thinking of in Rails where if you put a class file in the apps/model directory Rails automatically load/require's the file for you)
depends on whether it's a tool you expect people to use on every host they find it on or not. also depends on whether the tool can be shared with pubic repos.
if it just has to work, without worrying about whether you've installed the gems via bundler already or not, you can use something like the following from within your standalone script to install gems if not already present (be mindful of system vs. user ruby):
#!/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 'active_support/all'
...
In my humble opinion, a gem is the way to go. Bundler makes it easy to get started; it starts a skeleton for you when you run the command…
bundle gem <GEM_NAME>
Take a look. As long as you specify your dependencies in your gem's .gemspec file, and somebody installs your packaged gem (they won't need bundler, just RubyGems' gem command), the dependencies will be installed as gems along with it.
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
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.