アトラシエの開発ブログ

株式会社アトラシエのブログです

carrierwaveで画像フォーマットを複数持つ方法

carrierwaveはサンプルを読む限りでは保存形式をjpegとかpngとかに決め打って、versionではリサイズしかしないのが一般的な使い方のようです。

しかしこれだと「ネイティブアプリではwebp形式で配信し、ブラウザにはjpeg形式で配信したい」というようなユースケースに対応できません。

carrierwaveはこういったフォーマットのマルチ化ができないのかと思っていたのですが、コードレベルでちょっと工夫すれば簡単にできました。

class PictureUploader < CarrierWave::Uploader::Base

  version :limit_500x_jpeg do
    process convert: 'jpeg'
    process resize_to_limit: [500, 10000]
  end

  version :limit_500x do
    process convert: 'webp'
    process resize_to_limit: [500, 10000]
    def full_filename(for_file)
      super(for_file).sub(/\.jpeg/, '.webp')
    end
  end

  version :fill_500x500 do
    process convert: 'webp'
    process resize_to_fill: [500, 500]
    def full_filename(for_file)   # ・・・※2
      super(for_file).sub(/\.jpeg/, '.webp')
    end
  end

  def filename    #・・・※1
    "#{secure_token}.jpeg" if original_filename.present?
  end

  def extension_white_list
    %w(jpg jpeg gif png webp)
  end

  def secure_token
    if model.attributes[mounted_as.to_s].present? && !model.send("#{mounted_as}_changed?")
      model.attributes[mounted_as.to_s].split('.').first
    else
      get_secure_token
    end
  end

  def get_secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var,  SecureRandom.uuid)
  end
end

このように、まずは全体のfilenameをjpegで決め打って(※1の部分)、あとは各バージョンでプライベートメソッドをオーバーライドして(※2の部分)filenameを変更します。 これだけでアップロード先のURLも画像URLもうまく対応できます。

ちなみに本当は※1の部分でjpegを決め打たずにextensionなしのfilenameを格納し、各バージョンごとにextensionをつけたほうが見た目は綺麗なのですがその方法だとmodel.picture.urlというバージョン抜きのURLの拡張子に対応できないので、少し違和感がありますがsubで置換させることにしました。

ちなみにsub置換のコストは手元のベンチマークでは誤差レベルです。

carrierwaveの限界

とはいえ、個人的にはcarrierwaveは便利ですが限界があるように感じてきました。まずリサイズに時間がかかること、画像のサイズ変換というあまりアプリケーションのビジネスロジックに関係ない計算コストを持たないといけないこと、サイズの決め打ちがマルチデバイス対応で辛いこと等です。

大手だと独自のリサイズ配信システムをnginxモジュールなどで作ってCDNを咬ませてキャッシュさせるようなことをしていると思いますが、これも知識を持った人でないと開発・運用は難しいでしょう。

cloudinary.com

あまりはやってないですがこういう配信サービスもあるので、これを利用するのも手です。 また、google app engineでは標準で画像配信を動的に行う仕組みを持っているので、配信主体のアプリケーションはappengineで作るか、配信部分だけでもapp engineで社内サービス化するのがいいかも。

qiita.com