rubysherpas/paranoia · warning

You are passing an instance of ActiveRecord::Base to `restor

Error message

You are passing an instance of ActiveRecord::Base to `restore`. Please pass the id of the object by calling `.id`

What it means

The class method Model.restore(id_or_ids, opts) is an id-based API: it flattens the argument and runs only_deleted.find(id).restore!(opts) for each element. Since paranoia 2.2 it still accepts ActiveRecord::Base instances by coercing them via id.id, but emits an ActiveSupport::Deprecation warning asking you to pass .id, because a future major release removes the coercion. In suites configured to raise on deprecations (config.active_support.deprecation = :raise) the warning becomes a hard test failure.

Source

Thrown at lib/paranoia.rb:52

        return with_deleted.where.not(paranoia_column => paranoia_sentinel_value)
      end
      # if paranoia_sentinel_value is not null, then it is possible that
      # some deleted rows will hold a null value in the paranoia column
      # these will not match != sentinel value because "NULL != value" is
      # NULL under the sql standard
      # Scoping with the table_name is mandatory to avoid ambiguous errors when joining tables.
      scoped_quoted_paranoia_column = "#{connection.quote_table_name(self.table_name)}.#{connection.quote_column_name(paranoia_column)}"
      with_deleted.where("#{scoped_quoted_paranoia_column} IS NULL OR #{scoped_quoted_paranoia_column} != ?", paranoia_sentinel_value)
    end
    alias_method :deleted, :only_deleted

    # If you want to restore a record
    def restore(id_or_ids, opts = {})
      ids = Array(id_or_ids).flatten
      any_object_instead_of_id = ids.any? { |id| ActiveRecord::Base === id }
      if any_object_instead_of_id
        ids.map! { |id| ActiveRecord::Base === id ? id.id : id }
        ActiveSupport::Deprecation.warn("You are passing an instance of ActiveRecord::Base to `restore`. " \
                                        "Please pass the id of the object by calling `.id`")
      end
      ids.map { |id| only_deleted.find(id).restore!(opts) }
    end

    def paranoia_destroy_attributes
      {
        paranoia_column => current_time_from_proper_timezone
      }.merge(timestamp_attributes_with_current_time)
    end

    def timestamp_attributes_with_current_time
      timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
    end
  end

  def paranoia_destroy
    with_transaction_returning_status do

View on GitHub (pinned to a950fe4981)

Solutions

  1. Pass ids instead of instances: Model.restore(record.id) for one record, Model.restore(records.map(&:id)) for many.
  2. If you already hold the record, prefer the instance method — record.restore (optionally record.restore(opts)) — which restores it and returns self with no warning.
  3. For records fetched from only_deleted, chain instance restore: Model.only_deleted.find(id).restore instead of the class method with an object.
  4. Sweep the codebase for .restore( call sites whose argument is an ActiveRecord object (grep after a paranoia upgrade) and normalize them through an id-coercion helper.

Example fix

# before
Paranoia.restore(deleted_post)               # deprecation: pass the id by calling .id
Paranoia.restore([post_a, post_b])          # same warning for array elements

# after
deleted_post.restore                        # single record you hold: instance method
Paranoia.restore([post_a, post_b].map(&:id))  # class method: ids only
Defensive patterns

Strategy: type-guard

Validate before calling

ids = Array(arg).map { |v| v.is_a?(ActiveRecord::Base) ? v.id : v }
Model.restore(ids)

Type guard

def to_restore_ids(arg)
  Array(arg).flatten.map { |v| v.is_a?(ActiveRecord::Base) ? v.id : v }
end

# usage: Model.restore(to_restore_ids(param))

Prevention

When it happens

Trigger: Calling Model.restore(record_instance) or Model.restore([instance_a, instance_b]) — any element of id_or_ids for which ActiveRecord::Base === id is true enters the warning branch at lib/paranoia.rb:52. Typical cases: restoring a single held object (deleted_post), bulk restores passing an array or relation of instances, and legacy call sites written against paranoia < 2.2 where passing an instance was silent.

Common situations: Upgrading paranoia from 2.1 to 2.2+ makes old tests (e.g. test_restore_on_object_return_self, test_multiple_restore, test_restore_with_associations) that pass instances start warning; CI environments that turn deprecation warnings into errors fail those tests; admin/bulk-restore features pass ActiveRecord objects or relations instead of ids.

Related errors


AI-assisted analysis of rubysherpas/paranoia@a950fe4981 (2026-08-23). Data as JSON: /api/errors/ecf8885406cf5a08. Report an issue: GitHub.