Ruby’s File.expand_path method vs. require_relative

When a Rails application is initialized, the File.expand_path method is used in the config/ and script/ directories as well as the Rakefile. As noted by Theo in this StackOverflow post, File#expand_path is used to get the absolute path when the relative path is known. I think File#expand_path is somewhat antiquated now that require_relative exists, but it is work studying a Rails still uses it.

Suppose we have a project with the following directory structure:

file_stuff/
  lib/
    animals/
      animal.rb
      dog.rb
  stuff/
    main.rb

The Ruby files are populated with the following code:

# lib/animals/animal.rb
class Animal
  def earth_dwelling
    "I am an earthling"
  end
end

# lib/animals/dog.rb
class Dog < Animal
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

# stuff/main.rb
fido = Dog.new("fido")
puts fido.earth_dwelling

In order to execute the code in stuff/main.rb, we need to require the animal.rb file in the dog.rb file and require dog.rb in the main.rb file. The require_relative method can be used to get this code working relatively easily.

# lib/animals/dog.rb
require_relative "animal"
class Dog < Animal
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

# stuff/main.rb
require_relative "../lib/animals/dog"
fido = Dog.new("fido")
puts fido.earth_dwelling

File.expand_path can be used instead of require_relative, but it is less intuitive. The following code comments out the require_relative and adds File.expand_path to contrast the differences.

# lib/animals/dog.rb
# require_relative "animal"
require File.expand_path("../animal.rb", __FILE__)
class Dog < Animal
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

# stuff/main.rb
# require_relative "../lib/animals/dog"
require File.expand_path("../../lib/animals/dog.rb", __FILE__)
fido = Dog.new("fido")
puts fido.earth_dwelling

Why does it look like File.expand_path goes up and extra directory compared to require_relative? The __FILE__ variable in main.rb represents “stuff/main.rb”. When we use __FILE__ we are asking File.expand_path to provide a directory relative to the main.rb file, not the stuff/ directory, so we need to go up and extra directory. File.expand path is more consistent with require_relative when the second argument is a directory.

# lib/animals/dog.rb
# require_relative "animal"
require File.expand_path("animal.rb", File.dirname(__FILE__))
class Dog < Animal
  attr_reader :name
  def initialize(name)
    @name = name
  end
end

# stuff/main.rb
# require_relative "../lib/animals/dog"
require File.expand_path("../lib/animals/dog.rb", File.dirname(__FILE__))
fido = Dog.new("fido")
puts fido.earth_dwelling

Passing a directory as the second argument to File#expand_path is slighly more verbose, but recommended for code clarity.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s