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

时间:2014-06-06 14:24:14

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

我正在使用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 ...)

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

4 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

如果您想使用与原始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

的交易中

答案 2 :(得分:0)

你正在做一个映射到insert语句的create。将ID设置为现有记录并不会使其知道您想要什么,它只是将ID设置为现有记录,而主键不是您的数据库所关心的。

您需要在插入之前删除该记录才能使其生效。

答案 3 :(得分:0)

我会这样做:

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的save方法,而不是使用before_create。

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

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