惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
V
V2EX
博客园 - 叶小钗
雷峰网
雷峰网
小众软件
小众软件
量子位
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
Martin Fowler
Martin Fowler
G
Google Developers Blog
博客园_首页
博客园 - Franky
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享

Nic Lin's Blog

謝明真 - 高效領導力的課後筆記 NFT 開發實戰!基礎智能合約入門 (3) NFT 開發實戰!基礎智能合約入門 (2) NFT 開發實戰!基礎智能合約入門 (1) 如何自我檢測 log4j CVE 漏洞 Rails 如何在資料寫入時記錄來源 IP 位置 如何經營工程師 Youtube 頻道 - Part 8 營收篇 如何經營工程師 Youtube 頻道 - Part 7 酸民文化篇 如何經營工程師 Youtube 頻道 - Part 5 設備器材篇 如何經營工程師 Youtube 頻道 - Part 4 後製剪輯篇 如何經營工程師 Youtube 頻道 - Part 3 文案企劃篇 如何經營工程師 Youtube 頻道 - Part 2 設備器材篇 如何經營工程師 Youtube 頻道 - Part 1 制訂頻道方向篇 如何經營工程師 Youtube 頻道 - Part 0 Rails 中避免 race condition 的最佳實踐(二) Rails 中避免 race condition 的最佳實踐(一) 10 分鐘整合 google sheet 做自動化開發功能週報 經營 Side Project 300 天所帶來的收穫及挑戰 我的 Youtube 影片製作流程 API 設計時必須注意的 HTTP header 底線問題 如何提升你的程式可讀性之實務技巧(三) 如何提升你的程式可讀性之實務技巧(二) 如何提升你的程式可讀性之實務技巧(一) Ruby 中使用 freeze 優化效能的時機 避免 React 中的 useEffect 無限 render 在 Rails 內輕量使用 Vue Component 的最佳實踐 如何在區域網路用 Docker 架設有 SSL 的 Gitlab 從被問到問人,那些我常問的面試問題 [Rails] 如何漂亮寫出可維護的 query (Maintainable Rails Query) 在已知長度情況下優化 slice 的性能
[Rails] 如何讓 AASM 的 event 事件個別 validate 且有 callback
Nic Lin · 2018-06-09 · via Nic Lin's Blog

最近遇到了這麼個需求,需要針對狀態機內的 event 有的部分要 valiadte,有的部分則不需要,但是所有的 event 卻都要有 ActiveRecord 的 callback

需求釐清

  • 商品上架需要通過所有的 validate, 這些是商業邏輯的規則
  • 商品下架不需要通過,因為可能有市場價格浮動,導致 validate 有 fail 的可能
  • 每一個 event 都要有 PaperTrail 的紀錄

第一步:Skip 所有 validate + callback

原先我以為 AASM 可以直接做到 skip 指定 event 的 validate,但文件東翻西找,卻只有

skip_validation_on_save

If you want make sure the state gets saved without running validations (and thereby maybe persisting an invalid object state), simply tell AASM to skip the validations. Be aware that when skipping validations, only the state column will be updated in the database (just like ActiveRecord update_column is working).

這個如果設定為 true, 等同於將所有 event!(加驚嘆號)變成 update_column, 也就是單純更新欄位,不執行任何 callback

也就是說,你只能選擇全要或是全不要。

aasm skip_validation_on_save: true do
  event :go_live do
    transitions from: :offline, to: :online
  end
  event :go_offline do
    transitions from: :online, to: :offline
  end
end

第二步:針對個別 Validate

其實 ActiveRecord 中可以直接使用 valid? 這個 method 去執行 validate

所以我們可以讓商品上架的這個行為,單獨使用 guard 去檢查 valid?

event :go_live do
  transitions from: :offline, to: :online, guard: [:valid?]
end

這樣子就可以做到, go_live 會被 validate 而 go_offline 不會。

第三步:讓所有 event 都有 callback

我爬了 AASM 的文件,發現有一個 method 可以在所有 event 執行後執行,也就是 after_commit 的概念

after_all_events

不過在官方文件中沒有詳細說明這個 method, 自己試了之後發現,確實是會在所有 event 執行後進行 commit

那我們就可以設計一個執行 update callback 來讓 PaperTrail 產生記錄

aasm skip_validation_on_save: true do
  after_all_events :trigger_update_callback
  
  event :go_live do
    transitions from: :offline, to: :online
  end
  event :go_offline do
    transitions from: :online, to: :offline
  end
end

private

def trigger_update_callback
  touch(:updated_at) && run_callbacks(:update) if persisted?
end

這邊解釋一下 touch(:updated_at),是因為我發現 PaperTrail 在生成 Log 時的 created_at 並不是真正的數據建立時間,而是拿 object 的 updated_at 時間,我可以在 PaperTrail 中的 record_trail.rb 看見

所以這裡順序很重要,一定是先執行 touch(:updated_at),再執行 run_callbacks(:update) 這樣 PaperTrail 裡面的時間才會是對的。

而後面的 .persisted? 也是非加不可,如果遇到數據是新建立的,沒有這個判斷就會噴

ActiveRecord::RecordInvalid: Validation failed: Children parent must exist

結論

這個問題我花了好長一段時間才想到解法的,可以兼顧可讀性、又能夠完美達成刁鑽的需求,應該此生難忘了XD