我正在使用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 ...)
如何覆盖最旧的记录而不是创建新记录?
答案 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