最近画像管理にcarrierwaveを使っていたものをactivestorageに移行した。 移行のモチベーションはローカルに置いていた画像をGCSとかに置きたいなと思ったが、どうせ対応するならactivestorageに移行してからにしようかと思ったため。 今回はactivestorageに移行したがファイルはlocalに置いたままで、GCSに置くのはまたGKEに移すときにでも考えようと思っている。
5系のrailsで移行しようとしていたが、そろそろ6が出る && 6系で破壊的に変わる部分があるというのをissueでちら見していたので一応activestorageのmigrationファイルを確認。 当たり前だけど、DBに関しては破壊的変更はなさそうだったのでそのままやることにした。
移行にあたってはとりあえずraiseはしないことだけ主目的に移行した。 業務だったらもう少しいい感じにやらないといけないが、個人サービスなので多少UXが下がる瞬間があっても良いかな、という気持ち。
- carrierwaveで管理している画像をactivestorageに移行するためのメソッドを生やす
- 新規で画像が取り込まれるときにはactivestorage側に保存するようにする
- 画像を表示する部分はactivestorageで画像が管理されているものを表示するようにする
- capistranoを使っているならlinked_dirsでpublic/uploadsを指定していると思うが、storageに変更しておく(localに置いている人限定)
一旦ここまでやってデプロイ。 移行するためのメソッドは具体的には以下のようなものを生やした。
class User < ApplicationRecord mount_uploader :image, ProfileImageUploader has_one_attached :avatar def self.migrate_as! failed_user_ids = [] where.not(image: nil).find_each do |user| next unless user.image.url user.avatar.attach(io: open(user.image.file.file), filename: 'avatar.png') # URI.openを使ったほうが無難 rescue Errno::ENOENT failed_user_ids.push(user.id) user.remove_image! end failed_user_ids end end
user.image
にcarrierwaveをぶら下げていたが、たまになぜかimageがある扱いなのにないやつがいたので rescue Errno::ENOENT
で救う。
今回はそういう人たちは画像はなかったこと扱いにした。
が、一応念のためにidだけメモっておきたかったので配列で確保しておく。
デプロイした後は上記メソッドを速やかに叩いて表面上の移行は完了。 ただし、ちゃんとやるなら以下のような点を注意する必要があるなと感じた。
- デプロイされる
- user Aが新しく画像を設定(activestorageに保存される)
- 移行スクリプトを叩く => user Aの画像がcarrierwave時代のものに戻ってしまう
まぁメンテナンスタイムを設けるのが無難だとは思う。
次に残ったcarrierwaveをいろいろと抹消していく。
- carrierwaveで使っていたカラムの削除(今回であればuser.imageを消すmigrationファイルを書く)
- uploads系のファイルとかを含めたcarrierwave依存のロジックを消す
- デプロイ先のpublic/uploads削除(localに置いている人限定)
.migrate_as!
の削除
今回ちょっと手間だったのは user.image.url
をAPI経由で返している場所。
viewで使う分には
= image_tag(url_for(user.avatar)) if user.avatar.attached?
と書けばいいが、modelでurlを返すメソッドを生やしてるとこうは書けない。 自分の場合は以下のように書いて対応した。
Rails.application.routes.url_helpers.rails_blob_path(avatar, disposition: :inline, only_path: true)
only_path: true
をつけているので、host名は含まれずにpathだけで返される。
なのでurlを実際に使う側でいい感じにhost名を補完する必要がある(もちろん上記にhost名を返すように追加ロジックを書いてもいい)。
おまけ
carrierwaveを消す際にactivestorageではいろいろテーブルを追加してファイルを管理しているが、carrierwaveではどのように管理しているのかが気になった。
今回はimageというカラムを生やしていたので、imageにいろいろ情報が突っ込まれてるのかな?と思ったがstring型である。
string型ということはserialize hashとかにして管理してるのか…と思って中身を見たら image
という文字列しか入ってない。
これはどういうことなんだろう、と思ってuploaderの実装(数年前なので全然覚えてなかった)を見に行ったら以下のような感じだった。
class ProfileImageUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end version :thumb do process resize_to_fit: [50, 50] end end
カラムがnilじゃなかったらstore_dirから探索してファイルを置くような感じっぽい。 少しドキュメントを読んでみた感じだとserialize jsonとしてデータを保存することもできたようだ。
脳死でとりあえずcarrierwaveを入れてしまっており、挙動をあんまり理解していなかったのでactivestorageではもう少しこの辺も意識していきたいと思う。