Jwan622
Jwan622

Reputation: 11647

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

Upvotes: 3

Views: 1022

Answers (3)

7stud
7stud

Reputation: 48599

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"]

Upvotes: 4

ian
ian

Reputation: 12251

The other 2 answers are technically correct and address the goal but not the aim. In other words - why are you doing that in the first place? For instance, why do this:

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

class HouseDistributer < Distributer
end

And not this?

require_relative "distributer.rb"
class HouseDistributer < Distributer
end

And in the tests (cough) sorry, the specs:

# spec/house_distributer_spec.rb
require 'spec_helper'
require_relative "../lib/house_distributer.rb"

# spec/transaction_service_spec.rb
require 'spec_helper'
require_relative "../lib/transaction_service.rb"

And since transaction_service.rb appears to need HouseDistributer from lib/house_distributer.rb

# lib/transaction_service.rb
require_relative "house_distributer.rb"

The rule of thumb

If a file needs another to run then require (or require_relative) it in the file that requires it. Then you get:

  • A kind of natural sandboxing by only running what is needed (maybe Distributer works perfectly and HouseDistributer has the error because of a monkey patch - how will you know if you require files that aren't actually needed?)
  • No need to handle requires elsewhere.
  • No need (or much less need) to know the order of requires.
  • No need to fiddle about with the load path (which has been a dubious need ever since require_relative was introduced).
  • Changing the load path and then using require can break sandboxing and make specs work that should fail by loading a gem you have installed on your system but isn't defined as a dependency in the gemspec.

I'd add, use bundler's sandboxing too, to avoid further errors and let it handle your load path, e.g.

bundle install --binstubs --path=vendor (or vendor.noindex on a Mac) then:

bin/rspec and whatever commandline args you need.

Upvotes: 2

max pleaner
max pleaner

Reputation: 26768

The file not found is because you use "lib/*.rb" and not ./"lib/*.rb".

To ensure the dependencies are loaded in the correct order, you can do this:

  1. move HouseDistributor to lib/distributor/house_distributor.rb

  2. Require files like so:

    Dir['./lib/**/*.rb']
    .sort_by { |path| path.count("/") }
    .each { |path| require path }
    

    this uses **/*.rb to do a recursive search and will sort the files by the count of "/" (their depth) before requiring

Just a note of caution, if you are doing a recursive require, keep in mind that you actually do want to require all those files. For example if you are are using ActiveRecord and have a schema.rb file, you probably don't want to require that.

Upvotes: 2

Related Questions