【问题标题】:How can I overwrite the oldest record instead of creating a new one?如何覆盖最旧的记录而不是创建新记录?
【发布时间】:2014-06-06 14:24:13
【问题描述】:

我正在使用 Ruby on Rails 4,在 before_create 回调中,我想让它覆盖现有记录,而不是在数据库中创建新记录。也就是说,例如,当我尝试创建新记录时,我有 10 条记录存储到数据库表中,那么第 11 条记录应该覆盖最旧的记录,而不是创建新记录。

我尝试了以下代码:

before_create do
  if more_than_ten?
    # Overwriting oldest record
    oldest_record = self.class.order(:created_at).first

    self.id = oldest_record.id
  else
    true
  end
end

但我收到此错误:

ActiveRecord::RecordNotUnique (Mysql2::Error: Duplicate entry '1' for key 'PRIMARY': INSERT INTO ...)

如何覆盖最旧的记录而不是创建新记录?

【问题讨论】:

  • 如果存在超过 10 个,您不能只查询新的并重定向到最旧的编辑操作吗?
  • @Baloo - 我认为您指的是控制器操作,但在这种情况下,这是一个模型问题。
  • 我是,为什么是模型问题?如果超过 10 个,则获取最旧的并让他们进行编辑。
  • @Baloo - 说来话长……基本上,我想确保条件。

标签: ruby-on-rails ruby activerecord ruby-on-rails-4 callback


【解决方案1】:

我在这里看到 2 个选项:

  • 删除最旧的记录并创建一个新记录
  • 用新的属性更新最旧的记录

我们将选择第一个选项

before_create do
  if more_than_ten?
    oldest_record = self.class.order(:created_at).last.destroy
  else
    true
  end
end

短版:

before_create do
  self.class.order(:created_at).last.destroy if more_than_ten?
  true # if more_than_ten? return false, it will also return false for this before_create
end

为什么使用第一个选项?因为如果我们更新“最旧”的记录,下次我们添加新记录时,它会更新相同的记录(我们依赖created_at字段)。

我们可以使用updated_at 字段来依赖,但正如@NickVeys 所说,你在before_create 回调中,这意味着如果我们用新记录的属性更新最旧的记录,我们不会实际上创造了一个记录。它正在改变 Rails 的默认行为(更新而不是创建),并可能导致混乱(太多的黑魔法甚至会迷惑最优秀的巫师!)。


第二个选项:我认为这是行不通的,因为 before_create 回调不会为创建的对象返回 true 或有效属性:

before_create do
  if more_than_ten?
    oldest_record = self.class.order(:created_at).last # get the record with the lowest created_at

    oldest_record.update_attributes(self.attributes.merge({ created_at: DateTime.current }))
    false # returns false to stop the creation process of the new record
  else
    true
  end
end

如您所见,我们将created_at 字段更新为DateTime.now,以“模拟”现在创建的对象。它解决了上面暴露的问题:下次我们创建记录时,它不会使用相同的记录(因为.order(:created_at) 将新更新的对象放在最上面)而是另一个最旧的记录。


正如@Stefan 指出的那样,对于选项#1,我们应该在 10 号之后销毁所有记录:

before_create do
  self.class.order(:created_at).offset(10).destroy_all
end

【讨论】:

  • 如果在数据库中我想继续使用最旧记录的id 怎么办?如果在before_create 回调之后创建事务过程失败会发生什么?旧记录无论如何都会被删除?
  • 对于第二个选项,为什么不更新(在before_create 回调中)甚至将updated_at 属性更新为当前时间,以便避免@NickVeys 所说的问题?
  • 顺便说一句,我认为销毁 after_create 钩子中除最后 10 个条目之外的所有条目会更加健壮。
  • 未经测试,但这可能会起作用:after_create { self.class.order(:created_at).offset(10).destroy_all },你甚至不需要more_than_ten? 检查
  • @user502052 我不明白你为什么害怕 id 列大小,11 位数字很多:99 999 999 999 是最后一个可能的 id。如果您每秒创建一条记录,则需要大约 27 777 777 小时才能到达最后一个 id!这也是〜1 157 407天〜= 3 170年!唯一可以限制它的方法是,如果您每秒创建 1000 条记录(但我记得您将其限制为 10 条)!
【解决方案2】:

如果您想使用与原始 ID 相同的 ID,那么您应该能够执行以下操作

before_create do
 if more_than_ten?
   # Overwriting oldest record
   oldest_record = self.class.order(:created_at).first
   old_id = oldest_record.id
   oldest_record.destroy
   self.id = old_id
  else
    true
  end
end

如果你想确保它在失败时不会删除旧记录,你可以确保它被包含在ActiveRecord::Base.transaction 的事务中

【讨论】:

  • ActiveRecord::Base.transaction 会在before_create 回调中默认启用吗?或者,我是否必须自己声明ActiveRecord::Base.transaction(如您在编辑前的回答中一样)?
  • before_create 回调的最后添加raiser "FAILED" 以测试在引发错误时记录是否真的被销毁。如果是 => 换行,如果不是 => 你很好。 @user502052
  • 来自documentationsavedestroy 都包含在一个事务中,以确保您在验证或回调中所做的任何事情都将在其受保护的范围内发生。”
  • @Stefan 说了什么。我自己查了一下,以确保我告诉你的正确(因此进行了编辑),它将处理它自己的事务恢复。然而,我认为值得一提的是,能够将事物包装在创建和保存之外的事务中,以确保在发生错误时不会破坏事物。就像如果你想调用一个特定的函数,你可以ActiveRecord::Base.transaction{ func1 }
【解决方案3】:

您正在执行创建,它映射到插入语句。将 ID 设置为现有记录并不会让它知道您想要什么,它只是将 ID 设置为现有记录,对于主键来说,这不是您的数据库所关心的。

您需要在插入之前删除该记录才能使其正常工作。

【讨论】:

  • 感谢您的努力,但其他问题请看我的 cmets。
【解决方案4】:

我会这样做:

before_create do
  if more_than_ten?
    self.id = self.class.oldest.id
    find(self.id).destroy
  end
  true
end

# You should use oldest scope
def self.oldest
  all.order(created_at: :desc).first
end

或者还有另一种方法可以将旧记录更新为新记录。新记录没有 id,因此您可以覆盖 ActiverRecord 的保存方法,而不是使用 before_create。

def save
  more_than_ten? ? all.oldest.update(attributes) : super
end

def self.oldest
 all.order(created_at: :desc).first
end

【讨论】:

  • 如果我想在数据库中继续使用最旧记录的id 怎么办?如果在before_create 回调之后创建事务过程失败会发生什么?旧记录无论如何都会被删除?
  • before_create 是创建当前模型的事务的一部分。在以后失败的情况下,它不会破坏任何东西。我修复了我的代码以使用原始 ID。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-08
  • 1970-01-01
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 2012-07-25
  • 2021-03-30
相关资源
最近更新 更多