Discard Documentation

repository·master·Indexed 25 days ago

https://github.com/jhawthorn/discard

A lightweight ActiveRecord mixin for implementing soft-deletion by flagging records as discarded instead of deleting them. It provides methods for discarding and undiscarding records, specialized scopes (kept, discarded, with_discarded), and support for custom discard columns and lifecycle callbacks.

Tokens
1.9K
Snippets
7
Records
13
Agent score
80%

What's inside Discard

  1. How to handle associations with Discard

    master

    Unlike paranoia, Discard does not automatically destroy dependent associations. This prevents accidental data loss. Instead, you should manage associations using one of two patterns:

    1. Independent Discarding

    Keep records independent. For example, a Comment can remain 'kept' even if its parent Post is discarded. You simply query for kept records using the kept scope.

    2. Dependent Scoping

    If a child record should only be considered 'kept' if its parent is also 'kept', override the child's kept scope using a join and a merge.

    class Comment < ActiveRecord::Base
      belongs_to :post
      include Discard::Model
    
      # Only returns comments where both the comment and the post are kept
      scope :kept, -> { undiscarded.joins(:post).merge(Post.kept) }
    
      def kept?
        undiscarded? && post.kept?
      end
    end
    class Comment < ActiveRecord::Base
      belongs_to :post
    
      include Discard::Model
      scope :kept, -> { undiscarded.joins(:post).merge(Post.kept) }
    
      def kept?
        undiscarded? && post.kept?
      end
    end
  2. Declare a record as discardable

    master

    To enable soft-deletion on an ActiveRecord model, include Discard::Model in the class. You must also ensure the model has a discarded_at datetime column with an index.

    Migration Example

    You can generate the migration using Rails:

    rails generate migration add_discarded_at_to_posts discarded_at:datetime:index

    Or create a manual migration:

    class AddDiscardToPosts < ActiveRecord::Migration[5.0]
      def change
        add_column :posts, :discarded_at, :datetime
        add_index :posts, :discarded_at
      end
    end
    class Post < ActiveRecord::Base
      include Discard::Model
    end
  3. Configure a custom discard column

    master

    If you are migrating from another gem like paranoia and want to reuse an existing column (e.g., deleted_at), set the discard_column attribute in your model.

    class Post < ActiveRecord::Base
      include Discard::Model
      self.discard_column = :deleted_at
    end
    class Post < ActiveRecord::Base
      include Discard::Model
      self.discard_column = :deleted_at
    end
  4. Integrate Discard with Devise

    master

    If you apply Discard to a User model, discarded users can still log in by default. To prevent discarded users from authenticating, override active_for_authentication? in your User model:

    class User < ActiveRecord::Base
      def active_for_authentication?
        super && !discarded?
      end
    end
    class User < ActiveRecord::Base
      def active_for_authentication?
        super && !discarded?
      end
    end
  5. Use Discard callbacks

    master

    You can hook into the discard/undiscard lifecycle using before_, after_, or around_ callbacks. This is useful for manually managing associated records.

    Important Callback Behaviors:

    • Validations: #discard and #undiscard use update_attribute, which skips validations.
    • State Transition: The discard column is flipped between before_ and after_ callbacks.
      • before_discard sees discarded? as false.
      • after_discard sees discarded? as true.
    class Post < ActiveRecord::Base
      include Discard::Model
      has_many :comments
    
      after_discard do
        comments.discard_all
      end
    
      after_undiscard do
        comments.undiscard_all
      end
    end
    class Post < ActiveRecord::Base
      include Discard::Model
      has_many :comments
    
      after_discard do
        comments.discard_all
      end
    
      after_undiscard do
        comments.undiscard_all
      end
    end
  6. Discard and Undiscard records

    master

    Once Discard::Model is included, you can use the following methods to manage the lifecycle of a record:

    Discarding

    • discard: Returns true if successful.
    • discard!: Raises Discard::RecordNotDiscarded if the record was not already discarded.
    • discarded?: Returns true if the record is discarded.
    • undiscarded?: Returns true if the record is kept.
    • kept?: Returns true if the record is kept.
    • discarded_at: Returns the timestamp when the record was discarded.

    Undiscarding

    • undiscard: Returns true if successful.
    • undiscard!: Raises Discard::RecordNotUndiscarded if the record was not already undiscarded.

    Scopes

    • kept: Returns only records where discarded_at is NULL.
    • discarded: Returns only records where discarded_at is NOT NULL.
    • with_discarded: Returns all records, including discarded ones.
    post = Post.first
    post.discard        # => true
    post.discarded?     # => true
    
    post.undiscard      # => true
    post.undiscarded?   # => true
  7. Discard and Undiscard individual records

    master

    You can soft-delete or restore individual records using the following methods:

    Soft-deleting a record

    • discard: Attempts to soft-delete the record. Returns true if successful, false if the record is already discarded.
    • discard!: Attempts to soft-delete the record. Raises Discard::RecordNotDiscarded if the action fails or is aborted by a before_discard callback.

    Restoring a record

    • undiscard: Attempts to restore a discarded record. Returns true if successful, false if the record is already undiscarded.
    • undiscard!: Attempts to restore a discarded record. Raises Discard::RecordNotUndiscarded if the action fails or is aborted by a before_undiscard callback.

    Checking status

    • discarded?: Returns true if the record has been discarded.
    • undiscarded? (or kept?): Returns true if the record has not been discarded.
  8. Use Discard scopes to filter records

    master

    Once Discard::Model is included in your model, you can use the following scopes to query records based on their discard status:

    • kept: Returns records that have not been discarded (alias for undiscarded).
    • undiscarded: Returns records where the discard_column is nil.
    • discarded: Returns records where the discard_column is present.
    • with_discarded: Returns all records, unreserving the discard_column filter.
  9. Discard or Undiscard collections of records

    master

    To perform bulk operations on a collection (ActiveRecord Relation), use the _all methods.

    Warning: These methods instantiate every record in the collection and call the individual discard/undiscard methods. This triggers all model callbacks and generates at least one SQL UPDATE query per record. This can be very slow for large datasets.

    For high-performance bulk updates where you do not need to run callbacks or handle associations, use standard ActiveRecord update_all instead.

    Bulk Discard

    • discard_all: Discards all records in the relation, running callbacks for each.
    • discard_all!: Discards all records in the relation, running callbacks and raising errors on failure.

    Bulk Undiscard

    • undiscard_all: Restores all discarded records in the relation, running callbacks for each.
    • undiscard_all!: Restores all discarded records in the relation, running callbacks and raising errors on failure.
  10. Handle Discard::RecordNotDiscarded errors

    master
    The Discard::RecordNotDiscarded error is raised by Discard::Model#discard! when an attempt is made to discard a record that is not currently active (i.e., it is already discarded). You can access the problematic record via the record attribute on the error object.