2011年8月7日日曜日

S3ってなんじゃ?(RUBY SDK鈍行編3 PaperclipでRailsからS3へファイルアップロード)

今回はRuby on RailsからS3へファイルをアップロードしてみたいと思います。
RailsにはPaperclipというファイルアップロードプラグインがあり、Marcel Molina Jr.さん謹製のAWS::S3というgemを使用したS3へのアップロードもサポートしているのですが、今回はせっかくAmazonの公式ruby sdkが公開されたので、そちらを利用したPaperclip拡張である@igor-alexandrovさんのpaperclip-awsを使用してS3ファイルアップロードを試してみます。

PaperclipはImageMagickを使用するので、まずImageMagickをインストールします。
yumからだとバージョンが古い場合があるので、ソースからインストールします。
# cd /usr/local/src
# wget ftp://ftp.imagemagick.org/pub/ImageMagick/ImageMagick.tar.gz
# tar xzvf ImageMagick.tar.gz
# cd ImageMagick-6.7.1-2
# ./configure
# make
# make install

次にldconfigでライブラリの更新をシステムに知らせます。
# ldconfig /usr/local/lib

Gemfileに以下のgemをインストールします。
  • RMagick(ImageMagickのRubyラッパー)
  • Paperclip(ファイルアップロードプラグイン)
  • paperclip-aws(paperclipでのS3アップロード機能拡張)
$ vi Gemfile
gem 'rmagick'
gem "paperclip", "~> 2.3"
gem "papepclip-aws"

gemのインストールと更新を行います。
$ bundle install vendor/bundle
$ bundle update

ImageMagickへのパスを通します。
$ vi config/environments/development.rb 
Paperclip.options[:command_path] = "/usr/local/bin/"

railsでScaffoldを作成してみます。
今回は簡易ブログを作成する想定で、blogというモデルのscaffoldを作成しました。
$ rails g scaffold blog title:string description:text

モデルにファイルアップロードの項目を追加します。
paperclipのhas_attacched_fileメソッドの引数では、デフォルトのpaperclipの項目以外にpaperclip-awsで追加されたs3用の項目も指定できます。
項目についてはここで解説されています。
ここではphotoという名前で追加してみます。このブログの鈍行編1の設定でaws.ymlにアクセスキーとシークレットキーしか設定していなかったので、今回その他の設定は直接指定していますが、これらもaws.ymlに含めてしまうこともできます。以下赤字が追加した部分です。
$ vi app/models/blog.rb
class Blog < ActiveRecord::Base

  def self.s3_config
    @@s3_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/aws.yml")).result)[Rails.env]
  end

  has_attached_file :photo,
                    :styles => {
                      :thumb => [">75x"],
                      :medium => [">600x"]
                    },
                    :storage => :aws,
                    :s3_credentials => {
                      :access_key_id => self.s3_config['access_key_id'],
                      :secret_access_key => self.s3_config['secret_access_key'],
                      :endpoint => 's3-ap-northeast-1.amazonaws.com'
                    },
                    :s3_bucket => 'myfirst-bucket',
                    :s3_host_alias => self.s3_config['s3_host_alias'],
                    :s3_acl => :public_read,
                    :s3_protocol => 'http',
                    :path => "images/:id/:style/:data_file_name"

end

続いて、viewに画像ファイルの項目を追加します。
viewは通常のpaperclipのviewの書き方とまったく同じです。
まずは、入力画面にphoto属性のファイル選択フィールドを追加します。
$ vi app/views/blogs/_form.html.erb
<%= form_for(@blog, :html => { :multipart => true } ) do |f| %>
  <% if @blog.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@blog.errors.count, "error") %> prohibited this blog from being saved:</h2>

      <ul>
      <% @blog.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description %>
  </div>

  <div class="field">
    <%= f.label :photo %><br />
    <%= f.file_field :photo %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

詳細画面や完了画面で写真が確認できるようにを表示するようにphotoのimgタグを追加します。
$ vi app/views/blogs/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Title:</b>
  <%= @blog.title %>
</p>

<p>
  <b>Description:</b>
  <%= @blog.description %>
</p>


<%= image_tag @blog.photo.url(:medium) %>

<%= link_to 'Edit', edit_blog_path(@blog) %> |
<%= link_to 'Back', blogs_path %>

一覧画面にサムネイルを表示するようにサムネイル用のimgタグを追加します。
$ vi app/views/blogs/index.html.erb
<h1>Listing blogs</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Description</th>
    <th>Photo</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @blogs.each do |blog| %>
  <tr>
    <td><%= blog.title %></td>
    <td><%= blog.description %></td>
    <td><%= image_tag blog.photo.url(:thumb) %></td>
    <td><%= link_to 'Show', blog %></td>
    <td><%= link_to 'Edit', edit_blog_path(blog) %></td>
    <td><%= link_to 'Destroy', blog, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Blog', new_blog_path %>


次に、paperclip用の項目をDBに追加するためのマイグレーションファイルを作成します。
$ rails g migration add_photo_columns_to_blog
      invoke  active_record
      create    db/migrate/20110805111437_add_photo_columns_to_blog.rb
$ vi db/migrate/20110805111437_add_photo_columns_to_blog.rb 
class AddPhotoColumnsToBlog < ActiveRecord::Migration

  def self.up
    add_column :blogs, :photo_file_name,    :string
    add_column :blogs, :photo_content_type, :string
    add_column :blogs, :photo_file_size,    :integer
    add_column :blogs, :photo_updated_at,   :datetime
  end

  def self.down
    remove_column :blogs, :photo_file_name
    remove_column :blogs, :photo_content_type
    remove_column :blogs, :photo_file_size
    remove_column :blogs, :photo_updated_at
  end

end

マイグレーションを行います。
$ rake db:migrate
(in /var/www/html/myfirstcloud)
==  CreateBlogs: migrating ====================================================
-- create_table(:blogs)
   -> 0.0011s
==  CreateBlogs: migrated (0.0014s) ===========================================

==  AddPhotoColumnsToBlog: migrating ==========================================
-- add_column(:blogs, :photo_file_name, :string)
   -> 0.0007s
-- add_column(:blogs, :photo_content_type, :string)
   -> 0.0004s
-- add_column(:blogs, :photo_file_size, :integer)
   -> 0.0005s
-- add_column(:blogs, :photo_updated_at, :datetime)
   -> 0.0004s
==  AddPhotoColumnsToBlog: migrated (0.0028s) =================================

これで、S3にアップロードすることができるようになりました。

ブラウザで確認してみます。
http://IPアドレス/blogs
にアクセスすると、以下のようにscaffoldで作成された一覧画面が表示されます。



New Blogのリンクをクリックして、新規登録画面を表示すると、
画像のアップロードのフィールドがあることが確認できます。
テキストを入力し、画像を選択したらCreate Blogボタンをクリックします。



すると、登録完了の画面として、アップロードした写真が表示されます。



また、一覧画面に戻ると、一覧にもサムネイルが表示されているのがわかります。



表示されている画像のURLをみると以下のようにS3からホストされていることが確認できます。

http://s3-ap-northeast-1.amazonaws.com/myfirst-bucket/images/1/thumb/:data_file_name
簡単にS3ファイルアップロードを実装することができました。

本日はこれにて。