/sequel-paranoid

A plugin for the Ruby ORM Sequel, that allows soft deletion of database entries.

Primary LanguageRubyMIT LicenseMIT

sequel-paranoid

A plugin for the Ruby ORM Sequel, that allows soft deletion of database entries.

Usage

Basics

In order to use the paranoid plugin in the very basic version, just add it your model like this:

class ParanoidModel < Sequel::Model
  plugin :paranoid
end

This will assume that you have a column deleted_at, which gets filled with the current timestamp once the model gets destroyed:

instance = ParanoidModel.create(:something)

instance.deleted?    # => false
instance.deleted_at  # => nil

instance.soft_delete # sets the deleted_at column and soft deletes the record. `destroy` still works normally.
record.paranoid_models_dataset.soft_delete # also allows soft deletion in bulk on the dataset.

instance.deleted?    # => true
instance.deleted_at  # => current timestamp

This will assume that you have a column deleted_at, which gets filled with the current timestamp once the model gets destroyed:

instance = ParanoidModel.create(:something)

instance.deleted?    # => false
instance.deleted_at  # => nil

instance.soft_delete # sets the deleted_at column and soft deletes the record. `destroy` still works normally.

instance.deleted?    # => true
instance.deleted_at  # => current timestamp

Reading The Data

By default the plugin will not change the way scopes have been working. So if you want to take the deletion state of an entry into account you can use the following dataset filters:

ParanoidModel.not_deleted.all  # => Will return all the non-deleted entries from the db.
ParanoidModel.deleted.all      # => Will return all the deleted entries from the db.
ParanoidModel.with_deleted.all # => Will ignore the deletion state (and is the default).

Renaming The Deletion Timestamp Columns

If you don't want to use the default column name deleted_at, you can easily rename that column:

class ParanoidModel < Sequel::Model
  plugin :paranoid, :deleted_at_field_name => :destroyed_at
end

instance = ParanoidModel.create(:something => 'foo')

instance.destroy
instance.destroyed_at # => current timestamp

Side Effects Mode (Enable Default Scope & Override destroy)

If you want a more "magical" implementation, you will need to opt-in.

In order to exclude deleted entries by default from any query, you can enable an option in the plugin. We highly advise against these modes because it will not interoperate well with associations when you want to load deleted associated instances.

If you want to override destroy, you can opt-in for this with the soft_delete_on_destroy option.

class ParanoidModel < Sequel::Model
  plugin :paranoid, :enable_default_scope => true, :soft_delete_on_destroy => true
  one_to_many :child_models
end

class AnotherModel < Sequel::Model
  plugin :paranoid, :soft_delete_on_destroy => true
  many_to_one :paranoid_model
end

# create some dummy data

parent1 = ParanoidModel.create(:something => 'foo')
parent2 = ParanoidModel.create(:something => 'bar')

child1  = ChildModel.create(:something => 'foo')
child2  = ChildModel.create(:something => 'bar')
child3  = ChildModel.create(:something => 'baz')

parent1.add_child_model(child1)
parent1.add_child_model(child2)
parent2.add_child_model(child3)

# destroy one of the children

child1.destroy
child1.deleted? # => true

# load the children

ChildModel.all                              # => [child2, child3] (works as expected)
ChildModel.dataset.unfiltered.all           # => [child1, child2, child3] (works as expected)

parent1.child_models_dataset.all            # => [child2] (works as expected)
parent1.child_models_dataset.unfiltered.all # => [child1, child2, child3] (broken)

Note that the last command is broken, as child3 is not associated with parent1. The reason for that is unfiltered, which will not only remove the deleted_at check but also the assocation condition of the query.

Validates Helpers

You can supply include_validation_helpers: true to enable uniqueness on non-deleted records.

class ParanoidModel < Sequel::Model
  plugin :paranoid, include_validation_helpers: true
  
   def validate
     super
     validates_unique :name, paranoid: true
   end
    
end

Using Unique Constraints With Soft-deletion

You can use the :deleted_column_default option in order to specify a value that is not NULL, which will allow you to include the column in a unique constraint.

Using this option requires you to also set this default column value in your database.

class ParanoidModel < Sequel::Model
  plugin :paranoid, :deleted_column_default: Time.at(0)
end

Contributors