Search code examples
rubysinatra

Easier way to require spec files in Sinatra


I have a spec_helper that looks like this:

require 'pry'
require 'helpers/data_helper.rb'
require 'distributer.rb'
require 'house_distributer.rb'
require 'accounting_service.rb'
require 'mixer_worker.rb'
require 'mixer.rb'
require 'transaction_service.rb'

ENV['RACK_ENV'] = 'test'

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.warnings = true

  config.order = :random
end

and a folder structure that looks like this:

.
├── Gemfile
├── Gemfile.lock
├── README.md
├── app.rb
├── config.ru
├── lib
│   ├── accounting_service.rb
│   ├── distributer.rb
│   ├── house_distributer.rb
│   ├── mixer.rb
│   ├── mixer_worker.rb
│   └── transaction_service.rb
├── public
│   ├── css
│   │   └── add_coins.css
│   ├── images
│   │   └── bitcoin_dawg.jpg
│   └── javascripts
│       └── add_coins.js
├── spec
│   ├── helpers
│   │   └── data_helper.rb
│   ├── lib
│   │   ├── accounting_service_spec.rb
│   │   └── transaction_service_spec.rb
│   └── spec_helper.rb
└── views
    └── add_coins.erb

This does not work:

Dir["lib/*.rb"].each {|file| require file }

[1] pry(main)> Dir["lib/*.rb"]
=> ["lib/house_distributer.rb", "lib/distributer.rb", "lib/mixer.rb", "lib/accounting_service.rb", "lib/mixer_worker.rb", "lib/transaction_service.rb"]

I get this error message:

/Users/jwan/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- lib/house_distributer.rb (LoadError)
    from /Users/jwan/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'

What can I do to make this easier?

Also side note, distributer.rb has to be loaded before house_distributer.rb because of this:

class HouseDistributer < Distributer
end

Solution

  • Some explanation of max pleaner's answer...

    When you write:

    require 'lib/house_distributer.rb'
    

    ruby looks for the file in the directories assigned to the $LOAD_PATH environment variable. $LOAD_PATH is an array of Strings, where each String is a path to a directory. The directories in the $LOAD_PATH array are searched in order, and the first match wins.

    If $LOAD_PATH contains a directory called:

    '/Users/7stud/ruby_programs'
    

    Then the require statement above will look for a file with the absolute path:

    '/Users/7stud/ruby_programs/lib/house_distributer.rb'
    

    You can check which directories are in your $LOAD_PATH like this:

    $ puts $LOAD_PATH
    

    This is what I get:

    /Users/7stud/.rvm/gems/ruby-2.4.0@global/gems/did_you_mean-1.1.0/lib
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/site_ruby/2.4.0
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/site_ruby/2.4.0/x86_64-darwin14
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/site_ruby
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/vendor_ruby/2.4.0
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/vendor_ruby/2.4.0/x86_64-darwin14
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/vendor_ruby
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0
    /Users/7stud/.rvm/rubies/ruby-2.4.0/lib/ruby/2.4.0/x86_64-darwin14
    

    Obviously, your app's files are not in directories like those.

    On the other hand, if you require a file whose path starts with a / or a . -- for instance:

    require './lib/house_distributer.rb'
    

    then ruby skips $LOAD_PATH, and in this case ruby looks for the file relative to the current working directory. Note however, that the current working directory may not be the directory containing the file with the require statement. For instance, if you execute your sinatra program from a different directory, say two levels up from the file containing the require statement, then the two levels up directory will be the current working directory, and ruby will look for the required file relative to the two levels up directory.

    Enter require_relative. require_relative will look for the file relative to the path of the current file--not the current working directory.

    As a result, you should probably never use require with a relative path and instead use require_relative.

    Note that you can also programmatically add paths to $LOAD_PATH any time you want:

    $LOAD_PATH << '/Users/7stud/ruby_programs'
    

    And if a file called dog.rb is in that directory, I can require it like so:

    require 'dog'  #extension is unnecessary
    

    Response to comment:

    The simplest thing to do would be:

    $LOAD_PATH << "/Users/jwan/Desktop/programming/interview_questions/gemini/‌​jobcoin_mixer/"
    

    But in sinatra, settings.root is the path to your app directory, so do:

    $LOAD_PATH.unshift settings.root 
    

    That way you can move your app to another directory without changing anything.

    Or, you can remove lib/ from the front of every path that Dir[] returned:

    require 'pathname'
    
    paths = [
      "lib/house_distributer.rb", 
      "lib/distributer.rb", 
      "lib/mixer.rb", 
      "lib/accounting_service.rb", 
    ]
    
    new_paths = paths.map do |path|
      pn = Pathname.new path
      pn.relative_path_from(pn.parent).to_s
    end
    
    p new_paths
    
    --output:--
    ["house_distributer.rb", "distributer.rb", "mixer.rb", "accounting_service.rb"]