Rails 3.2 で CarrierWave を使う時に public 以下にファイルを置きたくない場合

CarrierWave は便利なプラグインですが、デフォルト設定のまま Rails で使うと public 以下の誰でもアクセス可能なディレクトリにファイルを書き込んでしまいます。これでは困る場合は config/application.rb で以下のようにデフォルトのアップロード先を指定します。

module Hoge
  class Application < Rails::Application
    # 略
    CarrierWave.configure do |config|
      config.root = Rails.root
    end
  end
end

これで書き込みは public 以下ではなく Rails.root 直下をベースに行われる…のですが、肝心のファイルの読み出しの方がうまくいきません。自由にアクセスできない位置にファイルを置きたかったので成功と言えば成功ですが、適切なユーザがファイルを見られるように設定してあげる必要があります。これを実現するために、ファイルの読み出し用のコントローラを作成します。名前は attachments_controller.rb とします。

rails g controller attachments

コントローラを作ったら、ファイル表示用のメソッドを作成します。File.binread は Ruby 1.9 系のメソッドなので Ruby 1.8 系を使う時は適宜書き換えてください。

class AttachmentsController < ApplicationController
  def show
    full_path = "#{Rails.root}" + params[:path]
    image = File.binread(full_path)
    send_data image, :disposition => 'inline'
  end
end

続いて config/route.rb にルーティングを記述。

Hoge::Application.routes.draw do
  get "attachments/show"
  # 中略
end

最後に、ビューの表記を書き換えます。元のビューではこのように記述しているとします。

<%= image_tag hoge.image_url %>

これを以下のように変更します。

<%= image_tag(url_for(:controller => 'attachments', :action => 'show', :path => hoge.image_url)) %>

後は、先ほど作った attachments_controller の中で適宜認証をかけてあげれば、必要なユーザのみが目的の画像を見られるようになります。

class AttachmentsController < ApplicationController
  def show
    render :text => "Not Allowed" unless authenticate # 適当に認証したとする
    full_path = "#{Rails.root}" + params[:path]
    image = File.binread(full_path)
    send_data image, :disposition => 'inline'
  end
end