【问题标题】:What is the 'Rails Way' to implement a dynamic reporting system on data什么是实施数据动态报告系统的“Rails 方式”
【发布时间】:2023-03-23 11:53:01
【问题描述】:

简介

我正在做一个系统,其中我有一个非常简单的布局,仅包含事务(带有基本 CRUD)。每笔交易都有日期、类型、借方金额(减)和贷方金额(加)。想想网上银行对账单,就差不多了。

我遇到的问题是我的控制器很瘦,并且担心可能过度查询数据库。

一个简单的报告示例

  • 所选期间的总借记,例如SUM(debit) as total_debit
  • 所选期间的总信用,例如SUM(credit) as total_credit
  • 总体总数,例如total_credit - total_debit

  • 报告必须允许动态日期范围,例如where(date BETWEEN 'x' and 'y')

  • 日期范围永远不会超过一年,一次最多只能说 1000 个事务/行

所以在我创建的控制器中:

def report
  @d = Transaction.select("SUM(debit) as total_debit").where("date BETWEEN 'x' AND 'y'")
  @c = Transaction.select("SUM(credit) as total_credit").where("date BETWEEN 'x' AND 'y'")
  @t = @c.credit_total - @d.debit_total
end

其他问题信息

我的实际报告有接近 6 或 7 个数据库查询(例如,根据类型 == 1 或类型 == 2 等提取总贷方/借方)并且有更多计算,例如总计某些贷方/借方类型和然后从其他总数中添加和删除这些总数。

我正在尽最大努力坚持“瘦模型,胖控制器”,但我的控制器需要传递给视图的变量数量存在问题。在您创建要传递给视图的变量之前,Rails 似乎非常简单。除了将变量创建线放入控制器并通过将一些查询点点滴滴放入模型中使其“更瘦”之外,我看不出您还能如何做到这一点。

我在模型中创建变量然后让控制器将这些变量传递给视图的地方有什么遗漏吗?

【问题讨论】:

  • 一个问题太多了。请尝试将它们减少到您的前一两个,以避免混淆答案并稀释整个问题的价值。 Stack Overflow 需要简洁且定义明确的问题和答案。
  • 稍后我会尝试让它变得更简单,我会做更多的搜索,因为我认为这将是一个很常见的问题,也许我太过分了并且担心太多关于瘦控制器,胖模型短语......
  • 我会介绍一个TransactionReporter 对象,它应该执行它需要的查询并处理所有计算,当您执行 ActiveRecord 查询时,它们基本上返回 ruby​​ 数组,因此您可以将该数据存储在实例中报告对象上的变量,并在所有计算完成后将其返回到视图
  • @house9 我对 Rails 有点太新了,而且它是行话(因为我来自非框架 PHP)来完全理解 TransactionReporter 对象的含义以及如何在我的示例中实现它.如果您有时间并且很容易通过答案给出一个简单的示例,那么我会非常乐意试一试并报告结果,现在我将寻求 Andrea 的答案。
  • TransactionReporter 将是一个 PORO(普通的旧 ruby​​ 对象),而不是 rails 模型或控制器 - 只需使用 Andrea Fiore 最初建议的内容。如果您发现 Transaction 对象开始变得过于臃肿 - 业务规则、验证、数据访问等,并且您想将报告逻辑移出到它自己的对象中,那么请这样做

标签: ruby-on-rails ruby ruby-on-rails-3.2 reporting


【解决方案1】:

在 Activerecord 中编写查询的更惯用方式可能是:

class Transaction < ActiveRecord::Base
  def self.within(start_date, end_date)
    where(:date => start_date..end_date)
  end

  def self.total_credit
    sum(:credit)
  end

  def self.total_debit
    sum(:debit)
  end
end

这意味着在您的控制器中发出 3 个查询,如果您创建数据库索引,这应该不是什么大问题,并将事务数量和时间范围限制在合理的范围内:

@transactions = Transaction.within(start_date, end_date)
@total = @transaction.total_credit - @transaction.total_debit

最后,您还可以使用 Ruby 的 Enumerable#reduce 方法,通过直接遍历从数据库中检索到的交易列表来计算您的总数。

@total = @transactions.reduce(0) { |memo, t|  memo + (t.credit - t.debit) }

对于非常小的数据集,这可能会提高性能,因为您只需访问数据库一次。但是,我认为第一种方法更可取,当您的数据库中的记录数开始增加时,它肯定会提供更好的性能

【讨论】:

  • 嗨 Andrea,self.total_credit 等非常有帮助,但我有一个关于 self.within 的问题 - 这如何让我通过链接指定参数 start_date 和 end_date 例如&lt;a href='transactions/report?start_date=2013-03-17&amp;end_date=2014-03-17'&gt;link&lt;/a&gt;。我还假设定义,例如,15 个不同的变量可以在控制器中完成所有这些工作(使用模型定义使 SQL 查询读起来很好),也许我有点担心把 as在模型中尽可能多的?
  • 您可以将日期参数从 String 实例转换为 Date 对象,然后将它们作为参数提供给 'within' 方法。在您的控制器中,尝试类似:'Transaction.within(Date.parse(params[:start_date], Date.parse(params[:end_date]))'
  • 太好了,听起来很完美,感谢@Andrea 的帮助!
【解决方案2】:

我正在为 x 和 y 输入 params[:year_start]/params[:year_end],这样做安全吗?

您不应该将params[:anything] 直接嵌入到查询字符串中。请改用这种形式:

where("date BETWEEN ? AND ?", params[:year_start], params[:year_end])

我的实际报告可能有接近 5 次数据库调用,然后对这些变量进行 6 或 7 次计算,我是否应该只查询一次日期范围,然后对数组/哈希等进行所有工作?

这有点主观,但我会给你我的意见。通常,扩展应用程序层比扩展数据库层更容易。您目前是否遇到数据库性能问题?如果是这样,请考虑将逻辑移至 Ruby 并将更多资源添加到您的应用程序服务器。如果没有,也许现在担心这个还为时过早。

我真的不知道如何将大部分工作/计算纳入模型,我了解范围,但您如何将日期范围放入范围并仍然使用 GET 参数?

你见过has_scope吗?这是一个很棒的 gem,可让您在模型中定义范围并让它们自动应用于控制器操作。我通常将其用于过滤/搜索,但看起来您可能有一个很好的用例。

如果您可以举例说明如何通过广泛的数据库调用创建数组,然后对该数组进行各种计算,然后将这些变量传递给模板,那就太棒了。

这不太适合 Stack Overflow,而且与您在标准 Rails 应用程序中所做的工作相差无几。我会阅读 Rails 指南和一本 Ruby 书籍,这并不难理解。

【讨论】:

  • 嗨,为答案喝彩,别担心我的意思是使用?在 where() 所以这至少是正确的。我觉得主要问题是我需要简化我的问题。我确实认为这将是一个更常见的问题,也许我使用了错误的术语......
  • @bensmithbwd 我建议将其分解为非常具体的问题。这样你肯定会得到更好的回应。
猜你喜欢
  • 2013-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
  • 2018-05-16
  • 2011-08-30
相关资源
最近更新 更多