1

I have a gem project with the following structure:

foo-bar
├── lib
│   └── foo
│       ├── bar
│       │   └── qux.rb
│       └── bar.rb
└── spec
    ├── spec_helper.rb
    └── unit
        ├── baz_spec.rb
        └── qux_spec.rb

In lib/foo, bar.rb defines a module Foo::Bar, and inside that, a class Foo::Bar::Baz. Inside lib/foo/bar, qux.rb defines a class Foo::Bar::Qux.

spec_helper.rb sets up RSpec and Simplecov, and finishes with require 'foo/bar'. Both baz_spec.rb and qux_spec.rb start with require 'spec_helper'.

baz_spec.rb has the specs for Foo::Bar::Baz, and it works fine. qux_spec.rb, however, which has the specs for Foo::Bar::Qux, fails with:

/Users/me/foo-bar/spec/unit/qux_spec.rb:6:in `<module:Bar>': uninitialized constant Foo::Bar::Qux (NameError)
    from /Users/me/foo-bar/spec/unit/qux_spec.rb:4:in `<module:Foo>'
    from /Users/me/foo-bar/spec/unit/qux_spec.rb:3:in `<top (required)>'
    from /Users/me/.rvm/gems/ruby-2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `load'
    ...
    (etc.)

I've verified that it's not just a typo by moving the code for Foo::Bar::Baz out of lib/foo/bar.rb and into its own file, lib/foo/bar/baz.rb, after which baz_spec.rb also stops working.

It also doesn't seem to make a difference whether I declare the class as

class Foo::Bar::Qux
  ...

or as

module Foo
  module Bar
    class Qux
      ...

I'm using Ruby 2.2.0 with RSpec 3.2.2 on Mac OS X Yosemite.

Clearly there's something wrong with my requires, but as a Ruby novice I'm not seeing it. Any ideas?

David Moles
  • 48,006
  • 27
  • 136
  • 235
  • A possible solution depends on what kind of frameworks you are using. So, are you using either Rails or ActiveSupport? – Alex Mar 17 '15 at 19:17
  • Neither. Well, I suppose ActiveSupport is in there because I have ActiveJob in my gemspec, but it's not being used yet. – David Moles Mar 17 '15 at 19:58

3 Answers3

2

you need to add foo.rb file under ./lib and add require statements for each file in order you want them to get loaded. You can look at sample gem for reference: dogeify and an article that walks you through gem creation build your first gem.

Iuri G.
  • 10,460
  • 4
  • 22
  • 39
1

If you're using ActiveSupport a single line like this will help you:

ActiveSupport::Dependencies.autoload_paths << "./lib"

If you're now trying to use Foo::Bar::Qux, ActiveSupport will look for a file named foo/bar/qux.rb inside of the lib folder.

Alex
  • 2,398
  • 1
  • 16
  • 30
  • I wouldn't recommend using ActiveSupport autoload feature since it introduces dependency which you might not need and also does not give you control of the order in which you files are loaded. Also, consider using ruby's built in autoload feature (http://ruby-doc.org/core-2.1.0/Module.html#method-i-autoload) if you are using recent version – Iuri G. Mar 17 '15 at 20:51
1

Solved: after taking a closer look at the other projects I was cargo-culting from, I realized I'd misunderstood require.

Unlike (apparently) its Rails equivalent, the out-of-the-box Kernel.require just loads .rb files (and extension libraries). So in the example above, require 'foo/bar' doesn't load files from the foo/bar directory, it just loads foo/bar.rb.

In order to load the files under foo/bar, including qux.rb, I had to go into bar.rb and explicitly load those files at the top of the module declaration:

module Foo
  module Bar
    Dir.glob(File.expand_path('../bar/*.rb', __FILE__), &method(:require))

    # ...module declaration continues...

Just one of the many scripting-language-heritage pitfalls waiting for those who come to Ruby from other more heavyweight languages, I suppose.

David Moles
  • 48,006
  • 27
  • 136
  • 235