命名作用域真的使這個問題更容易,但它還遠遠沒有解決。常見的情況是在命名範圍和模型方法中重新定義邏輯。在方法和作用域(和sql)中複製邏輯
我會嘗試通過使用一些複雜的例子來演示這種邊緣情況。假設我們有Message
模型,有很多Recipients
。每個收件人都可以將郵件標記爲正在閱讀。
如果你想獲得給定用戶未讀消息列表中,你會說這樣的事情:
Message.unread_for(user)
這將使用命名範圍unread_for
會產生將返回未讀郵件的sql對於給定的用戶。這個sql可能會將兩個表連接在一起,並由那些尚未讀取它們的收件人過濾郵件。
在另一方面,當我們使用在我們的代碼Message
模型中,我們使用的是以下幾點:
message.unread_by?(user)
這種方法在信息類中定義的,甚至是做基本上是同樣的事情,它現在有不同的實現。
對於更簡單的項目,這真的不是一件大事。在這種情況下在sql和ruby中實現相同的簡單邏輯不是問題。
但是,當應用程序開始變得非常複雜時,它開始成爲一個問題。如果我們已經實施了許可系統來檢查誰能夠根據數十個表中定義的幾十個標準來訪問哪些消息,那麼這開始變得非常複雜。不久之後,您需要加入5個表格並手動編寫非常複雜的SQL以定義範圍。
問題的唯一「乾淨」解決方案是使範圍使用實際的ruby代碼。他們會獲取所有消息,然後用ruby過濾它們。然而,這會導致兩個主要問題:
- 性能
- 分頁
性能:我們正在創造更大量數據庫查詢。我不確定DMBS的內部結構,但數據庫在單個表上執行5個查詢有多困難,或者一次要連接5個表的1個查詢?
分頁:我們希望保持提取記錄直到檢索到指定數量的記錄。我們一個接一個地檢查它們,並檢查它是否被ruby邏輯接受。一旦有10人被接受,流程就會停止。
好奇聽到你對此的想法。我沒有使用nosql dbms的經驗,他們能以不同的方式解決問題嗎?
UPDATE:
我只是說hypotetical,但這裏是一個活生生的例子。讓我們說,我們想顯示在一個頁面上的所有交易(付款和費用)。
我已經創建了SQL UNION QUERY來獲取它們,然後遍歷每條記錄,檢查它是否可以:被當前用戶讀取並最終以數組形式分頁。
def form_transaction_log
sql1 = @project.payments
.select("'Payment' AS record_type, id, created_at")
.where('expense_id IS NULL')
.to_sql
sql2 = @project.expenses
.select("'Expense' AS record_type, id, created_at")
.to_sql
result = ActiveRecord::Base.connection.execute %{
(#{sql1} UNION #{sql2})
ORDER BY created_at DESC
}
result = result.map do |record|
klass = Object.const_get record["record_type"]
klass.find record["id"]
end.select do |record|
can? :read, record
end
@transactions = Kaminari.paginate_array(result).page(params[:page]).per(7)
end
兩個payments
和expenses
需要進行相同的表中顯示,通過創建日期和分頁排序。
payments
和expenses
都有完全不同的:read
權限(在能力等級中定義,CanCan
寶石)。這些許可非常複雜,他們需要查詢其他幾個表。
「理想」的事情是編寫一個巨大的sql查詢,它會返回我所需要的。這將使分頁和其他一切變得更容易。但是這將複製我在ability.rb類中定義的邏輯。
我知道CanCan
提供了一種爲能力定義sql查詢的方法,但能力非常複雜,無法用這種方式定義。
我所做的是工作,但我正在加載所有事務,然後檢查哪些可以讀取。我認爲這是一個很大的性能問題。這裏的分頁似乎毫無意義,因爲我已經加載了所有記錄(它只能節省帶寬)。另一種方法是編寫將很難維護的非常複雜的SQL。
謝謝你的回答。我已經包含了一個真實生活的例子。 –