首页 > 解决方案 > Rails - 将值附加到 JsonB 字段时的更好方法

问题描述

每当将候选记录输入/移动到新入口点时,我都在努力填充 jsonB 字段。

我目前正在使用ff。方法:

(它们可以被多次调用,并且只会将新入口点附加到 jsonB 字段 [entry])

// Method in Candidate model
def log_entry(value)
  self.entry = Array(self.entry) << { val: value, timestamp: Time.now.to_i }
end

def log_entry!(value)
  self.log_entry(value)
  self.save
end

在每个控制器入口点调用

ca = Candidate.new(name: 'John Doe')
ca.log_entry('Point 1')
ca.save

移动到新入口点时也可以这样调用

ca = Candidate.find id
ca.log_entry!('Point 2')

调用两次后的记录是这样的log_entry: Candidate Model

Name: 'John Doe'
Entry: [{ val: 'Point 1', timestamp: '<time>' }, { val: 'Point 2', timestamp: '<time>' }]

到目前为止,上述方法正在达到预期的效果。但是我的印象是代码太杂乱了,并且有更好的方法。

任何想法将不胜感激。

标签: ruby-on-railsruby

解决方案


这可能看起来/感觉很混乱,只是因为这违反了单一责任原则。您没有说明为什么要为这些对象中的每一个保留似乎是访问日志的Candidate内容,但是当您考虑它时,不应该有Candidates责任记录访问它的原因和时间。通过以这种方式将它们耦合在一起,每当控制器入口点的两个调用者同时发生时,您将获得一个StaleObjectError或简单地删除一个日志条目。

另外顺便说一句,这是当前编码的,要记录条目日志,您必须手动调用单独的方法。这使得以后在Candidate.find(x)某处添加调用的任何人都需要log_entry在检索到Candidate.

我建议的是两件事:

  1. 将此访问日志存储到它自己的对象中,即它是一个运行日志,以便它可以轻松地同时被多个源更新。这可以通过将log_entry内容存储分解为它自己的对象并Candidate通过has_many关系将其链接到对象来实现。

  2. 创建一个新的关注点,可以混合到任何需要此访问日志记录的模型中,它会覆盖任何访问器方法,例如findor find_by,以便log_entry在返回 found 之前插入一个新的关注点Candidate。如果您想对此进行详细说明,您可以使用装饰器模式通过使用装饰器来修改方法的内部工作,从而允许访问日志轻松附加到任何现有方法定义。

如果您需要联轴器:

既然您需要耦合,那么我建议您仍然将逻辑分解为一个关注点,以便您可以装饰需要更新这些日志条目的适当方法。并利用乐观锁定和一些更新重试逻辑来确保数据在并发请求期间不会丢失。这将消除多个知识的笨拙,将其限制在一个地方,并消除意外数据丢失的情况。

一些粗略的代码(只是find方法修改)如下所示:

# — Candidate Class —
def find(id, reason=“”)
  super(id).tap do |candidate|
    Array(candidate.log_entry) << { ... }
    candidate.save
  rescue ActiveRecord::StaleObjectError => ex
    # do some logging here
    retry
  end
end

上面对find方法的修改会找到请求的对象,在上面记录日志条目,在stale object错误的情况下重试记录,并返回找到的对象。这样就不需要在 Rails 应用程序的其他任何地方重复代码。


推荐阅读