Posts Tagged ‘require’
Creating gems is easy, all you need is a gemspec:
Gem::Specification.new do |spec| spec.name = 'chubby-bat' spec.version = '0.0.1337' spec.files = [ './lib/chubby_rain.rb', './lib/crack_murphy.rb', ] spec.summary = 'An example gem' spec.author = 'Ben Biddington' end
And then to build it:
$ gem build chubby-bat.gemspec
And then to install it:
$ gem install chubby-bat-0.0.1337.gem
No such file to load — xxx
Trouble is, when requiring the new gem, you may encounter an error like:
irb(main):001:0> require 'chubby-bat' LoadError: no such file to load -- chubby-bat from C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require' from C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require' from (irb):1 irb(main):002:0>
I found this pretty confusing, until I realised the interpreter is looking for a file called chubby-bat.
In short, a gem needs to contain a file with the same name as the gem itself in its lib folder:
And gemspec becomes:
Gem::Specification.new do |spec| spec.name = 'chubby-bat' spec.version = '0.0.1337' spec.files = [ './lib/chubby-bat.rb', './lib/chubby_rain.rb', './lib/crack_murphy.rb', ] spec.summary = 'An example gem' spec.author = 'Ben Biddington' end
The comment in rubygems custom_require explains:
# See: path/to/Ruby/lib/ruby/site_ruby/1.8/rubygems, custom_require ## # 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.
Ruby loadpath, gem install and Kernel#require
Installing a gem simply copies files to somewhere in $LOAD_PATH, and invoking require triggers a file search within these locations.
Kernel require does the following:
- Ruby tries to load the library named string, returning true if successful.
- If the filename does not resolve to an absolute path, it will be searched for in the directories listed in $:.
- If the file has the extension “.rb’’, it is loaded as a source file; if the extension is “.so’’, “.o’’, or “.dll’’,or whatever the default shared library extension is on the current platform, Ruby loads the shared library as a Ruby extension. Otherwise, Ruby tries adding “.rb’’, “.so’’, and so on to the name.
- The name of the loaded feature is added to the array in $”.
- A feature will not be loaded if it‘s name already appears in $”.
This means if you print out the contents of $”, you’ll see chubby-bat.rb in there.
While diagnosing problems with a ruby application, we discovered we had to move certain folders around so the interpreter could find them. Clearly we were missing something fundamental.
Inspecting the source (v1.9) for require shows:
Ruby tries to load the library named _string_, returning * +true+ if successful. If the filename does not resolve to * an absolute path, it will be searched for in the directories listed * in <code>$:</code>. If the file has the extension ``.rb'', it is * loaded as a source file; if the extension is ``.so'', ``.o'', or * ``.dll'', or whatever the default shared library extension is on * the current platform, Ruby loads the shared library as a Ruby * extension. Otherwise, Ruby tries adding ``.rb'', ``.so'', and so on * to the name. The name of the loaded feature is added to the array in * <code>$"</code>. A feature will not be loaded if it's name already * appears in <code>$"</code>. However, the file name is not converted * to an absolute path, so that ``<code>require 'a';require * './a'</code>'' will load <code>a.rb</code> twice.
Require does not canonicalize paths
Here’s the relevant part of the comment:
However, the file name is not converted to an absolute path, so that require 'a'; require './a' will load a.rb twice.
So, as described, ruby treats require statements literally: a different path is a different file, even if it’s a different path to the same file. And this may result in a file being loaded twice.
This can have confusing side effects. We encountered this in one of our cucumber projects — after scenario handlers were firing twice and we didn’t know why.
Require does not expand paths
Another implication of this behaviour is that an application may not run as expected unless the correct working directory is used.
Adding to the $” list does not work
I thought another option may be adding items directly to the $” list. I guess this shows that require is doing more than just inserting an item in a list.
Adding to $” is in fact not enough, demonstrated by the fact that this works:
require file_to_include $".unshift(file_to_include)
While the reverse order does not:
$".unshift(file_to_include) require file_to_include
This proves that require has exited early because the file already exists in $” – and hence the include fails.
Fortunately there is a way around both of these problems.
Rather than require relative paths:
absolute paths may be specified:
File.expand_path(File.dirname(__FILE__) + '/../lib/application')
This means that:
- Working directory is no longer relevant. The second example ensures that the required path is relative the the file that requires it.
- Duplicate requires are not possible
EDIT, 2009-05-12: Also consider require_relative, added in v1.9