Leon's Blogging

Coding blogging for hackers.

Rails With Active Storage

| Comments

Active Storage 是 rails 後來出的新功能,可以簡單的做到上傳檔案,雲端整合(Amazon S3, Google Cloud Storage),MiniMagick 影像操作等等,就像是 carrierwave, paperclip 等比較常用到的套件

跟以往用 carrierwave 或是 paperclip 不太一樣,不需要在現有的 model 新增欄位,而是透過 polymorphic 的方式,將檔案存在兩張獨立的 table, active_storage_blobs, active_storage_attachments

Init Project

1
rails new active_storage_demo --webpack=stimulus --skip-coffee --skip-test -d=mysql

Install active_storage

透過 rails active_storage:install 建立 active_storage 所需要的兩張 table,包括 active_storage_blobs, active_storage_attachments

  • active_storage_attachments: 用來存跟 model 相關的資訊
  • active_storage_blobs: 用來存詳細檔案的資訊(裡面包含了 checksum 將檔案做 hash 可以知道是否為同一個檔案)

接著跑 rake db:create db:migrate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs 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 ], unique: true
    end

    create_table :active_storage_attachments do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false
      t.references :blob,     null: false

      t.datetime :created_at, null: false

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
    end
  end
end

預設檔案存在 local(也可以改成 s3 或 GCS)

1
2
3
4
5
6
7
8
9
10
11
# config/environments/development.rb
config.active_storage.service = :local

# config/storage.yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

建立 user moblie

1
2
rails g scaffold User name
rake db:migrate

在 user model 設定可以上傳的檔案有哪些

1
2
3
4
5
# models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :documents
end

在 view 上面新增畫面,如果是要多個檔案,要加上 multiple: true

1
2
3
4
5
6
7
8
9
10
11
# views/users/_form.html.erb

<div class="field">
  <%= form.label :avatar %>
  <%= form.file_field :avatar %>
</div>

<div class="field">
  <%= form.label :documents %>
  <%= form.file_field :documents, multiple: true %>
</div>
  • image_tag(@user.avatar) 顯示圖片
  • rails_blob_path(doc, disposition: "attachment")) 附件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# views/users/show.html.erb
<h2>avatar</h2>
<% if @user.avatar.attached? %>
  <%= image_tag(@user.avatar) %>
<% end %>

</p>

<% if @user.documents.attached? %>
  <p>
  <h2>Download Documents</h2>
  <% @user.documents.each_with_index do |doc, i| %>
    <p>
    <%= link_to("Document #{i}", rails_blob_path(doc, disposition: "attachment")) %>
    </p>
  <% end %>
  </p>
<% end %>

並在 controller 新增 permit,因為 documents 是多個檔案,所以給 Array

1
2
3
4
# controllers/users_controller.rb
def user_params
  params.require(:user).permit(:name, :avatar, documents: [])
end

接著就可以 rails s 來新增 user 測試看看了

刪除檔案

1
2
3
4
5
# 同步刪除頭像和實際資源檔案。
@user.avatar.purge

# 透過 Active Job 非同步刪除相關模型和實際資源檔案。
@user.avatar.purge_later

調整圖片尺寸

不同尺寸可以透過 minimagick 做轉換

1
2
3
4
5
# Gemfile
gem 'mini_magick'

# views/users/show.html.erb
<%= image_tag @user.avatar.variant(resize: "100x100") %>

Direct Upload

1
yarn add activestorage
1
2
3
4
# javascript/packs/direct_upload.js

import * as ActiveStorage from 'activestorage';
ActiveStorage.start();

other

1
2
3
4
5
6
7
# url
url_for(@user.avatar)

# download link
rails_blob_path(user.avatar, disposition: "attachment")

Rails.application.routes.url_helpers.rails_blob_url(@user.avatar, only_path: true)

後續

接著看一下 source code,可以發現到 blob 可以有多個 attachments。

代表 blob 是可以重複利用,但是必須在 has_one/many_attached 後面加上 dependent: false 才不會刪除了一個 attachment 就將 blob 刪除,導致其他 attachment 關聯不到

另外 delegate_missing_to 則是類似 delegate 的加強版,讓 user 可以直接 call user.filename

Rails 5.1 add delegate_missing_to

belongs_to :record 則是透過 polymorphic 的方式來存取是由哪個 model 的資料

1
2
3
4
5
6
7
# rails/activestorage/app/models/active_storage/blob.rb
class ActiveStorage::Blob < ActiveRecord::Base
  # ...
  self.table_name = "active_storage_blobs"
  has_many :attachments
  # ...
 end
1
2
3
4
5
6
7
# rails/activestorage/app/models/active_storage/attachment.rb
class ActiveStorage::Attachment < ActiveRecord::Base
  self.table_name = "active_storage_attachments"
  belongs_to :record, polymorphic: true, touch: true
  belongs_to :blob, class_name: "ActiveStorage::Blob"
  delegate_missing_to :blob
end

缺點

demo

active_storage_demo

Reference:

Comments