Help us understand the problem. What is going on with this article?

Rails.root.join("foo", "bar")よりも、Rails.root.join("foo/bar")が良いのでは?というお話

はじめに:joinに渡す引数はいくつ?

たとえば、Railsのテストを書いたりするときに、テスト用のCSVファイルのパスを指定することがあると思います。

# 入力フォームでCSVファイルをアップロードするコード例
attach_file 'CSV file', csv_file_path

このとき、Rails.root.joinを使うと簡潔にファイルパスを取得できるのですが、いろんな人のコードを見ていると、大きく分けて以下のような2パターンがあるようです。

# "/" で区切った1つの文字列を渡すパターン
csv_file_path = Rails.root.join('spec/fixtures/sample.csv')

# パスの要素ごとに区切って複数の文字列を渡すパターン
csv_file_path = Rails.root.join('spec', 'fixtures', 'sample.csv')

僕はふだん前者のパターンで書いています。その理由は以下のとおりです。

  • 後者のパターンより短く書けるから
  • 'spec/fixtures/sample.csv'のようなパスが、プロジェクトルートから見た相対パスとして、ぱっと認識しやすいから

一方、後者のパターンで書く人の理由を聞いてみると、「実行環境によってはパスの区切り文字が/と限らないから」と答える人が多かったです。
これはおそらく、Windows環境でパスの区切り文字が\になることを意識しているんだと思います。

検証:"/"でパスを区切っても、Windows環境でちゃんと動く

しかし、後者のパターンで書く理由が「Windows環境を意識しているから」なのであれば、その心配はおそらく無用です。

Windows環境のrails consoleで、先ほどのようなRails.root.joinの2つの書き方を比較した結果を以下に載せます。
(僕はWindowsをふだん使わないため、Railsのバージョンが少し古いですが、おそらく挙動は今でも同じだと思います)

C:\dev\rails-sandbox>rails c
Loading development environment (Rails 5.0.2)
irb(main):001:0> Rails.root
=> #<Pathname:C:/dev/rails-sandbox>
irb(main):002:0> p1 = Rails.root.join('config', 'database.yml')
=> #<Pathname:C:/dev/rails-sandbox/config/database.yml>
irb(main):003:0> File.exist? p1
=> true
irb(main):004:0> p2 = Rails.root.join('config/database.yml')
=> #<Pathname:C:/dev/rails-sandbox/config/database.yml>
irb(main):005:0> File.exist? p2
=> true

すこし見づらいかもしれませんが、Rails.root.join('config', 'database.yml')と書いた場合も、Rails.root.join('config/database.yml')と書いた場合も、どちらも同じようにファイルの存在チェックに成功しています。

まとめ:"/"で区切る書き方でも問題ないのでは?

どちらが絶対に正しい書き方、というのはないと思いますが、短くシンプルに書ける「/で区切る書き方」を積極的に採用するのは悪くない考えだと思います。

もし「パスの区切り文字が/とは限らないから(Windows環境が心配だから)」という理由でRails.root.join('spec', 'fixtures', 'sample.csv')のような書き方をしている人がいたら、次回からRails.root.join('spec/fixtures/sample.csv')のような書き方も検討してみてください。

また、Rails.root.join('spec', 'fixtures', 'sample.csv')の方がメリットが大きい、という方がいたら、コメントをお待ちしています。

補足1:"/"で始まるパスを渡さないように注意!

ただし、この書き方には1つだけ注意点があります。それは「/ではじまる文字列を引数として渡さないこと」です。
/で始まる文字列を引数にすると、プロジェクトルートのパスが付与されず、引数がそのまま絶対パスとして扱われます。

# OK: 最初に"/"を付けない場合→プロジェクトルートのパスが付与される
Rails.root.join('spec/fixtures/sample.csv').to_s
#=> "/Users/jnito/dev/rails-sandbox/spec/fixtures/sample.csv"

# NG: 最初に"/"を付けた場合→プロジェクトルートのパスが付与されない
Rails.root.join('/spec/fixtures/sample.csv').to_s 
#=> "/spec/fixtures/sample.csv"

Pathname#joinメソッドがなぜこのような振る舞いをするのかについては、以下の記事で詳しく説明しています。

補足2:Rails.rootメソッドが返すオブジェクトは何?

Rails.rootメソッドが返すのはRuby標準のPathnameオブジェクトです。

Rails.root.class
#=> Pathname

また、joinメソッドもRuby標準の実装が使われています。(ActiveSupportによって拡張されているわけではありません)

Rails.root.method(:join).source_location
#=> ["/Users/jnito/.rbenv/versions/2.6.5/lib/ruby/2.6.0/pathname.rb", 407]

Pathname#joinメソッドの仕様については以下のドキュメントを参照してください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした