あるパスからの相対パスを絶対パスで欲しい場合のやり方を間違っていた話

TL;DR

File.expand_path(File.join(Rails.root, "./my_config/hogehoge"))
=> "/Users/yoan/dev/src/github.com/myoan/rails_test/my_config/hogehoge"

とずっと書いていましたが、

File.expand_path("./my_config/hogehoge", Rails.root)
=> "/Users/yoan/dev/src/github.com/myoan/rails_test/my_config/hogehoge"

と書くほうがシンプルでした

挙動を調べる

リファレンスによると、

expand_path(path, default_dir = '.') -> String[permalink][rdoc]

(中略)

    [PARAM] path:
        パスを表す文字列を指定します。
    [PARAM] default_dir:
        path が相対パスであれば default_dir を基準に展開されます。

と書かれています。

相対パスなので第一引数のパスに対する比較対象が必要で、第二引数がその対象になります。 そして、第二引数が省略された場合は今いるパスが対象になります。

したがって、

  • File.expand_path("../hoge")今いるパスから"../hoge"移動した絶対パスを返す
  • File.expand_path("../hoge", "/tmp")/tmpから"../hoge"移動した絶対パスを返す
    • つまり /hoge
  • File.expand_path("./my_config/hogehoge", Rails.root)Rails.rootから"./my_config/hogehoge"移動した絶対パスを返す
    • つまり Rails.root/my_config/hogehoge

そもそもの問題

modelにはそのまま相対パスを、rspecにはRails.rootとjoinした形で書いていました

### rb

def my_path
  mkdir_if_not_exist(File.expand_path("../my_config/hogehoge"))
  File.expand_path("../my_config/hogehoge")
end

### spec

describe do
  it { is_expected(my_path).to eq File.expand_path(File.join(Rails.root, '../my_config/hogehoge'))" }
end

テストではディレクトリを作ってほしくないためFakeFSを導入したところエラーが出たところが始まりでした

expected: "/Users/yoan/dev/src/github.com/rails-test/my_config/hogehoge"
     got: "/my_conofig/hogehoge"

FakeFS.expand_pathを調べたところ

def self.expand_path(file_name, dir_string = FileSystem.current_dir.to_s)
  RealFile.expand_path(file_name, RealFile.expand_path(dir_string, Dir.pwd))
end

となっており、第二引数に"/"が渡されていました

つまりルートディレクトリからの絶対パスを返していたため、gotの結果が帰ってきたというわけでした。

参考