読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

Rails3+paperclipでファイルアップロード

Ruby界のファイルアップロードのライブラリはいくつかあるけど、PaperclipcarrierwaveがTOP2らしい。

どっちも歴史、機能、活発度的に大きく変わらないようだけど以下の点からpaperclipを使って見ることにした。

  • 要件はどちらも満たしている(Rails、S3対応くらい)
  • 使い方をざっくり見たところ、paperclipのほうがシンプルに見える(あまり変わらない)。
    • carrierwaveはいろんなモノに対応しすぎててちょっと重すぎに見える。
    • Railsへの対応のしかたもpaperclipはバージョンごとに過去のRailsをバッサリ切り捨ててRailsに合ってそう。
  • carrierwaveはpaperclipからの移行I/Fも備えているので、paperclipが嫌になったら移行すればいい

動作まで

とりあえずS3は置いておいて、publicディレクトリ配下にアップロードするようにしてみる。

gemの追加

Gemfile

gem "paperclip", "~> 3.0"
ImageMagickのインストール
brew install imagemagick
modelの修正

User.rb

class User < ActiveRecord::Base
  attr_accessible :email, :name, :password
  attr_accessible :avatar
  has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }

とりあえず適当にドキュメントの見本通り。

migrateファイル修正 & migrate
class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email, :limit => 60, :null => false
      t.string :name, :limit => 20, :null => true
      t.string :password_digest, :limit => 100, :null => false
      t.attachment :avatar

      t.timestamps
    end
bundle exec rake db:migrate


こんな感じになる。

mysql> desc users;
+---------------------+--------------+------+-----+---------+----------------+
| Field               | Type         | Null | Key | Default | Extra          |
+---------------------+--------------+------+-----+---------+----------------+
| id                  | int(11)      | NO   | PRI | NULL    | auto_increment |
| email               | varchar(60)  | NO   | UNI | NULL    |                |
| name                | varchar(20)  | YES  |     | NULL    |                |
| password_digest     | varchar(100) | NO   |     | NULL    |                |
| avatar_file_name    | varchar(255) | YES  |     | NULL    |                |
| avatar_content_type | varchar(255) | YES  |     | NULL    |                |
| avatar_file_size    | int(11)      | YES  |     | NULL    |                |
| avatar_updated_at   | datetime     | YES  |     | NULL    |                |
| created_at          | datetime     | NO   |     | NULL    |                |
| updated_at          | datetime     | NO   |     | NULL    |                |
+---------------------+--------------+------+-----+---------+----------------+
テストも書いてみる

http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers

spec_helper.rb

require "paperclip/matchers"
Spork.prefork do
 ~snip~
  RSpec.configure do |config|
    config.include Paperclip::Shoulda::Matchers
~snip~


user_spec.rb

describe User do
~snip~
  describe "when avatar is not valid" do
    it { should validate_attachment_content_type(:avatar).
                  allowing('image/png', 'image/gif', 'image/jpeg', 'image/jpg').rejecting('text/plain', 'text/xml') }
    it { should validate_attachment_size(:avatar).
                  less_than(1.megabytes) }
  end
~snip~


動いた。当然エラったけど。

Failures:

  1) User when avatar is not valid 
     Failure/Error: allowing('image/png', 'image/gif', 'image/jpeg', 'image/jpg').rejecting('text/plain', 'text/xml') }
       Expected avatar:
       
       Accept content types: image/png, image/gif, image/jpeg, image/jpg
         All were accepted successfully.
       
       Reject content types: text/plain, text/xml
         text/plain, text/xml were accepted.
     # ./spec/models/user_spec.rb:152:in `block (3 levels) in <top (required)>'

  2) User when avatar format is not valid 
     Failure/Error: less_than(1.megabytes) }
       Attachment avatar must be between  and 1048576 bytes
     # ./spec/models/user_spec.rb:154:in `block (3 levels) in <top (required)>'

Finished in 2.47 seconds
34 examples, 2 failures

Failed examples:

rspec ./spec/models/user_spec.rb:151 # User when avatar format is not valid 
rspec ./spec/models/user_spec.rb:153 # User when avatar format is not valid 


user.rbに追記

class User < ActiveRecord::Base
  attr_accessible :username, :email, :name, :password, :avatar_content_type
  attr_accessible :avatar
  has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
  has_secure_password

  validates_attachment_size :avatar, :less_than => 1.megabytes
  validates_attachment_content_type :avatar, :content_type => ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']

これで、テスト通った。ほんとにこれで動くのか?


テストしてみる。

<%=  form_for(@user) do |f| %>
          <%= render 'shared/error_messages' %>
          <%= f.label :name, t('users.form.name') %>
          <%= f.text_field :name %>

          <%= f.label :email, t('users.form.email') %>
          <%= f.text_field :email %>

          <%= f.label :username, t('users.form.username') %>
          <%= f.text_field :username %>

          <%= f.label :password, t('users.form.password') %>
          <%= f.password_field :password %>

          <%= avatar_for(@user) %>
          <%= f.label :avatar %>
          <%= f.file_field :avatar %>

          <%= f.submit t('users.edit.form.submit'), class: 'btn btn-large btn-primary' %>
        <% end %>

こんな感じのフォーム。railsの画像は画像が設定されてないときに出してる画像。
テキトウな画像を設定してみる。

おおっ。変わった。うそくせーな。
でも、画像もちゃんと:rails_root/public/system/users/avatars/:idが3桁ずつに分割されたフォルダ/画像サイズの種類/ria10.pngに保存されてる・・・。
もちろん保存先のフォルダは自由に変えられる。

これ、すごいね。

ただ、これね。テスト書いてると実行のたびに実ファイルががんがん置かれてってエライことになるので、テスト実行後に消すようにしないといけない、ということに今気づいた。

追記)書いた->Paperclip + rail3 + rspec でテストごとに作られたファイルを消す方法

追記)必須バリデーションは以下のいずれかで。

validates :avatar, :attachment_presence => true
validates_with AttachmentPresenceValidator, :attributes => :avatar

Ruby on Rails環境構築ガイド

Ruby on Rails環境構築ガイド