Ben Biddington

Whatever it is, it's not about "coding"

Posts Tagged ‘require

I’ve created my gem, but can’t require it

with one comment

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

Rubygems require

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.

Written by benbiddington

12 January, 2010 at 13:37

Posted in development

Tagged with , , , ,

Ruby require

with 2 comments

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.

Require: definition

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)
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.

Solution

Fortunately there is a way around both of these problems.

Rather than require relative paths:

require '../lib/application'

absolute paths may be specified:

File.expand_path(File.dirname(__FILE__) + '/../lib/application')

This means that:

  1. Working directory is no longer relevant. The second example ensures that the required path is relative the the file that requires it.
  2. Duplicate requires are not possible

EDIT, 2009-05-12: Also consider require_relative, added in v1.9

Written by benbiddington

11 May, 2009 at 13:00

Posted in development

Tagged with , , ,