【问题标题】:Rails: Tight coupling between deeply nested models: how to uncouple them for tests and DB efficiency?Rails:深度嵌套模型之间的紧密耦合:如何将它们解耦以进行测试和数据库效率?
【发布时间】:2017-05-21 00:15:25
【问题描述】:

我有以下模型和关联:

SuccessCriterion
  has_many :requirements
  has_many :findings, through: :requirements

Requirement
  belongs_to :success_criterion
  has_many :findings

Finding
  belongs_to :requirement
  has_one :success_criterion, through: :requirement
  • 每个结果都可以有一个状态critical
  • 如果一个需求至少有一个关键发现,那么它的状态也是关键
  • 如果成功标准至少有一个关键要求,那么它的状态也是关键

所以临界状态是继承的。

在显示成功标准列表时,我想显示其状态是否为关键。为此,我通过遍历所有需求并再次遍历所有发现并搜索至少一个关键发现来计算状态。

这需要大量的数据库查询,所以我正在考虑在成功标准中缓存状态,并且每次我添加/修改/删除结果时,都应该在成功标准中更新状态。

最好的方法是什么?我想过像这样的after_save 过滤器:

model Finding
  belongs_to :requirement
  has_one :success_criterion, through: :requirement

  after_save :update_success_criterion_status

  private
    def update_success_criterion_status
      if status.critical?
        success_criterion.update_attribute :status, :critical
      else
        success_criterion.calculate_status! # Iterate through all findings and look for at least one critical
      end
    end
end

在开发中,我猜这会很好用。

但是测试呢?对于单元测试成功标准,我必须为每个测试提供必要的关联,否则after_save 过滤器会崩溃。这意味着每次测试都会产生大量开销。

当然,我可以做一些 hacky 之类的事情

after_save :update_success_criterion_status, unless: -> { Rails.env.test? }

但我认为这不是一个好方法。

有没有更好的方法来做到这一点?还是我走错了路?也许甚至有宝石可以处理这样的事情(修改相关资源的属性)?

附言

类似的要求是反缓存深度关联元素的可能性。假设我们有另一个模型,例如

Project
  has_many :success_criteria

当我们想知道一个项目中有多少发现时,我们必须通过成功标准、要求和发现来计算它们。计数器缓存也可以在这里节省大量查询。但是需要处理所有关联模型的大量创建/更新/删除内容以更新计数器缓存...

【问题讨论】:

  • 我的回答对你有帮助吗?如果没有,你介意评论一下为什么不?
  • 感谢您的全面帖子。我还不能看它,但我肯定会很快的。

标签: ruby-on-rails database caching model


【解决方案1】:

我认为最好的方法是创建一个所有关联模型都可以有效查看的模型。您可以创建一个 Status 模型,其中包含用于不同状态类型的布尔列(显然,其中至少一个是 critical 列),您可以在其中存储计数。

您将能够使用<model_instance>.status 检索任何模型的状态,然后您可以确定它是否重要。例如,finding.status.critical?,或a_success_criterion.status.critical?,甚至a_success_criterion.critical?

model Finding
  belongs_to :requirement
  has_one :success_criterion, through: :requirement
  has_one :status

  def critical?
    status.critical? # Note: Rails should define this method for you on the status because it's a boolean column
  end
end

同样,要检索Requirements 的状态,您只需使用requirement.statusrequirement.critical? 来确定要求是否关键。

model Requirements
  belongs_to :success_criterion
  has_many :findings
  has_many :statuses

  def critical?
    return true if statuses.where(critical: true).any?
  end
end

要检索SuccessCriterion 的状态,您只需使用success_criterion.status 即可,因为:

model SuccessCriterion
  has_many :requirements
  has_many :findings, through: :requirements
  has_many :statuses

  def critical?
    return true if statuses.where(critical: true).any?
  end
end

关键部分(不是双关语——最初:)是当您创建一个结果并因此创建一个状态时:您必须或应该为状态提供结果、要求和成功的 id它所属的标准,因此您可能希望为它们的存在添加验证。此外,您可能希望在创建状态时添加 before_validation,在 StatusFinding 中,看起来像这样(在 Status 中):

model Status
  belongs_to :finding, :requirement, success_criterion
  before_validation :populate_ids
  validates_presence_of :finding_id, :requirement_id, :success_criterion_id

  def populate_ids
    self.finding_id = finding.id
    self.requirement_id = finding.requirement.try(:id)
    self.success_criterion_id = finding.requirement.try(:success_criterion).try(:id)
  end
end

在您的测试中,您只需为 id 提供整数,但如果您不想测试此类关系,则它们不必是实际相关模型 - 这是它的美妙之处,除了高效查询:)。

你大概明白了。我希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-09
    • 1970-01-01
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多