Rails ActiveRecord的default_scope的坑
其實,我是特別反對使用default_scope
的。它很強大,同時也非常的難於駕馭。它的優點在於我們只需要在Model
層增加一行代碼,就能解決整個項目中的如何一個地方數據的獲取。
舉個例子,我們有一個項目的Model,前期比較簡單,我們在代碼的如何地方都可以方便的使用Project.find
或者 Project.where...
。然後,我們來了一個新的需求,需要給Project
增加一個開關,只有通過審覈的項目才能在項目中顯示。這個時候,我們首先想到的方案就是使用 default_scope
。 So easy! 我們只需要在Model中增加一行代碼 default_scope { where("checked is not null") }
。 表面上,或者沒有經過全面的測試,這行代碼沒有問題。而且非常方便的解決了我們的問題。但其中隱藏很多問題。
問題
會在系統的其他引用Project
的地方報 nil
異常
比如我們有一個User
模型, 它跟 Project
的關係是 belongs_to
的關係。所以,下面的代碼
@users.each do |user|
user.project.name
end
是比較常見的case。 但由於project
的default_scope
的原因,導致 project
爲nil
, 這個時候,就會報錯~
解決辦法:每一個引用project
的地方,都做nil
判斷。 這個解決方案不好,特別麻煩。一種比較好的方案是引入 gem 'unscoped_associations'
.
組合而成的SQL
會有問題~
這個問題比較隱晦,很難發現。我先用一個例子說明,接着上面說的。
1. 有一個新的模型 Investor
,它跟Project
一樣,有同樣的default_scope
的條件,也就是說在 investors
表 和 projects
表,都有一個checked
字段。
2. 約會的模型 Interview
會關聯上 Project
和 Investor
現在有一個需求,客戶需要獲取所有的Interview
。我們一般會寫如下的代碼
Investor.includes(:project, :investor).all
上面這行代碼會報錯!原因是框架在組合SQL的時候,會有兩個checked
字段的條件,如果我們使用default_scope { where("checked is not null") }
這種方式,框架是不能設置 projects.checked
這樣的條件(如果使用Hash方式,是可以智能的組合的,所以,儘量要使用hash).
解決辦法:通過arel 底層組合SQL
default_scope { where(arel_table[:checked].not_eq(nil)) }
總結
default_scope
給我們解決的問題的同時,會引入更多的問題。甚至需要修改底層代碼才能解決。所以,在使用default_scope
的時候,一定要清楚自己做什麼。