Mongoid::Archivable (mongoid_archival)

Mongoid::Archivable
enables archiving (soft delete) of Mongoid documents.
Instead of being removed from the database, archived docs are flagged with an archived_at
timestamp.
This allows you to maintain references to archived documents, and restore if necessary.
Instability Warning
Versions prior to 1.0.0 are in alpha state. Behaviors, APIs, method names, etc.
may change anytime without warning. Please lock your gem version, be careful when upgrading,
and write tests in your own project.
Disambiguation
- This gem is different than mongoid-archivable,
which moves documents to a separate "archive" database/collection. Cannot be used concurrently with
this gem as both use the
Mongoid::Archivable
namespace.
- This gem is forked from mongoid_paranoia.
Can be used concurrently with this gem. See section below for key differences.
TODO
Usage
Installation
In your application's Gemfile:
gem 'mongoid_archival'
Adding to Model Class
class Person
include Mongoid::Document
include Mongoid::Archivable
end
Archiving with Documents
person.archive
person.archive_without_callbacks
person.restore
person.restore(recursive: true)
Querying
Person.all
Person.current
Person.archived
Global Configuration
You may globally configure field and method names in an initializer.
Mongoid::Archivable.configure do |c|
c.archived_field = :archived_at
c.archived_scope = :archived
c.nonarchived_scope = :current
end
Callbacks
Archivable documents have the following new callbacks.
Note that these callbacks are not fired on #destroy
.
before_archive
after_archive
around_archive
before_restore
after_restore
around_restore
class User
include Mongoid::Document
include Mongoid::Archivable
before_archive :before_archive_action
after_archive :after_archive_action
around_archive :around_archive_action
before_restore :before_restore_action
after_restore :after_restore_action
around_restore :around_restore_action
def before_archive_action
throw(:abort) if name == 'Pete'
end
end
Relation Dependencies
This gem adds two new relation dependency handling strategies:
:archive
- Invokes #archive
and callbacks on each dependency, recursively
including dependencies of dependencies. Analogous to :destroy
.
:archive_all
- Calls .set(archived_at: Time.now)
on the
dependency scope in a single query. Much faster but does not support callbacks
or dependency recursion. Analogous to :delete_all
.
If the dependent model is not archivable, these strategies will be ignored without any effect.
class User
include Mongoid::Document
include Mongoid::Archivable
has_many :pokemons, dependent: :archive
belongs_to :gym, dependent: :archive_all
end
In addition, dependency strategies :nullify
, :restrict_with_exception
,
and :restrict_with_error
will be applied when archiving documents.
:destroy
and :delete_all
are intentionally ignored.
Protecting Against Deletion
Add the Mongoid::Archivable::Protected
mixin to cause
#delete
and #destroy
methods to raise an error.
The bang methods #delete!
and #destroy!
can be used instead.
This is useful when migrating a legacy codebase.
class Pokemon
include Mongoid::Document
include Mongoid::Archivable
include Mongoid::Archivable::Protected
end
venusaur = Pokemon.create
venusaur.delete
venusaur.destroy
venusaur.delete!
venusaur.destroy!
Gotchas
The following require additional manual changes when using this gem.
Uniqueness Validation
You must set scope: :archived_at
in your uniqueness validations to prevent
validating against archived documents.
validates_uniqueness_of :title, scope: :archived_at
Indexes
You should add archived_at
to your query indexes. As a rule-of-thumb, we recommend
to add archived_at
as the final key; this will create a compound index that will be
selected with or without archived_at
in the query.
index category: 1, title: 1, archived_at: 1
Note that this may not give the best performance in all cases, for example
when performing a time-range query on the value of archived_at
. Please refer to the
MongoDB Indexes documentation
to learn more about index design.
Comparison with Mongoid::Paranoia
We used Mongoid::Paranoia at TableCheck
for many years. While many of design assumptions of Mongoid::Paranoia
lead to initial productivity, we found them ultimately limiting and unintuitive
as we grew both our team and our codebase.
Key Differences
- The flag named is
archived_at
rather than deleted_at
.
The name deleted_at
was confusing with respect to hard deletion.
- Mongoid::Paranoia overrides the
#delete
and #destroy
methods; this gem does not.
Monkey patches and hackery are removed; behavior is less surprising.
- This gem does not set a default scope on root (non-embedded) docs.
Use the
.current
(non-archived) and .archived
query scopes as needed.
Mongoid::Paranoia relies on .unscoped
Migration Checklist
About Us
Mongoid::Archivable is made with ❤ by TableCheck,
the leading restaurant reservation and guest management app maker.
If you are a ninja-level 🥷 coder (Javascript/Ruby/Elixir/Python/Go),
designer, product manager, data scientist, QA, etc. and are ready to join us in Tokyo, Japan
or work remotely, please get in touch at careers@tablecheck.com.
Shout out to Durran Jordan and Josef Šimánek for their original work on
Mongoid::Paranoia.