PaperclipでGoogle Storageに画像を保存しているときに表示がクッソ遅い。

原因は表示するたびにファイルの存在確認のリクエストを出しているためで、1ファイルにつき200msぐらい遅くなってた。(30個の表示で計6秒遅くなってた)

対応方法は、fog_hostを設定しておけばリクエスト出さなくなるようで、普通の速度に戻りました。

config/initializers/paperclip.rb:

Paperclip::Attachment.default_options[:fog_host] = "http://#{ENV["GOOGLE_STORAGE_BUCKET_NAME"]}.storage.googleapis.com"

ユーザーが自分のアイコンをアップできるよくあるサービスの場合、

Amazon CloudFrontの使用上の注意とTipsまとめ|Media Technology Labs (MTL) : メディアテクノロジーラボ

よって、更新にしろ、削除にしろ、24時間くらいずれても、いいコンテンツでないとCloudFront には向かないことになります。

写真共有サイトでつかう場合だと、写真の加工とかがあった場合は、必ずファイル名を変更するような対応が必要です。

アップしたアイコンがなかなか反映されずに困る。そりゃそうだ。「誰かがなんとかしてくれてるはず」みたいないい加減な思いでCloudFrontつかっちゃってた。反省。

papaerclipを使ってる場合の良い方法は無いかな?

追記:

Using Amazon’s CloudFront with Rails & Paperclip – Tristan Media Blog

このブログに書いてあるようにpaperclipのinterpolatesを使ってtimestampをファイル名に埋め込むのがいいのかも。でもこれだとファイルがどんどん増えちゃうからexpiresを長くするとお金がかかってしまう。

CloudFrontのファイルを即時削除する方法が見つからなかった。それがあったとしてアップする前に削除するっていう処理を書くのも大変だなあ。みんなどうやってるんでしょう?

追記2:

paperclipだけS3のみにすることで対応した。(assetsはcloudfront)

paperclipで画像をアップロードしてた場合、S3に移行するのは簡単。paperclipはDB内にファイルのパスを持ってるわけじゃないので、サーバーファイルシステム上の画像を全部S3にコピーした上でアプリの設定を変えれば良い。

# Gemfile:
gem 'paperclip'
gem 'aws-s3'
gem 'aws-sdk'
# config/initializers/paperlicp.rb:
if Rails.env.production?       
  Paperclip::Attachment.default_options.update(
    storage: :s3,              
    s3_credentials: "#{Rails.root}/config/s3.yml",
    s3_host_alias: Settings.cdn_host,
    path: ":class/:id/:attachment/:style",
    url: ':s3_alias_url'
  )   
end
# config/s3.yml:
production:
  bucket: kowabana-jp
  access_key_id: xxx
  secret_access_key: xxx
  s3_host_name: s3-ap-northeast-1.amazonaws.com

development:
  bucket: bucket_name
  access_key_id: xxx
  secret_access_key: xxx
  s3_host_name: s3-ap-northeast-1.amazonaws.com

test:
  bucket: bucket_name
  access_key_id: xxx
  secret_access_key: xxx
  s3_host_name: s3-ap-northeast-1.amazonaws.com

modelのhas_attached_fileに渡すオプションが環境毎に違ってたりする場合はinitializers以下に書いた方がスッキリする。productionでだけS3(ここではCloudFrontを使うのもの一緒にやってます)にする場合は上記。(staging環境で何か動かす時にもS3に影響が出ちゃうので注意する)

Railsアプリで画像添付メール経由で画像を受け取りたい。(ガラケーとか)

/etc/aliases:

foo: "| cat $1 > /tmp/foo"

これだとファイルのownerがnobodyとかになっちゃうので.forwardを使う。

/home/unk/.forward:

"| cat $1 > /tmp/foo"

Railsでメールを受け取る

Railsでメールを受け取りたい。railsをCLIから呼び出すにはrails runnerを使う。しかし、railsコマンドはRAILS_ROOT外ではrails new用の動きをするので困る。

しかし、その為にあるのかも知れないscript/railsを使えばOK。

rvm経由で呼び出すと相当長くなる。

"| BUNDLE_GEMFILE=/var/www/foo/current/Gemfile /home/unk/.rvm/bin/rvm ruby-1.9.2p290@foo exec bundle exec /var/www/foo/current/script/rails runner 'PictureMailer.receive(STDIN.read)' -e production"

しかしまだ画像ファイルが600で作成されるという問題が残る。下記の様にchmodするようにしてもいいけど、umaskを変更する方がいいかも。

mod_passenger利用時の注意点 - komagata

"| umask 0022; BUNDLE_GEMFILE=/var/www/foo/current/Gemfile /home/unk/.rvm/bin/rvm ruby-1.9.2p290@foo exec bundle exec /var/www/foo/current/script/rails runner 'PictureMailer.receive(STDIN.read)' -e production"

umaskは子プロセスに引き継がれるのでPaperclip経由で出来るファイルもこれで644になる。

paperclipを使ってて「縦長の画像をアップしたら横長になる」という問題。

これはpaperclipが悪いんじゃなくて、元のファイルもMacのPreviewで見ると縦長なんだけど、ブラウザで見ると横長。MacのPreview他、ExifのOrientationタグに対応したソフトで見ると縦長に見える。

Webアプリ的にはちょっと困る。「縦長の画像をアップしたら横長になった!」と言われても見てるツールの違いで最初から横長なんだから。

imagemagickのconvertにはExifのOrientationタグの内容に合わせて画像データを回転させてくれる超便利な-outo-orientオプションがあるのでそれを使えばいい。

paperclipはrmagickを使わず、convertコマンドを呼び出すだけで、好きなオプションが渡せるというイカシタ作りになっているのでこんな感じでOK。

class User < ActiveRecord::Base
  has_attached_file :picture, convert_options: {all: '-auto-orient'}
end

allは複数のstyleが合った場合に全部にこのオプションを付けるという指定。originalも残るから安心。

RailsのオススメファイルアップロードプラグインのPaperclipですが、最新版(2.1.4)で嬉しい機能が増えてました。

  • ImageMagick(convertコマンド)に自由にオプションを渡せるようになった。
  • 画像が無いときのパスを指定できるようになった。

これは嬉しい。ソース内のコメントの例では、アップされた画像をlargeとnegativeというstyleに分け、両方にprofileやexif情報を削除する-stripオプションを適応し、negativeには補色で補完する(つまり反転?)-negateオプションを適応してます。

class User < ActiveRecord::Base
  has_attached_file :avatar,
    :styles => {
      :large => "300x300",
      :negative => "100x100"},
    :convert_options => {
      :all => "-strip",
      :negative => "-negate"}
end

RMagickや他のAPIを使わず、直接convert使ってる利点が生かされてますな。 他にも-monochromeオプションでモノクロにしたりとか色々出来そうです。

画像無いときのパス指定は、「あれ?これ指定できないの?」ぐらいだったんで俺がすっきりしただけですが。

以前にも書きましたが、Capistrano利用時にはアップ画像を/public/system以下に置きたいのでオプション指定が必要です。

いちいち指定してたらModelがキモくなるので通常、下記を使ってます。

config/initializers/paperclip.rb:

module Paperclip::ClassMethods
  def has_attached_file_with_default(name, options = {})
    default = {
      :path => ":rails_root/public/system/:class/:attachment/:id/:style/:basename.:extension",
      :url => "/system/:class/:attachment/:id/:style/:basename.:extension",
      :default_style => :normal,
      :default_url => "/images/:class/:attachement/missing_:style.png" 
    }

    has_attached_file_without_default(name, default.update(options))
  end

  alias_method_chain :has_attached_file, :default
end

config/initializers/以下は一番最後(pluginよりあと)に読まれるのでこういうアプリの本質的でない部分の変更には向いてますな。

RMagickインストール地獄にハマってる方はぜひご検討ください! (Debian etch、CentOS 5.2共にimagemagick/ImageMagickパッケージを入れるだけで済みます)

参照: Capistrano利用時のPaperclip

大体の画像系と同じようにnilとかを入れるだけでOK。

% ./script/console
Loading development environment (Rails 2.1.0)
>> grave = Grave.first
=> (略)
>> grave.overview.url
=> "/system/graves/overviews/1/normal/test_picture_large.gif" 
>> grave.overview = nil
=> nil
>> grave.save
=> true
>> grave.overview.url
=> "/overviews/normal/missing.png"

しかも、削除した場合のURLが自動的にmissing.pngに変わってる。

この勝手な挙動が嫌いな人もいるかもしれないけど面倒臭がりの俺としては嬉しい。

画像があるかどうかは、grave.overview.exists? で分かる。

thoughtbot: Paperclip File Attachments

Requirements

Only ImageMagick, which you can install quite easily install via apt, yum, or port, or the package manager of your choice.

あとやっぱりRMagick無くても動く!そういやImageMagick自体が問題なんじゃなくて、殆どの場合、パッケージのImageMagickが言語バインディングと合わないことが問題だったんだよね。PureRubyで再実装しかないと思ってた。頭良いな作者。

月曜日なので一心不乱にコード(仕事の)。

画像アップするプラグインを前に目を付けていたPaperclipに変えてみた。

file_columnと同じでファイルに画像を保存する形式。で、attachment_fuと違って専用のテーブルを作らないタイプ。

うーん、ファイルに保存方式だとcapistranoでdeployしてるとき困るんだよな~。そんでsystem以下に保存するように変えたり。

とりあえず使ってみた。

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

モデルにhas_attached_fileを定義。

class AddAvatarColumnsToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :avatar_file_name, :string
    add_column :users, :avatar_content_type, :string
    add_column :users, :avatar_file_size, :integer
  end
  (略)
end

DBは専用のじゃなくて普通にそのモデルのテーブルに上記3種を追加。

<% form_for :user, :html => { :multipart => true } do |form| %>
  <%= form.file_field :avatar %>
<% end %>

アップするフォームはこんな感じ。

<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>

画像表示もこんな風に深く考えなくてもいいようになってる。

あとImageMagickだけでRMagickが要らないっぽいです。そこは嬉しい。

で、file_columnでちょっと面倒だったファイルの保存場所関係はhas_attached_fileのオプションで結構柔軟っぽい設定ができそうでした。

:url => "/:attachment/:id/:style_:basename:extension" 
:url => "http://some.other.host/stuff/:class/:id_:extension"

シンプルなところが気に入りました。使ってこ。

参照: thoughtbot: Paperclip File Attachments