简单总结,destroy会在一个transaction中检查关联对象和callback,而delete直接删记录。这对于delete_all和destroy_all同理,而在rails 5中,不推荐xxx_all带查询条件,推荐where().xxx_all

delete是有分实例方法和类方法的,实例方法会重用类方法

# activerecord-5.0.2/lib/active_record/persistence.rb
def delete
  self.class.delete(id) if persisted?
  @destroyed = true
  freeze
end


实例的delete,完整调用栈如下

instance_delete.html

简略来看是这样


最终的delete_all如下,它只是发起一条delete语句,并不涉及关联对象或自定义callback

def delete_all(conditions = nil)
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
    if MULTI_VALUE_METHODS.include?(method)
      send("#{method}_values").any?
    elsif SINGLE_VALUE_METHODS.include?(method)
      send("#{method}_value")
    elsif CLAUSE_METHODS.include?(method)
      send("#{method}_clause").any?
    end
  }
  if invalid_methods.any?
    raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
  end

  if conditions
    ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
      Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
      To achieve the same use where(conditions).delete_all.
    MESSAGE
    where(conditions).delete_all
  else
    stmt = Arel::DeleteManager.new
    stmt.from(table)

    if joins_values.any?
      @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
    else
      stmt.wheres = arel.constraints
    end

    affected = @klass.connection.delete(stmt, 'SQL', bound_attributes)

    reset
    affected
  end
end


而destory则是套在transaction和callback之中


准确来说,是include module Persistence之后,还有include Callbacks和Transactions

activerecord-5.0.2/lib/active_record/transactions.rb如下

def destroy
  with_transaction_returning_status { super }
end


activerecord-5.0.2/lib/active_record/callbacks.rb如下

def destroy
  @_destroy_callback_already_called ||= false
  return if @_destroy_callback_already_called
  @_destroy_callback_already_called = true
  _run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
  @_association_destroy_exception = e
  false
ensure
  @_destroy_callback_already_called = false
end


activerecord-5.0.2/lib/active_record/persistence.rb如下

def destroy
  raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
  destroy_associations
  self.class.connection.add_transaction_record(self)
  destroy_row if persisted?
  @destroyed = true
  freeze
end


这个include的顺序就在Base中

#activerecord-5.0.2/lib/active_record/base.rb
module ActiveRecord
  class Base
    include Persistence
    include Callbacks
    include Transactions
  end
end


完整调用栈如下(但实际上这个例子没自定义callback)

instance_destroy.html

因此,在整个transaction中,任何关联或callback报错,都会导致删除失败

顺提,类方法destroy_all,它先查出(如果还没预加载),然后再给每个对象调destroy。有趣的是,这个跟delete的“相反”,是类方法调实例方法

# Destroys the records by instantiating each
# record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
# Each object's callbacks are executed (including :dependent association options).
# Returns the collection of objects that were destroyed; each will be frozen, to
# reflect that no changes should be made (since they can't be persisted).
#
# Note: Instantiation, callback execution, and deletion of each
# record can be time consuming when you're removing many records at
# once. It generates at least one SQL +DELETE+ query per record (or
# possibly more, to enforce your callbacks). If you want to delete many
# rows quickly, without concern for their associations or callbacks, use
# #delete_all instead.
#
# ==== Examples
#
#   Person.where(age: 0..18).destroy_all
def destroy_all(conditions = nil)
  if conditions
    ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
      Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
      To achieve the same use where(conditions).destroy_all.
    MESSAGE
    where(conditions).destroy_all
  else
    records.each(&:destroy).tap { reset }
  end
end