ruby on rails Active Storage

activestorageを試している。

$ rails active_storage:install

を実行すると、

  • active_storage_attachments
  • active_storage_blobs

以下のテーブルが作られる。

npmパッケージやassets piipelineで利用できるjsはdirect uploadに対応している。

has_one_attached

# メソッド一覧
(byebug) @user.methods.grep(/avatar/).sort
[:autosave_associated_records_for_avatar_attachment, :autosave_associated_records_for_avatar_blob, :avatar, :avatar=, :avatar_attachment, :avatar_attachment=, :avatar_blob, :avatar_blob=, :build_avatar_attachment, :create_avatar_attachment, :create_avatar_attachment!, :reload_avatar_attachment, :reload_avatar_blob]

# avatarの中身
(byebug) @user.avatar
#<ActiveStorage::Attached::One:0x00007fb5cd150268 @name="avatar", @record=#<User id: 1, name: "test", created_at: "2018-01-13 06:46:34", updated_at: "2018-01-13 06:49:12">, @dependent=:purge_later>

# avatar.recordは親のレコード情報
(byebug) @user.avatar.record
#<User id: 1, name: "test", created_at: "2018-01-13 06:46:34", updated_at: "2018-01-13 06:49:12">

# avatar_attachmentはrecordとblobの紐付けレコードっぽい
(byebug) @user.avatar_attachment
#<ActiveStorage::Attachment id: 2, name: "avatar", record_type: "User", record_id: 1, blob_id: 2, created_at: "2018-01-13 06:49:12">
(byebug) @user.avatar_attachment.blob
#<ActiveStorage::Blob id: 2, key: "MvoHQ4n7qiFNEGHdsWHKSHSH", filename: "favicon-32x32.png", content_type: "image/png", metadata: {"analyzed"=>true}, byte_size: 4341, checksum: "cSRCQZDZGQ7muZTKtIcqow==", created_at: "2018-01-13 06:49:12">
(byebug) @user.avatar_attachment.record
#<User id: 1, name: "test", created_at: "2018-01-13 06:46:34", updated_at: "2018-01-13 06:49:12">

# blobファイルのレコードへアクセス
(byebug) @user.avatar_blob
#<ActiveStorage::Blob id: 2, key: "MvoHQ4n7qiFNEGHdsWHKSHSH", filename: "favicon-32x32.png", content_type: "image/png", metadata: {"analyzed"=>true}, byte_size: 4341, checksum: "cSRCQZDZGQ7muZTKtIcqow==", created_at: "2018-01-13 06:49:12">
(byebug) @user.avatar_blob.filename
#<ActiveStorage::Filename:0x00007fb5c7462b28 @filename="favicon-32x32.png">

# avatar.blobという呼び出し方でも良い
(byebug) @user.avatar.blob
#<ActiveStorage::Blob id: 2, key: "MvoHQ4n7qiFNEGHdsWHKSHSH", filename: "favicon-32x32.png", content_type: "image/png", metadata: {"analyzed"=>true}, byte_size: 4341, checksum: "cSRCQZDZGQ7muZTKtIcqow==", created_at: "2018-01-13 06:49:12">

has_many_attached

# method一覧
(byebug) @user.methods.grep(/documents/).sort
[:after_add_for_documents_attachments, :after_add_for_documents_attachments=, :after_add_for_documents_attachments?, :after_add_for_documents_blobs, :after_add_for_documents_blobs=, :after_add_for_documents_blobs?, :after_remove_for_documents_attachments, :after_remove_for_documents_attachments=, :after_remove_for_documents_attachments?, :after_remove_for_documents_blobs, :after_remove_for_documents_blobs=, :after_remove_for_documents_blobs?, :autosave_associated_records_for_documents_attachments, :autosave_associated_records_for_documents_blobs, :before_add_for_documents_attachments, :before_add_for_documents_attachments=, :before_add_for_documents_attachments?, :before_add_for_documents_blobs, :before_add_for_documents_blobs=, :before_add_for_documents_blobs?, :before_remove_for_documents_attachments, :before_remove_for_documents_attachments=, :before_remove_for_documents_attachments?, :before_remove_for_documents_blobs, :before_remove_for_documents_blobs=, :before_remove_for_documents_blobs?, :documents, :documents=, :documents_attachment_ids, :documents_attachment_ids=, :documents_attachments, :documents_attachments=, :documents_blob_ids, :documents_blob_ids=, :documents_blobs, :documents_blobs=, :validate_associated_records_for_documents_attachments, :validate_associated_records_for_documents_blobs]

(byebug) @user.documents.class
ActiveStorage::Attached::Many
(byebug) @user.documents_attachments
  CACHE ActiveStorage::Attachment Load (0.0ms)  SELECT  "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 1], ["record_type", "User"], ["name", "documents"], ["LIMIT", 11]]
#<ActiveRecord::Associations::CollectionProxy [#<ActiveStorage::Attachment id: 3, name: "documents", record_type: "User", record_id: 1, blob_id: 3, created_at: "2018-01-13 07:15:50">, #<ActiveStorage::Attachment id: 4, name: "documents", record_type: "User", record_id: 1, blob_id: 4, created_at: "2018-01-13 07:15:50">, #<ActiveStorage::Attachment id: 5, name: "documents", record_type: "User", record_id: 1, blob_id: 5, created_at: "2018-01-13 07:23:48">]>
(byebug) @user.documents_blobs
  CACHE ActiveStorage::Blob Load (0.0ms)  SELECT  "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 1], ["record_type", "User"], ["name", "documents"], ["LIMIT", 11]]
#<ActiveRecord::Associations::CollectionProxy [#<ActiveStorage::Blob id: 3, key: "JmoQ7kirgRS4KEufb3Ka9Dhj", filename: "IMG_0398.JPG", content_type: "image/jpeg", metadata: {"analyzed"=>true}, byte_size: 1172434, checksum: "zGwD8g/ZLOz18yZKK++gjQ==", created_at: "2018-01-13 07:15:49">, #<ActiveStorage::Blob id: 4, key: "7NPN1h3ay3vY8j98wjScSfV7", filename: "IMG_0484.JPG", content_type: "image/jpeg", metadata: {"analyzed"=>true}, byte_size: 1491969, checksum: "NwVtqcGENtT7ME96zNg2Ow==", created_at: "2018-01-13 07:15:50">, #<ActiveStorage::Blob id: 5, key: "o4tVQs7qxkSzn8VvzBE4h7GD", filename: "favicon-32x32.png", content_type: "image/png", metadata: {"analyzed"=>true}, byte_size: 4341, checksum: "cSRCQZDZGQ7muZTKtIcqow==", created_at: "2018-01-13 07:23:48">]>


データの更新

  • has_manyの場合新しいファイルを追加すると、単にaddされる
  • has_oneの場合新しいファイルを追加するとremoveされてaddされる
# has_one, has_manyがある状態
sqlite> select * from active_storage_attachments;
2|avatar|User|1|2|2018-01-13 06:49:12.943961
3|documents|User|1|3|2018-01-13 07:15:50.185869
4|documents|User|1|4|2018-01-13 07:15:50.351825

# has_manyに一つ追加
sqlite> select * from active_storage_attachments;
2|avatar|User|1|2|2018-01-13 06:49:12.943961
3|documents|User|1|3|2018-01-13 07:15:50.185869
4|documents|User|1|4|2018-01-13 07:15:50.351825
5|documents|User|1|5|2018-01-13 07:23:48.446867

# has_oneを入れ替える
sqlite> select * from active_storage_attachments;
3|documents|User|1|3|2018-01-13 07:15:50.185869
4|documents|User|1|4|2018-01-13 07:15:50.351825
5|documents|User|1|5|2018-01-13 07:23:48.446867
6|avatar|User|1|6|2018-01-13 07:24:39.176002

# blobの状態
sqlite> select * from active_storage_blobs;
3|JmoQ7kirgRS4KEufb3Ka9Dhj|IMG_0398.JPG|image/jpeg|{"analyzed":true}|1172434|zGwD8g/ZLOz18yZKK++gjQ==|2018-01-13 07:15:49.869750
4|7NPN1h3ay3vY8j98wjScSfV7|IMG_0484.JPG|image/jpeg|{"analyzed":true}|1491969|NwVtqcGENtT7ME96zNg2Ow==|2018-01-13 07:15:50.265944
5|o4tVQs7qxkSzn8VvzBE4h7GD|favicon-32x32.png|image/png|{"analyzed":true}|4341|cSRCQZDZGQ7muZTKtIcqow==|2018-01-13 07:23:48.420600
6|fhsSYkwpJMnn2cuG1BbeFGaa|test.png|image/png|{"analyzed":true}|45163|k7ubSzzlQFePZ51drkVsQw==|2018-01-13 07:24:39.173942

http://edgeguides.rubyonrails.org/active_storage_overview.html

direct upload

アップロード時のログ

Started POST "/rails/active_storage/direct_uploads" for 127.0.0.1 at 2018-01-14 15:33:43 +0900
Processing by ActiveStorage::DirectUploadsController#create as JSON
  Parameters: {"blob"=>{"filename"=>"スクリーンショット 2017-12-25 15.06.56.png", "content_type"=>"image/png", "byte_size"=>95238, "checksum"=>"2HWB5w4YTML9Z4Q6/p31iw=="}, "direct_upload"=>{"blob"=>{"filename"=>"スクリーンショット 2017-12-25 15.06.56.png", "content_type"=>"image/png", "byte_size"=>95238, "checksum"=>"2HWB5w4YTML9Z4Q6/p31iw=="}}}
   (0.1ms)  begin transaction
  ActiveStorage::Blob Create (0.5ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?)  [["key", "wcJFbBPMUAsBJGCti4tBbpyM"], ["filename", "スクリーンシ ョット 2017-12-25 15.06.56.png"], ["content_type", "image/png"], ["byte_size", 95238], ["checksum", "2HWB5w4YTML9Z4Q6/p31iw=="], ["created_at", "2018-01-14 06:33:43.050765"]]
   (0.8ms)  commit transaction
  Disk Storage (0.4ms) Generated URL for file at key: wcJFbBPMUAsBJGCti4tBbpyM (/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWRkMk5LUm1KQ1VFMVZRWE5DU2tkRGRHazBkRUppY0hsTkJqb0dSVlE2RVdOdmJuUmxiblJmZEhsd1pVa2lEbWx0WVdkbEwzQnVad1k3QmxRNkUyTnZiblJsYm5SZmJHVnVaM1JvYVFNR2RBRTZEV05vWldOcmMzVnRTU0lkTWtoWFFqVjNORmxVVFV3NVdqUlJOaTl3TXpGcGR6MDlCanNHVkE9PSIsImV4cCI6IjIwMTgtMDEtMTRUMDY6Mzg6NDMuMDU0WiIsInB1ciI6ImJsb2JfdG9rZW4ifX0=--165215f53af77b79cd2cb10034290a11e8fa4ab2)
Completed 200 OK in 16ms (Views: 0.5ms | ActiveRecord: 1.9ms)


Started PUT "/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWRkMk5LUm1KQ1VFMVZRWE5DU2tkRGRHazBkRUppY0hsTkJqb0dSVlE2RVdOdmJuUmxiblJmZEhsd1pVa2lEbWx0WVdkbEwzQnVad1k3QmxRNkUyTnZiblJsYm5SZmJHVnVaM1JvYVFNR2RBRTZEV05vWldOcmMzVnRTU0lkTWtoWFFqVjNORmxVVFV3NVdqUlJOaTl3TXpGcGR6MDlCanNHVkE9PSIsImV4cCI6IjIwMTgtMDEtMTRUMDY6Mzg6NDMuMDU0WiIsInB1ciI6ImJsb2JfdG9rZW4ifX0=--165215f53af77b79cd2cb10034290a11e8fa4ab2" for 127.0.0.1 at 2018-01-14 15:33:43 +0900
Processing by ActiveStorage::DiskController#update as */*
  Parameters: {"encoded_token"=>"eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWRkMk5LUm1KQ1VFMVZRWE5DU2tkRGRHazBkRUppY0hsTkJqb0dSVlE2RVdOdmJuUmxiblJmZEhsd1pVa2lEbWx0WVdkbEwzQnVad1k3QmxRNkUyTnZiblJsYm5SZmJHVnVaM1JvYVFNR2RBRTZEV05vWldOcmMzVnRTU0lkTWtoWFFqVjNORmxVVFV3NVdqUlJOaTl3TXpGcGR6MDlCanNHVkE9PSIsImV4cCI6IjIwMTgtMDEtMTRUMDY6Mzg6NDMuMDU0WiIsInB1ciI6ImJsb2JfdG9rZW4ifX0=--165215f53af77b79cd2cb10034290a11e8fa4ab2"}
  Disk Storage (2.9ms) Uploaded file to key: wcJFbBPMUAsBJGCti4tBbpyM (checksum: 2HWB5w4YTML9Z4Q6/p31iw==)
No template found for ActiveStorage::DiskController#update, rendering head :no_content
Completed 204 No Content in 77ms (ActiveRecord: 0.0ms)


Started PATCH "/users/1" for 127.0.0.1 at 2018-01-14 15:33:43 +0900
Processing by UsersController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"GIRZTP6iAhLNcdsv6wYQvHg+T4heWRYDdj6Q3mNQHPZvNaSqz8RYhM0tRHwNqdYvfWoS2rjSbmT8obaCxaBglg==", "user"=>{"name"=>"test", "direct_uploaded_files"=>["eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBEZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a98b1bd46e7c9e334c0989d766cbf744b35c4aa3"]}, "commit"=>"Update User", "id"=>"1"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  ActiveStorage::Blob Load (0.3ms)  SELECT  "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 9], ["LIMIT", 1]]
  ActiveStorage::Attachment Create (0.6ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?)  [["name", "direct_uploaded_files"], ["record_type", "User"], ["record_id", 1], ["blob_id", 9], ["created_at", "2018-01-14 06:33:43.253908"]]
  User Update All (0.5ms)  UPDATE "users" SET "updated_at" = '2018-01-14 06:33:43.255691' WHERE "users"."id" = ?  [["id", 1]]
   (1.3ms)  commit transaction
  ActiveStorage::Blob Load (0.2ms)  SELECT  "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 9], ["LIMIT", 1]]
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: b95c7246-eeae-403c-b348-2742ab47406d) to Async(default) with arguments: #<GlobalID:0x00007fc0e243a918 @uri=#<URI::GID gid://as-sample/ActiveStorage::Blob/9>>
[ActiveJob] [ActiveStorage::AnalyzeJob] [b95c7246-eeae-403c-b348-2742ab47406d] Performing ActiveStorage::AnalyzeJob (Job ID: b95c7246-eeae-403c-b348-2742ab47406d) from Async(default) with arguments: #<GlobalID:0x00007fc0e79b3c50 @uri=#<URI::GID gid://as-sample/ActiveStorage::Blob/9>>
Redirected to http://lvh.me:3000/users/1
Completed 302 Found in 50ms (ActiveRecord: 3.9ms)


[ActiveJob] [ActiveStorage::AnalyzeJob] [b95c7246-eeae-403c-b348-2742ab47406d] Skipping image analysis because the mini_magick gem isn't installed
[ActiveJob] [ActiveStorage::AnalyzeJob] [b95c7246-eeae-403c-b348-2742ab47406d]    (0.1ms)  begin transaction
[ActiveJob] [ActiveStorage::AnalyzeJob] [b95c7246-eeae-403c-b348-2742ab47406d]   ActiveStorage::Blob Update (1.1ms)  UPDATE "active_storage_blobs" SET "metadata" = ? WHERE "active_storage_blobs"."id" = ?  [["metadata", "{\"analyzed\":true}"], ["id", 9]]
[ActiveJob] [ActiveStorage::AnalyzeJob] [b95c7246-eeae-403c-b348-2742ab47406d]    (4.3ms)  commit transaction
[ActiveJob] [ActiveStorage::AnalyzeJob] [b95c7246-eeae-403c-b348-2742ab47406d] Performed ActiveStorage::AnalyzeJob (Job ID: b95c7246-eeae-403c-b348-2742ab47406d) from Async(default) in 11.34ms


成功したイベントと発動順

addEventListener('turbolinks:load', () => {
  addEventListener('direct-upload:initialize', (event) => {
    console.log('direct-upload:initialize')
    const { target, detail } = event;
    console.log(target); // input
    console.log(detail); // {file: File(87589), id: 1}
  });

  addEventListener('direct-uploads:start', (event) => {
    console.log('direct-uploads:start')
    const { target, detail } = event;
    console.log(target); // form
    console.log(detail); // {}
  });

  addEventListener('direct-upload:start', (event) => {
    console.log('direct-upload:start')
    const { target, detail } = event;
    console.log(target); // input
    console.log(detail); // {file: File(665507), id: 1}
  });

  addEventListener('direct-upload:before-blob-request', (event) => {
    console.log('direct-upload:before-blob-request')
    const { target, detail } = event;
    console.log(target); // input
    console.log(detail); // {xhr: XMLHttpRequest, file: File(665507), id: 1}
  });

  addEventListener('direct-upload:before-storage-request', (event) => {
    console.log('direct-upload:before-storage-request')
    const { target, detail } = event;
    console.log(target); // input
    console.log(detail); // {xhr: XMLHttpRequest, file: File(665507), id: 1}
  });

  // VM33819:1 XHR finished loading: POST "http://lvh.me:3000/rails/active_storage/direct_uploads".

  addEventListener('direct-upload:progress', (event) => {
    console.log('direct-upload:progress')
    const { target, detail } = event;
    console.log(target); // input
    console.log(detail); // {progress: 100, file: File(665507), id: 1}
  });

  // addEventListener('direct-upload:error', (event) => {
  //   console.log('direct-upload:error')
  //   const { target, detail } = event;
  //   console.log(target);
  //   console.log(detail);
  // });

  addEventListener('direct-upload:end', (event) => {
    console.log('direct-upload:end')
    const { target, detail } = event;
    console.log(target); // input#user_direct_uploaded_files
    console.log(detail); // {file: File(665507), id: 1}
  });

  addEventListener('direct-uploads:end', (event) => {
    console.log('direct-uploads:end')
    const { target, detail } = event;
    console.log(target); // form
    console.log(detail); // {}
  });

  // VM33819:1 XHR finished loading: PUT "http://lvh.me:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWRUV0l5VkdwV1oyTmtSV3RPV0doNWMyTk1jazFoWkhSTkJqb0dSVlE2RVdOdmJuUmxiblJmZEhsd1pVa2lEbWx0WVdkbEwzQnVad1k3QmxRNkUyTnZiblJsYm5SZmJHVnVaM1JvYVFPakp3bzZEV05vWldOcmMzVnRTU0lkVkhoR1FsSktSMmRPZUhaQ1oySjJWVGRIUWxWWlVUMDlCanNHVkE9PSIsImV4cCI6IjIwMTgtMDEtMTRUMDc6MTI6NDIuODIyWiIsInB1ciI6ImJsb2JfdG9rZW4ifX0=--11181d80fb2e48eb9367f331bc8ee75d559d4b65".
});