QA@IT

Rails3でtmpディレクトリ配下の画像ファイルをimage_tagで表示するには?

6547 PV

Rails(3.2.8)で画像ファイルを一旦tmpディレクトリ配下に保存し、保存した画像ファイルをimage_tagで表示したいと思っています。
ですが、下記のようにファイルパスを指定しても、GETリクエストでファイルを表示しようとして、RoutingErrorになってしまいます。

<%= image_tag( "tmp/image/test.jpg" ) %>
 # => http://0.0.0.0:3000/assets/tmp/image/test.jpg
<img alt="" src="tmp/image/test.jpg" />
 # => http://0.0.0.0:3000/tmp/image/test.jpg
<%= image_tag( "/tmp/image/test.jpg" ) %>
 # => http://0.0.0.0:3000/tmp/image/test.jpg
<img alt="" src="/tmp/image/test.jpg" />
 # => http://0.0.0.0:3000/tmp/image/test.jpg
ActionController::RoutingError (No route matches [GET] "/tmp/image/test.jpg"):

HTMLの挙動としては正しい気がしますし、実際に描画されるブラウザのローカル環境は各ユーザのクライアントになってしまうので、やはり直接サーバ側のローカルファイルを表示するという事は出来ないのでしょうか?

画像を表示する場合は、assets配下に保存する、もしくはS3に保存するなどの方法しか無いでしょうか?
(Heroku環境でも動作するようにしたいので、assets配下への保存は代替案としては使えないと思っています。)

追記:
今回の用途では、プログラム内でURLなどから取得した画像ファイルを一旦サーバサイドに置いて、それをユーザに見せてから、ユーザが選択した画像のみS3に置く、というようなフローがしたいと思っています。

回答

検証していませんが、3点。

  • image_tagを使うのなら、public/tmp/foo.jpgではなく、public/images/tmp/foo.pngにすればimage_tag 'tmp/foo.png'でいけませんか?
  • ちなみに、ここで保存したものは「Railsサーバ上の」ローカルに保存されるので、NginxやApacheなどの静的ファイルをさばくリバースプロキシが別サーバにある場合には工夫が必要です。とくにRailsサーバが複数台ある場合には、リクエスト毎にどのサーバへ回されるかわからないので。アップ頻度が低いならリバースプロキシのpublicディレクトリを全RailsサーバでNFSマウントするのが手軽ですが、Herokuの場合には使えなさそうですね。。。このさいS3経由でもいいような気もします。(高価ですが、Heroku使う時点でコストより利便性なのだろうと推測)
  • 一時的な画像ファイルは、後処理をちゃんとしないとゴミが増え続ける可能性があります。そのためにtmpdir/tempfile (http://jp.rubyist.net/magazine/?0029-BundledLibraries) という仕組みがあるので、外部URLからとってきた一時的なファイルはこれで保存しておくのがよいと思います。
編集 履歴 (0)
  • すみません。tmpディレクトリはRails.root直下のtmpディレクトリを使う意図でした。
    Heroku上でも同じ実装で動かしたい、複数台構成になっても影響を受けないようにしたい、という希望もあるので、S3+tempfileライブラリで行こうかと思います。
    回答ありがとうございました。
    -

もっといい方法がありそうですが、
url_for と send_data を使う方式はどうでしょうか?

  • 下準備
rails new blog
cd blog
rails g scaffold article title body:text
rake db:migrate
cp app/assets/images/rails.png tmp/
  • config/routes.rb
# member/collectionなどは適切に
  resources :articles do
    member do
      get 'get_image'
    end
  end
  • controller
  def get_image
    File.open('tmp/rails.png', 'rb') do |f|
      send_data f.read, :type => "image/png", :disposition => "inline"
    end
  end
  • view
<p>
  <%= image_tag(url_for(:action => 'get_image'))%>
</p>

このあたりのことを昔ブログに書いたのでリンク載せておきます。
DBに格納したバイナリ画像を表示させる方法

編集 履歴 (2)
  • config.assets.paths << 'tmp'
    をふと思い立ちましたが、これは意図したものではないんですよね。
    -
  • 回答ありがとうございます。
    send_dataを使えば直接バイナリデータから画像を表示させる事が出来そうですね。
    Heroku上でassets配下にファイル書き込みが出来ない、複数台構成の場合、次のリクエストで書き込んだファイルが存在する保証が無い、などの理由があるので、```config.assets.paths << 'tmp' ```は今回の用途では使えなさそうです。
    -

公開したい静的なファイルは public 下に置くのが一番手軽です。

development 環境での Rails はリクエストがあると public 下にパスが一致するファイルがあるか調べ、あればそのファイルを返し、なければ config/routes.rb に従って処理します。

production 環境では、この「ファイルがあるか調べてあれば返す」の部分は nginx や apache などに任せることが多く、Rails はデフォルトではその処理をしませんが、たぶん Heroku はうまいことやってくれると思います (推測)。

編集 履歴 (0)
  • 回答ありがとうございます。
    通常の静的ファイルであればassets/imagesの下に置いて、コンパイル後public配下に置かれたファイルにアクセスされるのが普通だとは思いますが、今回の用途では、プログラム内でURLなどから取得した画像ファイルを一旦サーバサイドに置いて、それをユーザに見せてから、ユーザが選択した画像のみS3に置く、というようなフローがしたいと思っています。
    -
  • すみません、「静的な」というのは「Rails で生成しない」程度の意味でした。別にコンパイルする必要のないものは public 下にそのまま置けますし、運用時に取得したファイルを置くのも一般的と思います。 1. 取得した画像を public/tmp/foo.png に保存 2. <img src="/tmp/foo.png"> を表示 ではだめでしょうか? -
  • たびたびすみません、ノートって img タグ通るんですね...。img src="/tmp/foo.png" です。 -
  • なるほど。通常の構成であればpublic/tmpの下に普通に置いてあげればいいのですね。
    ただ、出来ればHeroku上でも同じ実装で動かしたいので、今回の用途では出来なさそうです。(Heroku上では/tmpと/log以外に対しての書き込みが出来ないため。)
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。