Rails 圖片上傳至 S3套件 paperclip

tingyiiii
tingyiiii
Published in
9 min readJun 27, 2021

在 Rails 使用 paperclip gem 來上傳圖片至 S3

GitHub: https://github.com/thoughtbot/paperclip

Step 1 安裝

  • 首先必須先在電腦中安裝 ImageMagick 來當作 paperclip 的 Image Processor,

使用 homebrew 來安裝,在終端機下指令

$ brew install imagemagick

Step 2 使用

  • 接下來就在需要加入圖片的 table 中加入欄位:

這裡可以直接使用指令

$ rails generate paperclip [your_table_name] [your_column_name]

會產生以下範例的 migration 檔 (也可以自己手動新增)

class Xxxxxx < ActiveRecord::Migration
def change
add_attachment :your_table_name, :your_column_name
end
end
  • migration 檔確認無誤後記得 $ bundle exec rake db:migrate

會產生以下 4 個欄位:

  1. <column>_file_name

2. <column>_file_size

3. <column>_content_type

4. <column>_updated_at

  • 欄位新增好後記得在 Model 中加入 has_attached_file :your_column_name,即大功告成!

範例:

class User < ActiveRecord::Base
has_attached_file :avatar
end

設定 paperclip

  • 在 Model 中加入的 has_attached_file :your_column_name 後面加上針對單一檔案的設定`

e.g. 圖片儲存的樣式名稱及尺寸、url、path …等

> styles: { medium: “300x300>”, thumb: “100x100>” } → 為這圖片另儲存兩種尺寸

> default_url → 設定取得圖片的 url 網址

  • 直接設定 paperclip 全部套用的統一設定,例如設定檔案上傳至 S3 或存在本地端
  1. 在 development.rb /production.rb 中分別設定需要的設定

e.g. 官方建議串 S3 的設定:

/config/environments/development.rb  config.paperclip_defaults = {
:storage => :s3,
:s3_host_name => 'REMOVE_THIS_LINE_IF_UNNECESSARY',
:s3_credentials => {
:access_key_id => AWS_ACCESS_KEY_ID,
:secret_access_key => AWS_SECRET_ACCESS_KEY,
:s3_region => "YOUR_S3_REGION_HERE"
},
:bucket => 'S3_BUCKET_NAME'
}
/config/environments/production.rb config.paperclip_defaults = {
:storage => :s3,
:preserve_files => true,
:s3_host_name => 'REMOVE_THIS_LINE_IF_UNNECESSARY',
:s3_credentials => {
:access_key_id => AWS_ACCESS_KEY_ID,
:secret_access_key => AWS_SECRET_ACCESS_KEY,
:s3_region => "YOUR_S3_REGION_HERE"
},
:bucket => 'S3_BUCKET_NAME'
}

2. 新增 config/initializers/paperclip.rb 檔案,並在此設定統一的設定

e.g. 若上面 development 和 production 是一樣的就可以騰到此

/config/environments/production.rb  config.paperclip_defaults = {
:storage => :s3,
:preserve_files => true,
:s3_host_name => 'REMOVE_THIS_LINE_IF_UNNECESSARY',
:s3_credentials => {
:access_key_id => AWS_ACCESS_KEY_ID,
:secret_access_key => AWS_SECRET_ACCESS_KEY,
:s3_region => "YOUR_S3_REGION_HERE"
},
:bucket => 'S3_BUCKET_NAME'
}

驗證 validation

paperclip 有提供 validations 可以驗證 attachment 欄位

並有提供三種寫法:

  1. use validator
  • AttachmentContentTypeValidator
  • AttachmentPresenceValidator
  • AttachmentSizeValidator
example:validates :avatar, attachment_presence: true
validates_with AttachmentPresenceValidator, attributes: :avatar
validates_with AttachmentSizeValidator, attributes: :avatar, less_than: 1.megabytes

2. with helper

  • validates_attachment_presence
  • validates_attachment_content_type
  • validates_attachment_size
example:validates_attachment_presence :avatar
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/

3. validates_attachment:可將多種 validations 定義為一行

example:validates_attachment :avatar, presence: true,
content_type: “image/jpeg”,
size: { in: 0..10.kilobytes }

上傳至 S3 (CloudFront) 設定

  1. 設定的檔案位置可參考上面 #設定 paperclip
  • :storage => :s3 :預設為 :filesystem 會存在本地端
  • :url => ':s3_alias_url' :搭配 `:s3_host_alias` 讓 paperclip 產生的 s3 url 可以使用我們設定的 cloudfront url
  • :s3_host_alias => 'xxx' :輸入我們設定 cloudfront 的 host name
  • :preserve_file => true:避免 S3 上的檔案在 Rails object 刪掉時一起被刪掉(官方建議)
  • :s3_credentials => { :access_key => xxx,:secret_access_key => xxx,:s3_region => xxx } :放 AWS 的敏感資料,記得不要直接寫在上面!可以使用 `figaro` 之類的 gem 另外保存
  • :bucket => 'xxx' :輸入 bucket 的名字
  • :s3_permissions => 'private':預設是 :public-read,但若 bucket 設定是 private,這個就記得一定要加!否則會出現以下錯誤訊息
Aws::S3::Errors::AccessDenied — Access Denied:
aws-sdk-core (3.69.1) lib/seahorse/client/plugins/raise_response_errors.rb:15:in call’ aws-sdk-s3 (1.50.0) lib/aws-sdk-s3/plugins/sse_cpk.rb:22:incall’
aws-sdk-s3 (1.50.0) lib/aws-sdk-s3/plugins/dualstack.rb:26:in call’ aws-sdk-s3 (1.50.0) lib/aws-sdk-s3/plugins/accelerate.rb:35:incall’
aws-sdk-core (3.69.1) lib/aws-sdk-core/plugins/jsonvalue_converter.rb:20:in call’ aws-sdk-core (3.69.1) lib/aws-sdk-core/plugins/idempotency_token.rb:17:incall’
aws-sdk-core (3.69.1) lib/aws-sdk-core/plugins/param_converter.rb:24:in call’ aws-sdk-core (3.69.1) lib/aws-sdk-core/plugins/response_paging.rb:10:incall’ …….

更多設定可參考 官方 Doc

其他設定

  • 重新命名檔案後上傳 S3

這裡使用到的是 paperclip 的 interpolates

  1. config/initializers/paperclip.rb 中加入
Paperclip.interpolates :encoded_file_name do |attachment, style|
attachment.instance.encoded_file_name
end

2. 在設定 model/xxx.rb 中加入 #encoded_file_name 方法

e.g. 使用 sha_256 編碼 id 後作為檔案名稱

def encoded_file_name
“#{SecretKey.aes_128_encode(self.id)}”
end

3. 接著就可以在設定 path 時使用 :encoded_file_name 取代原本預設的 :filename

app/models/xxx.rbhas_attached_file :image, { path: ‘/images/:encoded_file_name’ }

Reference:

--

--