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 doView on GitHub (pinned to a950fe4981)
Solutions
- Pass ids instead of instances: Model.restore(record.id) for one record, Model.restore(records.map(&:id)) for many.
- 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.
- For records fetched from only_deleted, chain instance restore: Model.only_deleted.find(id).restore instead of the class method with an object.
- 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
- Treat the class-level restore as an ids-only API; wrap it with a coercion helper that maps instances to ids.
- When you already hold the object, call the instance method record.restore instead of Model.restore(record).
- Run the test suite with deprecation warnings visible and fix them right after upgrading paranoia, before the 3.x removal of instance coercion.
- Grep for .restore( sites receiving ActiveRecord objects or relations whenever paranoia is bumped.
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.