复制方法和范围(和sql)中的逻辑

时间:2011-11-17 08:15:38

标签: ruby-on-rails

命名范围确实使这个问题变得更容易,但它远未解决。常见的情况是在命名范围和模型方法中重新定义逻辑。

我将尝试通过使用一些复杂的例子来演示这种情况的边缘情况。假设我们的Message模型有很多Recipients。每个收件人都能够将邮件标记为自己阅读。

如果您想获取给定用户的未读消息列表,您可以这样说:

Message.unread_for(user)

那将使用将生成sql的命名范围unread_for,该sql将返回给定用户的未读消息。这个sql可能会将两个表连接在一起,并过滤那些尚未读取它们的收件人。

另一方面,当我们在代码中使用Message模型时,我们使用以下内容:

message.unread_by?(user)

这个方法在消息类中定义,即使它做的基本相同,它现在有不同的实现。

对于更简单的项目,这真的不是一件大事。在这种情况下,在sql和ruby中实现相同的简单逻辑不是问题。

但是当应用程序开始变得非常复杂时,它开始成为一个问题。如果我们实现了权限系统,该系统根据几十个表中定义的许多条件来检查谁能够访问哪些消息,那么这开始变得非常复杂。很快,你需要加入5个表并手动编写非常复杂的sql来定义范围。

问题的唯一“干净”解决方案是使范围使用实际的ruby代码。他们将获取所有消息,然后使用ruby过滤它们。但是,这会导致两个主要问题:

  1. 性能
  2. 分页
  3. 性能:我们正在为数据库创建更多查询。我不确定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
    

    paymentsexpenses都需要显示在同一个表中,按创建日期排序并分页。

    paymentsexpenses都有完全不同的:read权限(在能力等级CanCan gem中定义)。这些权限非常复杂,需要查询其他几个表。

    “理想”的事情是写一个巨大的SQL查询,它会返回我需要的东西。它会使分页和其他一切变得更容易。但这将复制我在ability.rb类中定义的逻辑。

    我知道CanCan提供了一种定义能力的sql查询的方法,但是这些能力非常复杂,无法以这种方式定义。

    我所做的是工作,但我正在加载所有交易,然后检查我可以阅读哪些。我认为这是一个很大的性能问题。这里的分页似乎没有意义,因为我已经加载了所有记录(它只能节省带宽)。另一种方法是编写一个难以维护的复杂SQL。

1 个答案:

答案 0 :(得分:0)

听起来你应该删除一些重复,并且可能更多地使用DB逻辑。没有理由不能在其他方法之间共享命名范围之间的代码。

您可以发布一些有问题的代码供审核吗?