命名范围确实使这个问题变得更容易,但它远未解决。常见的情况是在命名范围和模型方法中重新定义逻辑。
我将尝试通过使用一些复杂的例子来演示这种情况的边缘情况。假设我们的Message
模型有很多Recipients
。每个收件人都能够将邮件标记为自己阅读。
如果您想获取给定用户的未读消息列表,您可以这样说:
Message.unread_for(user)
那将使用将生成sql的命名范围unread_for
,该sql将返回给定用户的未读消息。这个sql可能会将两个表连接在一起,并过滤那些尚未读取它们的收件人。
另一方面,当我们在代码中使用Message
模型时,我们使用以下内容:
message.unread_by?(user)
这个方法在消息类中定义,即使它做的基本相同,它现在有不同的实现。
对于更简单的项目,这真的不是一件大事。在这种情况下,在sql和ruby中实现相同的简单逻辑不是问题。
但是当应用程序开始变得非常复杂时,它开始成为一个问题。如果我们实现了权限系统,该系统根据几十个表中定义的许多条件来检查谁能够访问哪些消息,那么这开始变得非常复杂。很快,你需要加入5个表并手动编写非常复杂的sql来定义范围。
问题的唯一“干净”解决方案是使范围使用实际的ruby代码。他们将获取所有消息,然后使用ruby过滤它们。但是,这会导致两个主要问题:
性能:我们正在为数据库创建更多查询。我不确定DMBS的内部结构,但数据库在单个表上执行5个查询或者一次性连接5个表的查询有多难?
分页:我们希望继续获取记录,直到检索到指定数量的记录。我们逐个获取它们并检查它是否被ruby逻辑接受。一旦接受其中10个,过程就会停止。
很想听听你对此的看法。我没有使用nosql dbms的经验,他们可以用不同的方式解决这个问题吗?
更新:
我只是在讲低音,但这是一个真实的例子。假设我们想在一页上显示所有交易(包括付款和费用)。
我创建了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
gem中定义)。这些权限非常复杂,需要查询其他几个表。
“理想”的事情是写一个巨大的SQL查询,它会返回我需要的东西。它会使分页和其他一切变得更容易。但这将复制我在ability.rb类中定义的逻辑。
我知道CanCan
提供了一种定义能力的sql查询的方法,但是这些能力非常复杂,无法以这种方式定义。
我所做的是工作,但我正在加载所有交易,然后检查我可以阅读哪些。我认为这是一个很大的性能问题。这里的分页似乎没有意义,因为我已经加载了所有记录(它只能节省带宽)。另一种方法是编写一个难以维护的复杂SQL。
答案 0 :(得分:0)
听起来你应该删除一些重复,并且可能更多地使用DB逻辑。没有理由不能在其他方法之间共享命名范围之间的代码。
您可以发布一些有问题的代码供审核吗?