Freelance Orgsin Official Site

ごゆっくりしていって下さい

paperclip から active storage へ移行する

経緯

paperclipは超有名なものだが,Rails5.2からはActiveStorageを推奨される.ちょうど5.2に上げたのでこの際マイグレーションしてしまおうという話.

非推奨記事

ちょうどのタイミングではてブに上がってた.

robots.thoughtbot.com

ActiveStorage

かなりわかりやすいのでガイドも貼っておく.

railsguides.jp

マイグレーション

Apply the ActiveStorage database migrations.
Configure storage.
Copy the database data over.
Copy the files over.
Update your tests.
Update your views.
Update your controllers.
Update your models.

github.com

基本的には,この手順通りで良いがそのままやってもうまくいかないので注意点を書いておく.移行タスクがまたあった時に最短でできるので.

前提

移行が完了するまでpaperclip関連のカラムを消してはいけない.


So, assuming you want to leave the files in the exact same place, this is your migration. Otherwise, see the next section first and modify the migration to taste.

マイグレーションタスクをそのままコピっても動かないのでその部分をメモしておく

postgresなのかmariadbなのかsqliteなのか

最後にinsertされたレコードを取得するための関数が変わる.

class ConvertToActiveStorage < ActiveRecord::Migration[5.2]
  def up
    # postgres
    get_blob_id = 'LASTVAL()'
    # mariadb
    # get_blob_id = 'LAST_INSERT_ID()'
    # sqlite
    # get_blob_id = 'LAST_INSERT_ROWID()'
paperclipで利用していたモデルのデータを取得しているだけ

attachされずnilのレコードがある場合はここでnilチェックをした方が良いかもしれない.デフォルトURLの設定によっては,エラーになるかもしれない. とは言え,普通はinsertのタイミングでレコードが作られるような場面が多いと思われるのでさほど重要ではない.

        model.find_each.each do |instance|
          next if instance.try(:image_file_name).nil?

          attachments.each do |attachment|            
〜〜〜〜〜
単純にローカルかリモートかで使い分ける
  def checksum(attachment)
    # ローカルはこっち
    url = attachment.path
    Digest::MD5.base64digest(File.read(url))

    # S3とかはこっち
    # url = attachment.url
    # Digest::MD5.base64digest(Net::HTTP.get(URI(url)))
  end

画像の指す場所はpaperclip と activestorageで違うので注意した方がよい.
# ローカル
ActiveStorageAttachment.find_each do |attachment|
  name = attachment.name

  source = attachment.record.send(name).path
  dest_dir = File.join(
    "storage",
    attachment.blob.key.first(2),
    attachment.blob.key.first(4).last(2))
  dest = File.join(dest_dir, attachment.blob.key)

  FileUtils.mkdir_p(dest_dir)
  puts "Moving #{source} to #{dest}"
  FileUtils.cp(source, dest)
end
# S3
  ActiveStorageAttachment.find_each do |attachment|
    source = attachment.record.send(name).try(:path)
    next if source.nil?

    dest = attachment.blob.key

    bucket = 'hogehoge' # 自分のバケット
    region = 'ap-northeast-1' # リージョン

    client = Aws::S3::Client.new(region: region, access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'), secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'))
    puts "Moving #{bucket}#{source} to #{dest}"
    client.copy_object(bucket: bucket, copy_source: "#{bucket}#{source}", key: dest)
  end
もしpaperclipのデータをDBから消しちゃった場合

事前にS3からpaperclipのハッシュキーを取得しておかないといけない.以下は適当に書いているが,キーと拡張子だけ取得しておくなどして↑のタスクのsourceと入れ替える処理にするなどして対応するとか.

client = Aws::S3::Client.new(region: 'ap-northeast-1', access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'), secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'))
client.list_buckets.buckets.map(&:name) # 必要なバケットを探す
contents = client.list_objects(bucket: 'hogehoge').contents
keys = contents.map { |v| v =~ /tweets\/images\/([0-9]*)\/original\/(.+)(\.)(png|jpg|jpeg)/; {"#{$1.to_i}": "#{$2}#{$3}#{$4}"} if $1 }
keys.compact.reject(&:empty?).inject(&:merge)

一旦DBのデータ移行がうまくいったかどうかは確認した方が良い.

rails c
> a = User.first.image.attachment
> a.blob