Soft deletes / discard
soft-deletes turns destruction into a reversible flag. Instead of issuing a
DELETE, a discarded record stamps a timestamp column and stays in the table,
so it can be restored later.
1 2 3 4 5 6 7 | |
The column defaults to deleted_at. Pass :column to use another:
1 | |
The column must exist in the table and be nullable:
1 2 3 4 | |
Discarding and restoring
1 2 3 4 5 6 7 8 | |
discard returns False without touching the row when it is already
discarded. undiscard returns False when the record is not discarded. The row
is never removed, so discard and undiscard round-trip the same record.
Scopes
Three class scopes filter on the timestamp column:
1 2 3 | |
Each returns a relation, so the usual chaining applies
(Notice.kept.where(...).order(...)).
Bulk operations
1 2 | |
Both run per-record, so discard callbacks fire for each row.
Callbacks
before-discard, after-discard, before-undiscard, and after-undiscard
wrap the state change. A before-discard that returns False aborts the
discard, leaving the record kept.
1 2 3 4 5 6 7 8 9 | |
See Callbacks for the handler forms (block or method name) and
the :if / :unless options, which apply here too.
Default scope
By default the scopes are opt-in and the plain relation still returns every row.
Pass :default-scope to hide discarded rows from the default relation:
1 2 3 4 5 6 7 8 9 | |
with-discarded and unscope(where => 'discarded_at') lift the hidden filter
when you need the full set. The default scope applies to relation queries
(all, where, associations); a direct find($id) still locates a discarded
row by primary key.