#253 CarrierWave File Uploads
- Download:
- source codeProject Files in Zip (480 KB)
- mp4Full Size H.264 Video (21 MB)
- m4vSmaller H.264 Video (11.7 MB)
- webmFull Size VP8 Video (24.9 MB)
- ogvFull Size Theora Video (25.4 MB)
アーティストが絵をアップロードしてギャラリーに展示できるサイトを作っているとしましょう。一部はすでにできています。ギャラリーを一覧表示するページがあり、リンク先のギャラリーで絵を見ることができます。
それぞれの絵は、タイトルと画像保存用のフィールドを持っていますが、絵そのものはありません。ギャラリーに新規に絵を追加しようとしても、タイトルを入れるテキストフィールドがあるのはわかりますが、画像をアップロードする方法がありません。
絵の画像を添付する機能を実装するにはどうすればいいでしょう?Paperclip gemについてエピソード134[動画を見る, 読む]で紹介しました。このgemを使うとRailsのモデルでファイル添付用のフィールドを扱うことができました。今回は、同様の機能を提供するCarrierWaveというgemを使ってみます。
CarrierWaveはPaperclipと比較してより柔軟な機能を提供します。Rackに対応しているため、Rails、SinatraなどRackベースのRubyアプリケーションで動作し、ActiveRecord、DataMapper、 Mongoidなど多くのORMをサポートしています。もうひとつCarrierWaveとPaperclipの大きな違いとして、CarrierWaveは添付ファイルとその処理ロジックを別のUploaderクラス内に持ちます。このため、添付ファイルがアプリケーションのモデルクラスと混ざって混乱するということがありません。
CarrierWaveのインストール
最初にCarrierWave gemをアプリケーションに追加します。gemはいつものようにGemfileに参照情報を追加してインストールします。
gem 'carrierwave'
bundleコマンドを実行し、 gemがインストールされたことを確認します。インストールが成功したら、まずuploaderクラスを作成します。CarrierWaveではuploader
というジェネレータが提供されるので、ここではimage
という名前でクラスを生成します。
$ rails g uploader image create app/uploaders/image_uploader.rb
ジェネレータが、app
内にuploaders
というディレクトリとその下にimage_uploader.rb
というファイルを生成します。このファイル内には、アップローダのカスタマイズ方法を解説したコメントがいくつか入っています。例えば、アップロード先を変更する、アップロード後に画像に処理を行う、アップロードできるファイルタイプを制限する、などの処理をカスタマイズできます。今はデフォルトのままにしておいて、後でカスタマイズを行うことにします。
次にPainting
モデルにアップローダを追加します。paintings
テーブルに列を追加するため、migrationを生成します。
$ rails g migration add_image_to_paintings image:string
作成する列はstring型で、uploaderクラスに付けたのと同じ名前にすることに注意してください。rake db:migrate
コマンドを実行し、データベースを更新します。
次にPainting
モデルにアップローダを追加します。具体的には、mount_uploader
メソッドを、引数として上で作成した列名とアップローダクラスを指定して、呼び出します。attr_accessible
の定義リストにもimage
列を追加し、コントローラからアクセスできるように設定します。
class Painting < ActiveRecord::Base attr_accessible :gallery_id, :name, :image belongs_to :gallery mount_uploader :image, ImageUploader end
これらを設定することで、絵の追加・修正用のフォームにファイルアップロード用のフィールドを持つことができます。合わせてform_for
に:html
オプションを追加して、フォームで添付ファイルを扱えるように修正します。
<%= form_for @painting, :html => {:multipart => true} do |f| %> <%= f.error_messages %> <%= f.hidden_field :gallery_id %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.file_field :image %> </p> <p><%= f.submit %></p> <% end %>
アップロードされた画像を見る
絵の登録のための編集画面を開くと、フォームにファイルアップロード用のフィールドが表示され、画像をアップロードすることができます。実行すると、アップロード成功のメッセージが表示されますが、ギャラリーにアップロードした絵は表示されていません。
ここでアップロードされた画像をプレースホルダーがある場所に表示させる必要があります。GalleryController
のshowテンプレートには、ギャラリー内の絵ごとに繰り返し処理で保存場所を出力するコードが記述されていて、画像のプレースホルダーのdiv
を出力します。これを、image_tag
を使って画像そのものに置き換え、それぞれの絵のimage_url
にリンクさせます。これでは、画像がない絵の場合にnil
を返すので、to_s
を呼び出してimage_tag
が正しく動作するようにします。
<div id="paintings"> <% @gallery.paintings.each do |painting| %> <div class="painting"> <%= image_tag painting.image_url.to_s %> <div class="name"><%= painting.name %></div> <div class="actions"> <%= link_to "edit", edit_painting_path(painting) %> | <%= link_to "remove", painting, :confirm => 'Are you sure?', :method => :delete %> </div> </div> <% end %> <div class="clear"></div> </div>
ページを再表示すると、アップロードされた画像が表示されますが大きすぎてプレースホルダーの枠を超えてしまいます。
画像が枠に正しく収まるようにリサイズできるといいでしょう。前に生成されたImageUploader
クラスを見てみると、コメントアウトされている画像処理のコードがあります。
# Process files as they are uploaded: # process :scale => [200, 300]
元の画像をリサイズしない方がいいので、別にサムネイル画像を作成しフルサイズの元画像へのリンクを表示します。ImageUploader
クラスのversion
メソッドを呼び出すことでこれを作ることができます。このメソッドはブロックをとり、process
を呼び出してサムネイルのバージョンの処理を行います。process
メソッドは柔軟で、オプションをハッシュ形式で指定します。ハッシュ内の各キーをループし、そのキーと同じ名前のメソッドを探してそのキーの値を引数をとして渡します。サムネイルを生成するために以下のように記述します。
# Create different versions of your uploaded files: version :thumb do process :scale => [50, 50] end def scale(width, height) # do something end
サイズを個別に指定しなくてはいけないというのは面倒ですし、幸いその必要はありません。RMagickを使えば自動的にサイズを設定できます。CarrierWaveにCarrierWave::RMagick
モジュールをincludeすることでRMagickを有効化できます。RMagickモジュールのドキュメントを見ると、モジュールをincludeすると利用できるメソッドを参照できます。これらの中には画像をリサイズするためのメソッドがいくつか含まれ、その中のresize_to_limit
を用いて、アスペクト比を保ったままリサイズすることができます。この機能を利用するため、uploaderクラスの次の行を非コメント化します。
include CarrierWave::RMagick
次にversionメソッドを非コメント化し、ブロックのprocess :resize_to_limit
を呼び出します。このメソッドは幅と高さの最大値を引数としてとるので、サムネイルの最大値を200 x 200ピクセルに制限します。
version :thumb do process :resize_to_limit => [200, 200] end
リサイズ機能を有効化させるためにGemfile
にRMagick gemの情報を追加します。
gem "rmagick"
いつものようにbundle
コマンドを実行し、 gemがインストールされたことを確認します。RMagickはImageMagickに依存するため、 RMagick gemをインストールする前に最新バージョンのImageMagickがインストールされていることを確認します。OS Xを使用している場合は、HomeBrewかMacPortsからImageMagickをインストールできます。
ビューにフルサイズの画像の代わりにサムネイルを表示させるために、image_url
の引数としてバージョンを特定します。
<%= image_tag painting.image_url(:thumb).to_s %>
ここでサムネイルを生成させるために再度画像をアップロードします。これによってギャラリーにサムネイル画像が表示されます。
URL経由で画像をアップロードする
CarrierWaveのもう一つの機能として、画像を直接アップロードする代わりにURLを介して画像を追加することもできます。このために、アップロード用フォームにremote_image_url
というフィールドを追加します。この名前が重要で、CarrierWaveがこの名前のフィールドを認識してURLから画像を取得します。
<%= form_for @painting, :html => {:multipart => true} do |f| %> <%= f.error_messages %> <%= f.hidden_field :gallery_id %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.file_field :image %> </p> <p> <%= f.label :remote_image_url, "or image URL" %> <%= f.text_field :remote_image_url %> </p> <p><%= f.submit %></p> <% end %>
painting
フォームに入力フィールドを追加したので、Painting
モデルのattr_accessible
で指定するフィールドのリストにも追加します。
class Painting < ActiveRecord::Base attr_accessible :gallery_id, :name, :image, :remote_image_url belongs_to :gallery mount_uploader :image, ImageUploader end
フォームにremote_image_url
フィールドができたので、そこにURLを入力すれば、手でアップロードすることなく画像を追加できます。
フォームを送信すると、別のサーバにある画像を取得してサムネイルが生成されます。
今回のエピソードはここまでですが、CarrierWaveにはまだ紹介していない機能も多くあるのでドキュメントを参照することをお勧めします。便利な機能として、フォームを再表示してもアップロードした画像をそのまま保持します。フォームにhiddenの状態で:image_cache
フィールドを追加して、フォームが送信されたときにモデルで検証エラーが発生した場合は、フォームが再表示された場合もアップロードされたファイルはそのまま保持されます。また、フォームにチェックボックスを追加してユーザが画像を削除できるようにしたり、保存場所オプションを変更して、アップロードされた画像をAmazon S3に保存することもできます。