Rails:如何防止记录被删除?

7

我有一个模型Country和一个countries表。countries表用作ISO国家和货币代码的集合,填充了种子数据后不应该减少其内容。因为Country是ActiveRecord :: Base的子类,它继承了类方法,如destroy、delete_all等,这些方法会删除记录。 我正在寻找一种在模型级别防止记录删除的解决方案。

当然,我知道可以利用面向对象的方法来解决此问题,通过重写这些方法(并在调用时引发错误)。但这假设我必须知道基类继承的所有方法。如果有更优雅的解决方案,我将不胜感激。


本文讨论如何在Rails模型中删除delete和destroy。文章链接:https://www.tigraine.at/2012/02/03/removing-delete-and-destroy-in-rails-models - Mark Swardstrom
你使用的是哪个数据库? - siegy22
也许你应该在数据库上解决这个问题:http://www.tutorialspoint.com/postgresql/postgresql_locks.htm - siegy22
4个回答

13

参考Mark Swardstrom的答案,我提出以下方案,在Rails > 5.0上也能正常运行:

在你的模型中:

before_destroy :stop_destroy

def stop_destroy
  errors.add(:base, :undestroyable)
  throw :abort
end
以下内容将使所有对model.destroy的调用都返回false,从而不会删除您的模型。
你可以说仍然可以调用model.delete并删除你的记录,但由于这些是较低级别的调用,所以这对我来说是非常合理的。
如果您想要直接从数据库中删除记录,那么您也可以这样做,但上述解决方案可以防止在应用程序级别进行删除,这是检查的正确位置。
Rubocop会检查您对delete或delete_all的调用,并提出警告,因此您可以百分之百地确定如果您调用model.delete是因为您真正想要它。
我的解决方案适用于最新版本的Rails,在该版本中,您需要throw :abort而不是返回false。

4

有一个before_destroy回调函数,也许你可以利用它。

before_destroy :stop_destroy

def stop_destroy
  self.errors[:base] << "Countries cannot be deleted"
  return false
end

2
只有在销毁时才会触发,而不是使用“delete”时触发。 - siegy22
同意,这并不是一个完整的答案。 - Mark Swardstrom
尝试在销毁前执行 :stop_destroy,优先级最高。 - Ravi Mariya
这确实是一个非常好的答案。我会写一个新的、更新的答案,加上更多的细节。 - coorasse

2

Rails 4+(包括Rails 6)

在我们的情况下,如果一个对象有任何关联记录,我们希望防止其被销毁。这将是意外的行为,因此我们希望引发一个带有有用错误消息的异常。

我们使用了本地的ActiveRecord::RecordNotDestroyed类,并按照下面所述使用自定义消息。

class MyClass < ApplicationRecord
  has_many :associated_records
  before_destroy :check_if_associated_records

  private 

  def check_if_associated_records
    # set a guard clause to check whether the record is safe to destroy
    return unless associated_records.exists?
    raise ActiveRecord::RecordNotDestroyed, 'Cannot destroy because...'
  end
end

使用 destroydestroy! 的行为

my_class = MyClass.first.destroy
# => false

my_class = MyClass.first.destroy!
# => ActiveRecord::RecordNotDestroyed (Cannot destroy because...)

注意:如果您有一个belongs_tohas_one关联而不是has_many关联,您的方法将如下所示:
def check_if_associated_records
  # set a guard clause to check whether the record is safe to destroy
  return unless associated_record
  raise ActiveRecord::RecordNotDestroyed, 'Cannot destroy because...'
end

0
在Rails 6中,这是我防止记录被删除的方法。 引发的异常将回滚ActiveRecord正在使用的事务,从而防止记录被删除。
    class MenuItem < ApplicationRecord

      after_destroy :ensure_home_page_remains

      class Error < StandardError
      end


      protected #or private whatever you need

      #Raise an error that you trap in your controller to prevent your record being deleted.
        def ensure_home_page_remains
          if menu_text == "Home"
          raise Error.new "Can't delete home page"
        end
    end

所以 ensure_home_page_remains 方法会引发一个 MenItem::Error,导致事务回滚,您可以在控制器中捕获它,并采取任何适当的操作,通常只需在重定向到某个地方后向用户显示错误消息即可。例如:
      # DELETE /menu_items/1
      # DELETE /menu_items/1.json
      def destroy
        @menu_item.destroy
        respond_to do |format|
          format.html { redirect_to admin_menu_items_url, notice: 'Menu item was successfully destroyed.' }
          format.json { head :no_content }
        end
      end

      #Note, the rescue block is outside the destroy method
      rescue_from 'MenuItem::Error' do |exception|
        redirect_to menu_items_url, notice: exception.message
      end
private
#etc...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接