こんにちは、あさのです。
前にActive Storageの記事を書きましたが、今回もそのネタで行こうと思います。
Active Storage移行記:バリデーション編|sitateru tech blog
今回は、Paperclip管理のデータをActive Storage管理に移行したときのお話です。
データ構造
Paperclipではファイルのデータは各モデルのカラムに保存されています。
userクラスのiconというファイルであれば、userテーブルに以下のようなカラムがあります。
t.string "icon_file_name", comment: "ファイル名"
t.string "icon_content_type", comment: "ファイルのcontent type"
t.integer "icon_file_size", comment: "ファイルサイズ"
t.datetime "icon_updated_at", comment: "ファイル更新日時"
対してActive Storageでは全クラスの全ファイルをまとめて2つのテーブルで管理します。
create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
たとえば、
- userクラスのicon
- user.id=15
- ファイル名 prof.jpg
というファイルだと以下のようなレコードになります。
2つのテーブルは blob_id
で関連づけられるんですね。
id name record_type record_id blob_id created_at
15 icon User 3 16 2017-12-28 19:23:03
id key filename content_type metadata byte_size checksum created_at
16 3e0ca647-18e3-43ee-85c4-4de552bac46f prof.jpg image/jpeg {} 44202 isFyYNJIs/6pyptfRUMUlA== 2017-12-28 19:23:03
データ移行
さて、それぞれのデータ構造がわかったのでマイグレーションをしていきます。
このあたりの記事を参考にさせてもらい、rakeタスクで実装しました。
namespace :import_to_active_storage do
desc 'Import to ActiveStorage'
task run_all: :environment do
models = ActiveRecord::Base.descendants.reject(&:abstract_class?)
import_data(models)
end
private
def import_data(models)
conn = ActiveRecord::Base.connection
models.each do |model|
attachments = model.column_names.map do |c|
c =~ /(.+)_file_name$/ && Regexp.last_match(1)
end.compact
model.find_each do |instance|
attachments.each do |attachment|
puts "Process #{model.name}/#{instance.id}"
next unless instance.send("#{attachment}?")
next if conn.select_one("SELECT `id` FROM active_storage_attachments WHERE `record_type` = '#{model.name}' AND `record_id` = #{instance.id}").present?
puts "Migrate #{instance.send(attachment.to_s)}"
ActiveRecord::Base.transaction do
execute_statement(conn, model, instance, attachment)
end
end
end
end
end
def execute_statement(connection, model, instance, attachment)
active_storage_blob_statement(connection).execute(
key,
instance.send("#{attachment}_file_name"),
instance.send("#{attachment}_content_type"),
instance.send("#{attachment}_file_size"),
checksum(instance.send(attachment)),
instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
)
active_storage_attachment_statement(connection).execute(
attachment,
model.name,
instance.id,
instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
)
end
def active_storage_blob_statement(connection)
connection.raw_connection.prepare(<<-SQL)
INSERT INTO active_storage_blobs (
`key`, `filename`, `content_type`, `metadata`, `byte_size`, `checksum`, `created_at`
) VALUES (?, ?, ?, '{}', ?, ?, ?)
SQL
end
def active_storage_attachment_statement(connection)
connection.raw_connection.prepare(<<-SQL)
INSERT INTO active_storage_attachments (
`name`, `record_type`, `record_id`, `blob_id`, `created_at`
) VALUES (?, ?, ?, LAST_INSERT_ID(), ?)
SQL
end
def key
SecureRandom.uuid
end
def checksum(image)
uri = URI.parse("https:#{image.url}")
body = Net::HTTP.get(uri)
Digest::MD5.base64digest(body)
end
end
そこそこ長いですが、結局は各モデルの各ファイルごとにPaperclipのカラム情報とファイルのハッシュ値を取り、
Active Storageのテーブルに入れるという繰り返しです。
なお、各モデルの実装をActive Storage用に書き換えた後ではこのタスクは動かないので、
Paperclip時に実行する必要があります。
実際にやったところ数万件のデータがあったために5~6時間かかるという大引っ越しになってしましましたが、
なんとか乗り切りました。
ということで今回はActive Storageのデータ移行について書きました。
まだもう少しActive Storageネタで書けるかもしれません。
それでは。