Rubyでパス文字列を組み立てるときはPathnameクラスを使おう

はじめに

Rubyで文字列操作だけでパス文字列を組み立てようとすると苦しいコードになりがちですが、Pathnameクラスを使うといい感じに書けることがわかったのでまとめました。

Pathnameとは

Rubyの組み込みクラスで、パス名をオブジェクト指向っぽく扱うことができます。
例えば、以下のようなインスタンスメソッドが用意されています。

  • directory?: selfがディレクトリであればtrueを返す
  • size: selfのファイルサイズを返す
  • symlink?: selfシンボリックリンクである時にtrueを返す

用意されているメソッドを見ればわかるように、自身の表すパスの情報を取得したりするときに便利です。
しかし、一番のポイントは、Pathnameインスタンスはパス名を表しているだけであり、存在しないファイルのパス名も扱えるということです。
なので、システムとは無関係のパス文字列を組み立てる際にも利用することができます。

Pathnameを使わないコード

文字列操作だけでパス文字列を組み立てようとすると、苦しいコードになりがちです。
例えば以下のような感じ。

path = "users/#{item.user.id}/"

path += 
  if item.image?
    'images'
  elsif item.movie?
    'movies'
  else
    'misc'
  end

path += "/#{item.name}.#{item.extension}"
puts path #=> users/46/photos/nogizaka.png

各階層を区切る/をいちいちつけているのでとても読みにくいコードになっています。
また、/をつける位置に気をつけないとすぐ不正なパス文字列になりそうです。

上のコードを改善案としてすぐ思いつくのは、各階層のファイル名を要素として持つ配列を用意し、これを最後にjoin('/')などで結合して返すことです。
が、要素の中に'/path'のような文字列が入っていたりすると、/が重なって不正なパス文字列になってしまう可能性があるので、ベストな方法とはいえません。

Pathnameを使ったコード

上のコードをPathname使って書き直すと以下のようになります。

require 'pathname' # Railsでは不要

path = Pathname.new('users').join(item.user.id.to_s)

path += 
  if item.image?
    'images'
  elsif item.movie?
    'movies'
  else
    'misc'
  end

path += "#{item.name}.#{item.extension}"
puts path #=> users/46/photos/nogizaka.png

+演算子#joinを使うことで、/を意識せずにパス文字列を組み立てることができます。

おわりに

実は、Rails.rootで返ってくるのもPathnameインスタンスだったりします。

参考