在防止删除之前修改 ActiveRecord 模型

Modifying ActiveRecord models before preventing deletion

我的应用程序中的某些记录分配有 DOI,在这种情况下不应删除它们。相反,他们应该更改他们的描述,并在用户触发他们的删除时被标记。我认为,相关模型中的一种方法如下:

before_destroy :destroy_validation

private

def destroy_validation
  if metadata['doi'].blank?
     # Delete as normal...     
     nil
  else
    # This is a JSON field. 
    modified_metadata = Marshal.load(Marshal.dump(metadata))
    description = "Record does not exist anymore: #{name}. The record with identifier content #{doi} was invalid."
    modified_metadata['description'] = description
    modified_metadata['tombstone'] = true
    update_column :metadata, modified_metadata
    raise ActiveRecord::RecordNotDestroyed, 'Records with DOIs cannot be deleted'
  end
end

这确实可以防止删除,但记录之后看起来没有变化,而不是有修改的描述。这是一个测试示例:

test "records with dois are not deleted" do
  record = Record.new(metadata: metadata)
  record.metadata['doi'] = 'this_is_a_doi'
  assert record.save
  assert_raises(ActiveRecord::RecordNotDestroyed) { record.destroy! }
  assert Record.exists?(record.id)
  modified_record = Record.find(record.id)
  puts "#{record.description}" # This is correctly modified as per the callback code.
  puts "#{modified_record.description}" # This is the same as when the record was created.
end

我只能猜测 Rails 正在回滚 update_column 由于出现异常,尽管我可能弄错了。我可以做些什么来防止这种情况发生?

save and destroy are automatically wrapped in a transaction https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

因此 destroy 失败,事务被回滚并且您在测试中看不到更新的列。

您可以尝试使用 after_rollback 回调 https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_rollback

record.destroy 检查 record.errors,如果发现手动更新记录 record.update_doi if record.errors.any?.

before_destroy :destroyable?
...
def destroyable?
  unless metadata['doi'].blank?
    errors.add('Doi is not empty.')
    throw :abort
  end
end

def update_doi
  modified_metadata = Marshal.load(Marshal.dump(metadata))
  description = "Record does not exist anymore: #{name}. The record with identifier content #{doi} was invalid."
  modified_metadata['description'] = description
  modified_metadata['tombstone'] = true
  update_column :metadata, modified_metadata
end

提示:使用 record.reload 而不是 Record.find(record.id)