Herokuで作るFacebookアプリ

第11回Herokuでファイルアップロードを実装しよう

はじめに

前回は、Herokuのコマンドの拡張pluginについて解説しました。ちょっとマニアックだったかと思いますが、Herokuのオープンであるというビジョンが見える非常に面白い仕組みだと思います。今回は、マンガ共有サービスの機能を追加したいと思います。Webサービスでは必須の機能のファイルアップロードの仕組みをHerokuを利用する場合どのように実現するかを紹介します。

Herokuでのファイルの扱い

Heroku上では、ファイルをサーバ上に保存するということができません。これは、以下の理由からそのようになっていると思います。

  • Webサーバのインスタンスがどのサーバで動くかが特定できない
  • 複数インスタンスが動いた場合に、同じファイルシステムを共有できない

Herokuでは、マルチインスタンスのプラットホームであるためこのような課題が出てきます。この問題をおそらく解決することはできるかもしれませんが、Herokuではアクセスを不可にしています。ファイルシステムを扱うことは他のサービスと組み合わせて利用することで代替が可能です。そうすることで、Herokuはシンプルな構成を維持できるのだと思います。

Herokuでファイルアップロードを実現するためにはファイルをサーバのファイルシステムに保存するのではなく、別の場所に保存するようにしないといけません。一般的にHerokuでファイルを保存する際に利用されるのは、Amazon S3ではないでしょうか?

Amazon S3は、Amazonが提供しているファイルストレージサービスです。S3を利用するには、Amazon Web Serviceの登録が必要です。非常に堅牢で安価なので、多くのサービスがAmazon S3を利用してファイルの保存や配信を行っています。

今回は、そのAmazon S3を利用してファイルアップロードを実現する方法を紹介します。マンガ共有サービスとしては、マンガに自分の好きな写真を載せられるという機能とします。こちらにアクセスして動作を試してみてください。マンガの編集画面からファイルアップロードができるようになっています。

carrierwaveでファイルアップロード

Ruby on Rails でファイルアップロードを実現するために、いくつかのgemがあります。代表的なものとして、以下の3つが上がります。

今回はこの中でも一番新しく出てきたcarrierwaveというgemを利用してファイルアップロードを実装します。まずは、ファイルをアップロードする部分を標準のローカルファイルシステムに保存する形で機能を実現します。

Gemfileに以下の行を追加します

gem "carrierwave"

gemを追加したので、bundlerを利用してgemをインストールします。

$ bundle

まずは、carrierwaveを利用するためにファイルをgenerateします。以下のコマンドを発行すると、ファイルアップロード用のクラスが生成されます。

$ rails g uploader image

既存のmangaクラスに画像を追加する形にするために、mangasテーブルにimageカラムを追加します。

$ rails g migration add_image_to_mangas image:string

マイグレーションファイルが生成されるので、マイグレーションを実行します。

$ rake db:migrate

次に、mangaクラス(app/models/manga.rb)にアップロードファイルをひもづけるためのコードを追加します。

attr_accessible :title, :author, :amazon_image_url, :image
mount_uploader :image, ImageUploader

attr_accessibleに、:imageを追加して、フォームから登録が可能にします。mount_uploaderメソッドを実行してクラスにアップロードファイルを紐付けます。

最後に、編集のための入力フォーム(app/views/mangas/_form.html.haml)を修正します。

= form_for @manga, :html => {:multipart => true} do |f|
  = f.error_messages
  %p
    = f.label :title
    %br
    = f.text_field :title
  %p
    = f.label :author
    %br
    = f.text_field :author
  %p
    = f.file_field :image
  %p
    = f.submit

form_forメソッドに :html => {:multipart => ture} の引数を追加してformタグをマルチパートにしてファイルアップロード可能にします。f.file_field :imageを追加してファイルアップロードの入力ボックスを追加します。

以上の変更点は、こちらのコミットになっています。carrierwaveを用いることで非常に簡単にファイルアップロードが実現できました。

保存先をAmazon S3に

続いて、Herokuで利用できるようにAmazon S3を利用するように変更していきましょう。carrierwaveで保存先をS3に指定するためには、fogというgemを利用します。このgemはAmazon S3に限らず様々なクラウドのサービスをRubyから利用しやすくしてくれるライブラリです。carrierwaveでは、S3を利用するためにこのgemを使うので以下の行をGemfileに追加してfogを利用できるようにします。

gem “fog”

gemを追加したので、bundlerを実行します。

$ bundle

carrierwaveでS3を利用するために設定ファイルを追加して、設定を書きます。config/initializers/carrierwave.rbというファイルを以下の内容で作成します。

CarrierWave.configure do |config|
  config.fog_credentials = {
    :provider               => 'AWS',
    :aws_access_key_id      => ENV["AWS_S3_KEY_ID"],
    :aws_secret_access_key  => ENV["AWS_S3_SECRET_KEY"]
  }
  config.fog_directory  = ENV["AWS_S3_BUCKET"]
end

Herokuでは、鍵の情報や環境ごとに異なる設定などはENVに登録するとコマンドと連携して設定しやすいのでENVから読み出すようにしました。以下のコマンドを発行すれば、ENVを設定することができます。

heroku config:add AWS_S3_KEY_ID="xxxx" AWS_S3_SECRET_KEY="yyyy" AWS_S3_BUCKET="gggg"

KEY_IDとSECRET_KEYは、Amazon Web Serviceの「セキュリティー証明書」のページからさ取得できます。BUCKETは、S3の管理画面からbucketを作成しその名前を設定します。

次に、ImageUploaderクラス(app/uploaders/image_uploader.rb)を編集して、ファイルアップロード先をS3に指定します。storageメソッドの引数に:fileが指定されていますが、以下のように:fogを指定するようにします。

storage :fog

また、最終的にはファイルはS3に保存されるのですが、一旦はHerokuのインスタンスで受け取る形になります。そのような場合のために、app/tmpディレクトリは一時的に書き込み可能になっています。carrierwaveの初期設定でファイルを一旦保持するためのディレクトリが別のディレクトリになっているので、以下のメソッドを定義して一時保存先をtmpディレクトリ以下に変更します。

def cache_dir
  "#{Rails.root}/tmp/uploads"
end

これで保存先をS3に変更できました。非常に簡単ですね。この保存先の変更のコミットはこちらになっています。

まとめ

carrierwaveとAmazon S3を利用することで、ファイルの保存に制限のあるHerokuでも、これほど簡単にファイルアップロード機能が実現できました。Herokuがマルチテナントのプラットホームであるがゆえの制限ですが、これほど簡単に問題が解決できるようになっているのであれば制限というほどではないと考えられるのではないでしょうか。

次回は、Webサービスで一般的に利用されるであろう非同期のサーバでの処理をHerokuで実現する方法を紹介します。

おすすめ記事

記事・ニュース一覧