Noticed today that running require fileutils in pry always returns false. But, if I do the same thing in irb, it returns true. Furthermore, when fileutils is set to false, it still works!
First I thought that fileutils is a dependency of pry, and that would explain why it is already required. But it's not. Am I missing something fundamental here?
[07:40] ~/Desktop $ gem dependency pry
Gem pry-0.10.2
bundler (~> 1.0, development)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
Gem pry-theme-1.1.3
bundler (~> 1.0, development)
coderay (~> 1.1)
json (~> 1.8)
Example:
First I use pry to make some folders. Is fileutils already loaded? Notice that even though fileutils returns false it still works:
[07:44] ~/Desktop $ ls
[07:44] ~/Desktop $ ########## (nothing)
[07:44] ~/Desktop $ pry
[1] pry(main)> require 'fileutils'
=> false
[2] pry(main)> FileUtils::mkdir_p 'foo'
=> ["foo"]
[3] pry(main)> Dir.mkdir('bar')
=> 0
[4] pry(main)> Dir["/home/caveman/Desktop/*"]
=> ["/home/caveman/Desktop/foo", "/home/caveman/Desktop/bar"]
I remove the folders I just created...
[07:48] ~/Desktop $ rm -r *
[07:48] ~/Desktop $ ls
[07:48] ~/Desktop $ ########## (nothing again)
Now I try again with irb:
[07:49] ~/Desktop $ irb
irb(main):001:0> require 'fileutils'
=> true
irb(main):002:0> FileUtils::mkdir_p 'foo2'
=> ["foo2"]
irb(main):003:0> Dir.mkdir 'bar2'
=> 0
irb(main):004:0> Dir["/home/caveman/Desktop/*"]
=> ["/home/caveman/Desktop/bar2", "/home/caveman/Desktop/foo2"]
Here's what blows my mind. Even after I set 'fileutils' to false in irb, it still works:
irb(main):005:0> require 'fileutils'
=> false
irb(main):006:0> FileUtils::mkdir_p 'foo3'
=> ["foo3"]
irb(main):007:0> Dir["/home/caveman/Desktop/*"]
=> ["/home/caveman/Desktop/foo3", "/home/caveman/Desktop/bar2", "/home/caveman/Desktop/foo2"]
First off, FileUtils isn't a gem, so it will never show up as a gem dependency.
Secondly, the return value of require doesn't tell you whether requiring succeeded or not (if it fails, it will raise an exception). false just means it's already loaded. Your statement
Even after I set 'fileutils' to false in irb
doesn't make sense. You're not setting anything at all, you're just calling require again and the second time it returns false.
What you've observed just means that pry (or one of its dependencies) either loads FileUtils directly or loads a bit of the standard library that loads FileUtils. It looks like pry requires 'tempfile', which itself requires 'tmpdir' which in turn requires 'fileutils'.
Related
When I use Tempfile class in pry, I don't use require it.
% pry -f
pry(main)> Tempfile
Tempfile < #<Class:0x00007fb5121149b8>
But when in irb I must to require tempfile first.
% irb
irb(main):001:0> Tempfile
NameError: uninitialized constant Tempfile
from (irb):1
from /Users/ironsand/.rbenv/versions/2.4.3/bin/irb:11:in `<main>'
irb(main):002:0> require 'tempfile'
=> true
So it seems pry load some libraries by default.
Which libraries are actually loaded?
This is my environment
% ruby -v
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]
% irb --version
pirb 0.9.6(09/06/30)
% pry --version
Pry version 0.11.3 on Ruby 2.4.3
You can compare $LOADED_FEATURES to see everything that's loaded in a default pry session vs an irb session (or a plain ruby script).
The short answer is that Pry loads whichever libraries it needs to provide its own behaviour. The long answer is too long (and too likely to get out of date, or differ between versions) to list here -- better to ask your current environment.
It's good practice not to rely on other libraries to load their dependencies for you, because those dependencies can change. (Though in cases like this, it can be hard to notice you're missing a require, because everything still works.)
As an alternative to $LOADED_FEATURES, which can be a bit excessive, consider:
Gem.loaded_specs.values.each {|s| puts s.name}
which only lists gems with specs
[20] pry(main)> Gem.loaded_specs.values.each {|s| puts s.name};nil
did_you_mean
coderay
method_source
pry
io-console
vls
=> nil
BTW, in this example, I had loaded the vls gem manually.
I'm doing some messing around to try to understand better how bundler works. I have just three files in my working directory:
Gemfile Gemfile.lock test.rb
All the Gemfile has is gem "slop" and test.rb looks like this:
puts Slop.parse
When I run bundle exec test.rb I get a NameError due to not having a require statement:
[ec2-user#xx my_app]$ bundle exec ruby test.rb
test.rb:1:in `<main>': uninitialized constant Slop (NameError)
But if I run bundle console, Bundler loads the gem correctly and I can run Slop.parse from the console without having to explicitly type require "slop":
[ec2-user#xx my_app]$ bundle console
irb(main):001:0> Slop.parse
=> #<Slop::Result:0x00000001339838...
So what am I missing? I was under the impression that since I don't have require: false in my Gemfile, Slop should be loaded when I run bundle exec ruby test.rb and I shouldn't need to put the require "slop" line in the file.
You need to config bundler to require all gems on your Gemfile like this:
require 'rubygems'
require 'bundler/setup'
Bundler.require(:default)
Check the docs at http://bundler.io/v1.12/bundler_setup.html
I was under the impression that since I don't have require: false in
my Gemfile, Slop should be loaded when I run bundle exec ruby test.rb
and I shouldn't need to put the require "slop" line in the file.
The bundler docs say:
Specify your dependencies in a Gemfile in your project's root:
source 'https://rubygems.org'
gem 'nokogiri' #<======HERE
Inside your app, load up the bundled environment:
require 'rubygems'
require 'bundler/setup'
# require your gems as usual
require 'nokogiri' #<========AND HERE
As for this:
I was under the impression that since I don't have require: false in
my Gemfile, Slop should be loaded when I run bundle exec ruby test.rb
and I shouldn't need to put the require "slop" line in the file.
The bundler docs are horrible on this point. As far as I can tell, :require => false is a Rails specific thing, which is used to decrease load times on project startup. In a rails app, specifying require: false means that the gem won't be loaded until you manually require the gem. If you don't specify :require => false, then the gem will be loaded automatically--however that is because rails code is written to do that automatic loading. Your app has no code that performs a similar function.
Edit: Made a mistake while testing. So here's the way it works: In a non rails app, e.g. in your test.rb, if you want to automatically require all the gems specified in your Gemfile, you need to write:
Bundler.require :default
The Bundler docs mention that in the fine print here:
Specify :require => false to prevent bundler from requiring the gem,
but still install it and maintain dependencies.
gem 'rspec', :require => false
gem 'sqlite3'
In order to require gems in your Gemfile, you will need to call Bundler.require in your application.
I'm not sure why that requirement was only mentioned in conjunction with require: false instead of being stated at the outset.
And, in your Gemfile if you specify:
gem 'slop', :require => false
(as well as Bundler.require :default in test.rb), then you also have to explicitly require the slop gem in test.rb:
require 'slop'
In other words, Bundler.require :default automatically requires all the gems in your Gemfile except the gems marked with require: false. For gems marked with require: false, you have to manually write require 'gem_name' in your app.
Therefore, neydroid posted the correct solution.
* In your Gemfile, you can nest gems in groups, which affects how Bundler.require() works. See the Bundler docs on groups.
You should add the require "slop" inside your test.rb
What's the difference between the require and gem methods?
For example, what's the difference betweenrequire 'minitest' and gem 'minitest'?
Say you have two versions of the gem foo installed:
$ gem list foo
*** LOCAL GEMS ***
foo (2.0.1, 2.0.0)
If you use only require, the newest version will be loaded by default:
require 'foo' # => true
Foo::VERSION # => "2.0.1"
If you use gem before calling require, you can specify a different version to use:
gem 'foo', '2.0.0' # => true
require 'foo' # => true
Foo::VERSION # => "2.0.0"
Note: using gem without subsequently calling require does not load the gem.
gem 'foo' # => true
Foo::VERSION # => NameError: uninitialized constant Foo
Kernel#require activates the latest version of a gem.
Kernel#gem (added by RubyGems) allows activation of specific gem versions.
While trying to solve Gem found in irb, not in Ruby , I tried seeing what effect require 'rubygems' had on my own installation:
$ irb
irb(main):001:0> RUBY_VERSION
=> "1.8.7"
irb(main):002:0> $:
["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/x86_64-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/x86_64-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/x86_64-linux", "."]
=> nil
irb(main):003:0> require "rubygems" # Hasn't been required already
=> true
irb(main):004:0> require "rubygems" # Will return false, because it's already been required
=> false
irb(main):005:0> $: # Same as before
=> ["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/x86_64-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/x86_64-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/x86_64-linux", "."]
If requring rubygems doesn't modify $:, then how does it help find files? Does it monkeypatch require? If so, what variable does it use to find rubygem-installed libraries?
Here's the current version of the relevant source: https://github.com/rubygems/rubygems/blob/02ead548e38ff90923444fa7c0ff9f6a5dbd87b0/lib/rubygems/custom_require.rb. (Edit: here's an earlier version (1.5.2) that more clearly expresses what happens.)
The docs say:
When RubyGems is required, Kernel#require is replaced with our own which
is capable of loading gems on demand.
When you call require 'x', this is what happens:
If the file can be loaded from the existing Ruby loadpath, it
is.
Otherwise, installed gems are searched for a file that matches.
If it's found in gem 'y', that gem is activated (added to the
loadpath).
The normal require functionality of returning false if
that file has already been loaded is preserved.
It does this by opening up module Kernel and aliasing the original require with alias gem_original_require require, then redefining require to first call the original version, and look at the gems if that doesn't work.
So the load path is only changed when you require a gem:
ruby-1.8.7-p330 :002 > $:.length
=> 9
ruby-1.8.7-p330 :003 > require 'rubygems'
=> true
ruby-1.8.7-p330 :004 > $:.length
=> 9
ruby-1.8.7-p330 :005 > require 'haml'
=> true
ruby-1.8.7-p330 :006 > $:.length
=> 10
Specifically, the ruby-oci8 gem. I have both 1.0.7 and 2.0.4 installed. I want 1.0.7.
I can just require oci8, but I don't get the version I want.
irb(main):001:0> require 'oci8'
=> true
irb(main):002:0> OCI8::VERSION
=> "2.0.4"
I can require using the full path to the file, which works, but is not going to be portable:
irb(main):001:0> require 'C:\Ruby\lib\ruby\gems\1.8\gems\ruby-oci8-1.0.7-x86-mswin32-60\lib\oci8'
=> true
irb(main):002:0> OCI8::VERSION
=> "1.0.7"
I can use the gem command to ask for the version I want, but it doesn't appear to actually load the library:
irb(main):001:0> gem 'ruby-oci8', :lib=>'oci8', :version=>'=1.0.7'
=> true
irb(main):002:0> OCI8::VERSION
NameError: uninitialized constant OCI8
from (irb):2
I would definitely favor this last approach if would load the library, rather than just confirming that it's present on my system. What am I missing?
My problem was twofold:
1) confusing gem command syntax with that used in config.gem lines in a rails environment.rb configuration file.
2) failing to issue a require command after the gem command.
Proper usage in a script is:
gem 'ruby-oci8', '=1.0.7'
require 'oci8' # example is confusing; file required (oci8.rb) is not
# same name as gem, as is frequently the case
Proper usage in a rails 2.3.x environment.rb file is:
config.gem "ruby-oci8", :version=>'1.0.7'
Thanks to the folks at http://www.ruby-forum.com/topic/109100
Try the following syntax (instead of require):
require_gem 'RMagick' , '=1.10'
require_gem 'RMagick' , '>=1.10'
require_gem 'rake', '>=0.7.0', '<0.9.0'