discard-1.4.0/ 0000755 0000041 0000041 00000000000 15145704354 013165 5 ustar www-data www-data discard-1.4.0/bin/ 0000755 0000041 0000041 00000000000 15145704354 013735 5 ustar www-data www-data discard-1.4.0/bin/setup 0000755 0000041 0000041 00000000203 15145704354 015016 0 ustar www-data www-data #!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
discard-1.4.0/bin/console 0000755 0000041 0000041 00000000526 15145704354 015330 0 ustar www-data www-data #!/usr/bin/env ruby
require "bundler/setup"
require "discard"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)
discard-1.4.0/.gitignore 0000644 0000041 0000041 00000000152 15145704354 015153 0 ustar www-data www-data /.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
/spec/examples.txt
discard-1.4.0/.github/ 0000755 0000041 0000041 00000000000 15145704354 014525 5 ustar www-data www-data discard-1.4.0/.github/dependabot.yml 0000644 0000041 0000041 00000000166 15145704354 017360 0 ustar www-data www-data version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
discard-1.4.0/.github/workflows/ 0000755 0000041 0000041 00000000000 15145704354 016562 5 ustar www-data www-data discard-1.4.0/.github/workflows/test.yml 0000644 0000041 0000041 00000002516 15145704354 020270 0 ustar www-data www-data name: Test
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
name: Test on Rails ${{ matrix.rails_version }} and Ruby ${{ matrix.ruby_version }}
strategy:
fail-fast: false
matrix:
include:
- rails_version: 8.0.0.rc1
ruby_version: '3.3'
sqlite_version: ~> 2.0
- rails_version: ~> 7.2.0
ruby_version: '3.3'
sqlite_version: ~> 2.0
- rails_version: ~> 7.2.0
ruby_version: '3.2'
sqlite_version: ~> 2.0
- rails_version: ~> 7.2.0
ruby_version: '3.1'
sqlite_version: ~> 2.0
- rails_version: ~> 7.1.0
ruby_version: '3.3'
sqlite_version: ~> 1.0
- rails_version: ~> 7.0.0
ruby_version: '3.2'
sqlite_version: ~> 1.0
- rails_version: ~> 6.1.0
ruby_version: '3.0'
sqlite_version: ~> 1.0
env:
RAILS_VERSION: ${{ matrix.rails_version }}
SQLITE_VERSION: ${{ matrix.sqlite_version }}
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby_version }}
- name: Bundle install
run: bundle install
- name: Test
run: bundle exec rake
discard-1.4.0/.github/ISSUE_TEMPLATE/ 0000755 0000041 0000041 00000000000 15145704354 016710 5 ustar www-data www-data discard-1.4.0/.github/ISSUE_TEMPLATE/bug-report.md 0000644 0000041 0000041 00000001066 15145704354 021323 0 ustar www-data www-data ---
name: Bug Report
about: Is Discard not working correctly for you? Let us know!
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Please tells us your Rails and Ruby versions, and anything else that might be helpful about the environment you encountered the issue.
discard-1.4.0/.github/ISSUE_TEMPLATE/feature-proposal.md 0000644 0000041 0000041 00000000741 15145704354 022524 0 ustar www-data www-data ---
name: Feature Proposal
about: Discard is feature-complete, but we're happy to hear you out!
title: ''
labels: ''
assignees: ''
---
**Discard is feature-complete, but if you have an idea for a feature that will benefit all discard users and that won't offer a significant maintenance burden, we're happy to listen. Explain it as best you can and we'll let you know if it's something we'd like to have or if it's something that might be a better off as an extension or fork.**
discard-1.4.0/lib/ 0000755 0000041 0000041 00000000000 15145704354 013733 5 ustar www-data www-data discard-1.4.0/lib/discard/ 0000755 0000041 0000041 00000000000 15145704354 015344 5 ustar www-data www-data discard-1.4.0/lib/discard/model.rb 0000644 0000041 0000041 00000014110 15145704354 016766 0 ustar www-data www-data # frozen_string_literal: true
module Discard
# Handles soft deletes of records.
#
# Options:
#
# - :discard_column - The columns used to track soft delete, defaults to `:discarded_at`.
module Model
extend ActiveSupport::Concern
included do
class_attribute :discard_column
self.discard_column = :discarded_at
scope :kept, ->{ undiscarded }
scope :undiscarded, ->{ where(discard_column => nil) }
scope :discarded, ->{ where.not(discard_column => nil) }
scope :with_discarded, ->{ unscope(where: discard_column) }
define_model_callbacks :discard
define_model_callbacks :undiscard
end
# :nodoc:
module ClassMethods
# Discards the records by instantiating each
# record and calling its {#discard} method.
# Each object's callbacks are executed.
# Returns the collection of objects that were discarded.
#
# Note: Instantiation, callback execution, and update of each
# record can be time consuming when you're discarding many records at
# once. It generates at least one SQL +UPDATE+ query per record (or
# possibly more, to enforce your callbacks). If you want to discard many
# rows quickly, without concern for their associations or callbacks, use
# #update_all(discarded_at: Time.current) instead.
#
# ==== Examples
#
# Person.where(age: 0..18).discard_all
def discard_all
kept.each(&:discard)
end
# Discards the records by instantiating each
# record and calling its {#discard!} method.
# Each object's callbacks are executed.
# Returns the collection of objects that were discarded.
#
# Note: Instantiation, callback execution, and update of each
# record can be time consuming when you're discarding many records at
# once. It generates at least one SQL +UPDATE+ query per record (or
# possibly more, to enforce your callbacks). If you want to discard many
# rows quickly, without concern for their associations or callbacks, use
# #update_all!(discarded_at: Time.current) instead.
#
# ==== Examples
#
# Person.where(age: 0..18).discard_all!
def discard_all!
kept.each(&:discard!)
end
# Undiscards the records by instantiating each
# record and calling its {#undiscard} method.
# Each object's callbacks are executed.
# Returns the collection of objects that were undiscarded.
#
# Note: Instantiation, callback execution, and update of each
# record can be time consuming when you're undiscarding many records at
# once. It generates at least one SQL +UPDATE+ query per record (or
# possibly more, to enforce your callbacks). If you want to undiscard many
# rows quickly, without concern for their associations or callbacks, use
# #update_all(discarded_at: nil) instead.
#
# ==== Examples
#
# Person.where(age: 0..18).undiscard_all
def undiscard_all
discarded.each(&:undiscard)
end
# Undiscards the records by instantiating each
# record and calling its {#undiscard!} method.
# Each object's callbacks are executed.
# Returns the collection of objects that were undiscarded.
#
# Note: Instantiation, callback execution, and update of each
# record can be time consuming when you're undiscarding many records at
# once. It generates at least one SQL +UPDATE+ query per record (or
# possibly more, to enforce your callbacks). If you want to undiscard many
# rows quickly, without concern for their associations or callbacks, use
# #update_all!(discarded_at: nil) instead.
#
# ==== Examples
#
# Person.where(age: 0..18).undiscard_all!
def undiscard_all!
discarded.each(&:undiscard!)
end
end
# @return [Boolean] true if this record has been discarded, otherwise false
def discarded?
self[self.class.discard_column].present?
end
# @return [Boolean] false if this record has been discarded, otherwise true
def undiscarded?
!discarded?
end
alias kept? undiscarded?
# Discard the record in the database
#
# @return [Boolean] true if successful, otherwise false
def discard
return false if discarded?
run_callbacks(:discard) do
update_attribute(self.class.discard_column, Time.current)
end
end
# Discard the record in the database
#
# There's a series of callbacks associated with #discard!. If the
# before_discard callback throws +:abort+ the action is cancelled
# and #discard! raises {Discard::RecordNotDiscarded}.
#
# @return [Boolean] true if successful
# @raise {Discard::RecordNotDiscarded}
def discard!
discard || _raise_record_not_discarded
end
# Undiscard the record in the database
#
# @return [Boolean] true if successful, otherwise false
def undiscard
return false unless discarded?
run_callbacks(:undiscard) do
update_attribute(self.class.discard_column, nil)
end
end
# Undiscard the record in the database
#
# There's a series of callbacks associated with #undiscard!. If the
# before_undiscard callback throws +:abort+ the action is cancelled
# and #undiscard! raises {Discard::RecordNotUndiscarded}.
#
# @return [Boolean] true if successful
# @raise {Discard::RecordNotUndiscarded}
def undiscard!
undiscard || _raise_record_not_undiscarded
end
private
def _raise_record_not_discarded
raise ::Discard::RecordNotDiscarded.new(discarded_fail_message, self)
end
def _raise_record_not_undiscarded
raise ::Discard::RecordNotUndiscarded.new(undiscarded_fail_message, self)
end
def discarded_fail_message
return "A discarded record cannot be discarded" if discarded?
"Failed to discard the record"
end
def undiscarded_fail_message
return "An undiscarded record cannot be undiscarded" if undiscarded?
"Failed to undiscard the record"
end
end
end
discard-1.4.0/lib/discard/version.rb 0000644 0000041 0000041 00000000141 15145704354 017352 0 ustar www-data www-data # frozen_string_literal: true
module Discard
# Discard version
VERSION = "1.4.0".freeze
end
discard-1.4.0/lib/discard/errors.rb 0000644 0000041 0000041 00000001103 15145704354 017200 0 ustar www-data www-data # frozen_string_literal: true
module Discard
# = Discard Errors
#
# Generic exception class.
class DiscardError < StandardError
end
# Raised by {Discard::Model#discard!}
class RecordNotDiscarded < DiscardError
attr_reader :record
def initialize(message = nil, record = nil)
@record = record
super(message)
end
end
# Raised by {Discard::Model#undiscard!}
class RecordNotUndiscarded < DiscardError
attr_reader :record
def initialize(message = nil, record = nil)
@record = record
super(message)
end
end
end
discard-1.4.0/lib/discard.rb 0000644 0000041 0000041 00000000203 15145704354 015664 0 ustar www-data www-data # frozen_string_literal: true
require "active_record"
require "discard/version"
require "discard/errors"
require "discard/model"
discard-1.4.0/discard.gemspec 0000644 0000041 0000041 00000002340 15145704354 016142 0 ustar www-data www-data # frozen_string_literal: true
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'discard/version'
Gem::Specification.new do |spec|
spec.name = "discard"
spec.version = Discard::VERSION
spec.authors = ["John Hawthorn"]
spec.email = ["john.hawthorn@gmail.com"]
spec.summary = %q{ActiveRecord soft-deletes done right}
spec.description = %q{Allows marking ActiveRecord objects as discarded, and provides scopes for filtering.}
spec.homepage = "https://github.com/jhawthorn/discard"
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_dependency "activerecord", ">= 4.2", "< 9.0"
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake", ">= 10.0"
spec.add_development_dependency "rspec", "~> 3.5.0"
spec.add_development_dependency "database_cleaner", "~> 1.5"
spec.add_development_dependency "with_model", "~> 2.0"
spec.add_development_dependency "sqlite3"
end
discard-1.4.0/LICENSE.txt 0000644 0000041 0000041 00000002070 15145704354 015007 0 ustar www-data www-data The MIT License (MIT)
Copyright (c) 2017 John Hawthorn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
discard-1.4.0/.yardopts 0000644 0000041 0000041 00000000154 15145704354 015033 0 ustar www-data www-data --protected
--no-private
--embed-mixin ClassMethods
-
README.md
CHANGELOG.md
CODE_OF_CONDUCT.md
LICENSE.txt
discard-1.4.0/.rspec 0000644 0000041 0000041 00000000036 15145704354 014301 0 ustar www-data www-data --color
--require spec_helper
discard-1.4.0/Rakefile 0000644 0000041 0000041 00000000246 15145704354 014634 0 ustar www-data www-data require 'bundler/setup'
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:rspec)
desc 'Run the test suite'
task default: :rspec
discard-1.4.0/CODE_OF_CONDUCT.md 0000644 0000041 0000041 00000006237 15145704354 015774 0 ustar www-data www-data # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at john.hawthorn@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
discard-1.4.0/Gemfile 0000644 0000041 0000041 00000000363 15145704354 014462 0 ustar www-data www-data source 'https://rubygems.org'
rails_version = ENV['RAILS_VERSION']
gem 'activerecord', rails_version
if sqlite_version = ENV['SQLITE_VERSION']
gem 'sqlite3', sqlite_version
end
# Specify your gem's dependencies in discard.gemspec
gemspec
discard-1.4.0/README.md 0000644 0000041 0000041 00000023160 15145704354 014446 0 ustar www-data www-data # Discard [](https://github.com/jhawthorn/discard/actions/workflows/test.yml)
Soft deletes for ActiveRecord done right.
## What does this do?
A simple ActiveRecord mixin to add conventions for flagging records as discarded.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'discard', '~> 1.4'
```
And then execute:
$ bundle
## Usage
**Declare a record as discardable**
Declare the record as being discardable
``` ruby
class Post < ActiveRecord::Base
include Discard::Model
end
```
You can either generate a migration using:
```
rails generate migration add_discarded_at_to_posts discarded_at:datetime:index
```
or create one yourself like the one below:
``` ruby
class AddDiscardToPosts < ActiveRecord::Migration[5.0]
def change
add_column :posts, :discarded_at, :datetime
add_index :posts, :discarded_at
end
end
```
#### Discard a record
```ruby
Post.all # => [#]
Post.kept # => [#]
Post.discarded # => []
post = Post.first # => #
post.discard # => true
post.discard! # => Discard::RecordNotDiscarded: Failed to discard the record
post.discarded? # => true
post.undiscarded? # => false
post.kept? # => false
post.discarded_at # => 2017-04-18 18:49:49 -0700
Post.all # => [#]
Post.kept # => []
Post.discarded # => [#]
```
***From a controller***
Controller actions need a small modification to discard records instead of deleting them. Just replace `destroy` with `discard`.
``` ruby
def destroy
@post.discard
redirect_to users_url, notice: "Post removed"
end
```
#### Undiscard a record
```ruby
post = Post.first # => #
post.undiscard # => true
post.undiscard! # => Discard::RecordNotUndiscarded: Failed to undiscard the record
post.discarded_at # => nil
```
***From a controller***
```ruby
def update
@post.undiscard
redirect_to users_url, notice: "Post undiscarded"
end
```
#### Working with associations
Under paranoia, soft deleting a record will destroy any `dependent: :destroy`
associations. Probably not what you want! This leads to all dependent records
also needing to be `acts_as_paranoid`, which makes restoring awkward: paranoia
handles this by restoring any records which have their deleted_at set to a
similar timestamp. Also, it doesn't always make sense to mark these records as
deleted, it depends on the application.
A better approach is to simply mark the one record as discarded, and use SQL
joins to restrict finding these if that's desired.
For example, in a blog comment system, with `Post`s and `Comment`s, you might
want to discard the records independently. A user's comment history could
include comments on deleted posts.
``` ruby
Post.kept # SELECT * FROM posts WHERE discarded_at IS NULL
Comment.kept # SELECT * FROM comments WHERE discarded_at IS NULL
```
Or you could decide that comments are dependent on their posts not being
discarded. Just override the `kept` scope on the Comment model.
``` ruby
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
Comment.kept
# SELECT * FROM comments
# INNER JOIN posts ON comments.post_id = posts.id
# WHERE
# comments.discarded_at IS NULL AND
# posts.discarded_at IS NULL
```
SQL databases are very good at this, and performance should not be an issue.
In both of these cases restoring either of these records will do right thing!
#### Default scope
It's usually undesirable to add a default scope. It will take more effort to
work around and will cause more headaches. If you know you need a default scope, it's easy to add yourself ❤.
``` ruby
class Post < ActiveRecord::Base
include Discard::Model
default_scope -> { kept }
end
Post.all # Only kept posts
Post.with_discarded # All Posts
Post.with_discarded.discarded # Only discarded posts
```
#### Custom column
If you're migrating from paranoia, you might want to continue using the same
column.
``` ruby
class Post < ActiveRecord::Base
include Discard::Model
self.discard_column = :deleted_at
end
```
#### Callbacks
Callbacks can be run before, after, or around the discard and undiscard operations.
A likely use is discarding or deleting associated records (but see "Working with associations" for an alternative).
``` ruby
class Comment < ActiveRecord::Base
include Discard::Model
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
```
*Warning:* Please note that callbacks for save and update are run when discarding/undiscarding a record
#### Performance tuning
`discard_all` and `undiscard_all` is intended to behave like `destroy_all` which has callbacks, validations, and does one query per record. If performance is a big concern, you may consider replacing it with:
`scope.update_all(discarded_at: Time.current)`
or
`scope.update_all(discarded_at: nil)`
#### Working with Devise
A common use case is to apply discard to a User record. Even though a user has been discarded they can still login and continue their session.
If you are using Devise and wish for discarded users to be unable to login and stop their session you can override Devise's method.
```ruby
class User < ActiveRecord::Base
def active_for_authentication?
super && !discarded?
end
end
```
## Non-features
* Special handling of AR counter cache columns - The counter cache counts the total number of records, both kept and discarded.
* Recursive discards (like AR's dependent: destroy) - This can be avoided using queries (See "Working with associations") or emulated using callbacks.
* Recursive restores - This concept is fundamentally broken, but not necessary if the recursive discards are avoided.
## Extensions
Discard provides the smallest subset of soft-deletion features that we think are useful to all users of the gem. We welcome the addition of gems that work with Discard to provide additional features.
- [discard-rails-observers](https://github.com/pelargir/discard-rails-observers) integrates discard with the [rails-observers gem](https://github.com/rails/rails-observers)
## Why not paranoia or acts_as_paranoid?
I've worked with and have helped maintain
[paranoia](https://github.com/rubysherpas/paranoia) for a while. I'm convinced
it does the wrong thing for most cases.
Paranoia and
[acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) both
attempt to emulate deletes by setting a column and adding a default scope on the
model. This requires some ActiveRecord hackery, and leads to some surprising
and awkward behaviour.
* A default scope is added to hide soft-deleted records, which necessitates
adding `.with_deleted` to associations or anywhere soft-deleted records
should be found. :disappointed:
* Adding `belongs_to :child, -> { with_deleted }` helps, but doesn't work for
joins and eager-loading [before Rails 5.2](https://github.com/rubysherpas/paranoia/issues/355)
* `delete` is overridden (`really_delete` will actually delete the record) :unamused:
* `destroy` is overridden (`really_destroy` will actually delete the record) :pensive:
* `dependent: :destroy` associations are deleted when performing soft-destroys :scream:
* requiring any dependent records to also be `acts_as_paranoid` to avoid losing data. :grimacing:
There are some use cases where these behaviours make sense: if you really did
want to _almost_ delete the record. More often developers are just looking to
hide some records, or mark them as inactive.
Discard takes a different approach. It doesn't override any ActiveRecord
methods and instead simply provides convenience methods and scopes for
discarding (hiding), restoring, and querying records.
You can find more information about the history and purpose of Discard in [this blog post](https://supergood.software/introduction-to-discard/).
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
## Contributing
Please consider filing an issue with the details of any features you'd like to see before implementing them. Discard is feature-complete and we are only interested in adding additional features that won't require substantial maintenance burden and that will benefit all users of the gem. We encourage anyone that needs additional or different behaviour to either create their own gem that builds off of discard or implement a new package with the different behaviour.
Discard is very simple and we like it that way. Creating your own clone or fork with slightly different behaviour may not be that much work!
If you find a bug in discard, please report it! We try to keep up with any issues and keep the gem running smoothly for everyone! You can report issues [here](https://github.com/jhawthorn/discard/issues).
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
## Acknowledgments
* [Ben Morgan](https://github.com/BenMorganIO) who has done a great job maintaining paranoia
* [Ryan Bigg](http://github.com/radar), the original author of paranoia (and many things), as a simpler replacement of acts_as_paranoid
* All paranoia users and contributors
discard-1.4.0/CHANGELOG.md 0000644 0000041 0000041 00000001672 15145704354 015004 0 ustar www-data www-data ### Unreleased
### Version 1.4.0
Release date: 2024-11-05
* Support Rails 8.0 and 8.1 (#110, #111)
* More descriptive error messages (#108)
### Version 1.3.0
Release date: 2023-08-17
* Fix `undiscard` so it returns false instead of nil when the record isn't
discarded (#95, #96)
### Version 1.2.1
Release date: 2021-12-16
* Support for ActiveRecord 7
### Version 1.2.0
Release date: 2020-02-17
* Add `discard_all!` and `undiscard_all!`
* Add `undiscarded?` and `kept?` to match the scopes of the same names
### Version 1.1.0
Release date: 2019-05-03
* Support for ActiveRecord 6
* `discard_all` and `undiscard_all` now return affected records
* Add `discard!` and `undiscard!`
### Version 1.0.0
Release date: 2018-03-16
* Add undiscard callbacks and `.undiscard_all`
### Version 0.2.0
Release date: 2017-11-22
* Add `.discard_all`
* Add `undiscarded` scope
* Add callbacks
### Version 0.1.0
Release date: 2017-04-28
* Initial version!