attache 以及 Ruby 檔案上傳

Juanito FatasThinking about what to do.

Ruby 現有的檔案上傳解決方案:

這些 RubyGem 把檔案上傳放在 Rails App 裡面處理,這些 RubyGem 提供了漂亮的語法,但不管是拆成 Uploader 還是在 Model 裡面使用 DSL,商業邏輯和檔案上傳邏輯總還是會混在一起。自己要處理好上傳檔案、檔案儲存、處理檔案等繁瑣的事物。Rails 也要耗費記憶體與時間來做這些事情。

不過有一點要提的是,這些 RubyGems 都是成熟穩定的解決方案,歷經考驗,使用者眾多,支援各大 Rails 版本,Bug 少,程式碼品質好,可定制性強。

Rails 已經有將工作抽出去處理的豐富經驗,對,就是 Microservices。那為什麼檔案上傳不可以?將檔案上傳拆分出去,改成 Server-Client 的架構:

|                 |         |             |         |             |
|      Rails      | ------- |   attache   | ------- |   attache   |
|   Application   | ------- |    client   | ------- |    server   |
|                 |         |             |         |             |
  Your Application      choonkeat/attache_rails     choonkeat/attache
                                                    standalone server

僅需要 Rack + ImageMagick,5 分鐘馬上部署一個檔案處理 Server 到 Heroku 或者是 Digital Ocean

在最近這篇 The Stack That Helped Medium Drive 2.6 Millennia of Reading Time 文章裡有相似的例子:

我們用 Go 重寫了圖片伺服器,使用了瀑布流的策略來處理圖片。
伺服器採用了 groupcache,groupcache 是一種 memcache 的替代方案,
可以減少重複的圖片處理工作。緩存雖存在記憶體裡,背後仍有 S3 做支援。
圖片在有需要處理的時候才會進行處理。這使得我們的設計師可以有彈性來修改,
並優化圖片的呈現方式,而無需重新調整所有圖片的大小。

attache 另一項創新之舉是實作了 Tus 協定,一個針對在不穩定網路進行檔案續傳的開放協定,attache 用一個 Middleware 實現了這個協定。

這份簡報有更多內容,就不在此一一重複。

Demo 見此:https://attache-demo.herokuapp.com

attache_rails 使用了: React、jQuery,目前支持 Bootstrap 3。

Gemfile 加入:

gem "attache_rails"

接著 bundle 安裝即可。

application.js 把 JavaScript Client 引入進來:

//= require attache

使用 attache 所上傳的檔案,在資料庫會以 JSON 格式儲存,要儲存 JSON 格式,加一個 photo 欄位,指定 type 為 text 即可。

使用者上傳單張照片:

rails generate migration AddPhotoPathToUsers photo:text

上傳多張照片也可以使用同一個欄位 photos

rails generate migration AddPhotoPathToUsers photos:text

PostgreSQL 可以使用 jsonjsonb

rails generate migration AddPhotoPathToUsers photo:json

PostgreSQL 還可以做這樣強大的查詢:

User.where("photo ->> 'content_type' = ?", "image/png")

單一上傳:

class User < ActiveRecord::Base
  has_one_attache :photos
end

多檔上傳:

class User < ActiveRecord::Base
  has_many_attaches :photos
end

= f.file_field :photos, f.object.avatar_options("64x64#")

更多諸如 64x64# 的格式請參考:http://www.imagemagick.org/Usage/resize

單張照片

def user_params
  params.require(:user).permit(:name, :photo, attaches_discarded: [])
end

多張照片

def user_params
  params.require(:user).permit(:name, photos: [], attaches_discarded: [])
end

attaches_discarded: [] 是什麼?

已經不要或是刪除的檔案(譬如刪除使用者時),檔案會自動從 attache 伺服器上刪除。只需要加入到 Strong Parameter 即可,無需做任何處理,attache_rails 已經都整合好了。

單張照片

= image_tag @user.photo_url("100x100#")

多張照片

- @user.photos_urls("200x200#").each do |url|
  = image_tag url

環境變數 用途
ATTACHE_URL 指定 attache server 所在位置,預設是 http://localhost:9292
ATTACHE_UPLOAD_DURATION 多久時間將上傳視為過期,預設是 600 秒
ATTACHE_SECRET_KEY 用來加密從 Rails 到 attache server 的請求
  • 若沒有設定 ATTACHE_SECRET_KEY,則客戶端與伺服器端的請求不會加密,也無法設定 ATTACHE_UPLOAD_DURATION
  • 若有設定 ATTACHE_SECRET_KEY 了,其值必須要和 attache server 的 SECRET_KEY 相同

Demo 見此:https://github.com/choonkeat/attache-railsapp