![]()
一個困擾Rails開發者多年的問題,直到最近才被完整解開。當你刷信息流時,系統怎么知道哪條帖子你已經點過贊?傳統做法要查兩次數據庫,用戶一多直接卡死。
這個問題叫「用戶關聯預加載」——既要查出所有帖子,又要標記出當前用戶點贊過的那些。表面看簡單,實際藏著Ruby on Rails框架里最隱蔽的性能陷阱之一。
一位開發者在技術社區寫下這句話:「我找遍全網,沒找到真正直接的答案。」
他的方案最終只用了幾行代碼,卻讓查詢效率提升數十倍。更關鍵的是,這招能復用到幾乎所有「判斷用戶是否已操作」的場景——好友關系、收藏狀態、閱讀記錄,全部通用。
CurrentAttributes:被低估的全局通行證
Rails 5.2引入了一個鮮為人知的模塊:ActiveSupport::CurrentAttributes。它像一張臨時通行證,能在單次請求周期內,把數據貼到任何需要的地方。
開發者創建了一個Current類,只存一個user_id。為什么不存整個用戶對象?他的解釋很克制:「Current Attributes不應該被濫用。」
設置方式取決于你的認證系統。用Devise的話,在ApplicationController里加一行before_action,每次請求前把當前用戶ID塞進去。代碼不到10行,卻打通了后續所有操作的關鍵鏈路。
這里有個細節容易踩坑。CurrentAttributes的生命周期嚴格綁定單次請求,線程安全由框架保證,但跨請求絕對隔離。這意味著你不用擔心用戶A的數據漏給用戶B——除非你自己在代碼里開了后門。
「我們只需要ID,所以保持簡單。」這位開發者在注釋里寫道。
關聯定義的精妙陷阱
真正的魔法發生在Post模型里。開發者定義了一個叫liked的關聯,寫法讓新手困惑:
has_many :liked, -> { where(id: Current.user_id) }, through: :likes, source: :user
拆解一下。through: :likes表示借道likes表,source: :user說明最終要拿的是用戶數據。但中間那個lambda才是核心——它動態注入當前用戶ID作為過濾條件。
結果很反直覺:liked關聯永遠不會返回nil,要么空數組,要么含一個元素。這正好配合Rails的any?方法,在視圖里寫判斷時干凈利落。
對比傳統方案。以前的做法是先查所有帖子,再循環查每條帖子的點贊狀態——經典的N+1查詢,100條帖子就是101次數據庫往返。現在eager_load(:liked)一次性搞定,SQL層面做了優化連接。
一位評論者算過賬:「從101次查詢降到2次,響應時間從800毫秒壓到40毫秒。」
為什么這招藏了這么久
CurrentAttributes的官方文檔 buried 在Rails指南的角落,例子全是關于時區和多租戶。把它和關聯預加載結合起來用,屬于典型的「框架能力組合創新」——單個功能都文檔齊全,拼在一起卻沒人寫過。
更深層的原因是思維定式。大多數開發者遇到「判斷是否點贊」的需求,第一反應是寫實例方法或裝飾器模式。他們沒意識到,這個問題本質是數據關聯問題,該用關聯層解決。
開發者在文章里埋了個彩蛋:這個技巧不止用于點贊。判斷好友關系?把Current.user_id扔進User模型的關聯條件。檢查收藏狀態?同樣的模式套到Bookmark模型。甚至閱讀進度、投票記錄、關注狀態,全部可以復用。
唯一的前提是:你的認證系統能在請求開始時把用戶ID塞進Current。
「它可以用在其他方式,不只是這一種。」作者特意強調。
社區反饋驗證了這個判斷。文章發布兩周內,被引用到Stack Overflow的7個相關問答里,有人用它解決了GraphQL的N+1問題,有人套用到StimulusReflex的實時更新場景。
一位團隊技術負責人在評論區寫道:「我們把這個模式抽成concern,現在全平臺的「用戶是否已X」判斷都統一了。」
當然也有爭議。部分開發者擔心CurrentAttributes的「全局變量」屬性會隱藏依賴關系,讓測試變復雜。作者回應得很直接:「只在請求周期內有效,測試時重置就行。」
Rails核心團隊成員在GitHub issue里提過,CurrentAttributes的設計初衷就是解決這種「請求上下文傳遞」場景,而非替代參數顯式傳遞。用對地方,它是利器;濫用,才是問題。
回到最初的那個場景。當你下次刷信息流,看到心形圖標自動點亮時,背后可能正跑著這段代碼。它不發通知、不彈窗,只是安靜地省下了幾百毫秒——以及服務器上本該瘋狂轉動的風扇。
那位開發者最后更新了一次文章,加了行備注:「如果Current.user_id為nil,關聯會返回空數組,不會報錯。但建議控制器里做好認證檢查。」
你的項目里,有多少處「判斷用戶是否已操作」還在用循環查詢?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.