[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7990":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":15,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":14,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":25,"readmeContent":26,"aiSummary":27,"trendingCount":16,"starSnapshotCount":16,"syncStatus":28,"lastSyncTime":29,"discoverSource":30},7990,"discard","jhawthorn\u002Fdiscard","jhawthorn","🃏🗑 Soft deletes for ActiveRecord done right","",null,"Ruby",2413,94,12,6,0,3,22,69.13,"MIT License",false,"master",true,[],"2026-06-12 04:00:36","# Discard [![Test](https:\u002F\u002Fgithub.com\u002Fjhawthorn\u002Fdiscard\u002Factions\u002Fworkflows\u002Ftest.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fjhawthorn\u002Fdiscard\u002Factions\u002Fworkflows\u002Ftest.yml)\n\nSoft deletes for ActiveRecord done right.\n\n\u003Cimg src=\"http:\u002F\u002Fi.hawth.ca\u002Fu\u002Fron-swanson-computer-trash.gif\" width=\"800\" \u002F>\n\n## What does this do?\n\nA simple ActiveRecord mixin to add conventions for flagging records as discarded.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'discard', '~> 1.4'\n```\n\nAnd then execute:\n\n    $ bundle\n\n## Usage\n\n**Declare a record as discardable**\n\nDeclare the record as being discardable\n\n``` ruby\nclass Post \u003C ActiveRecord::Base\n  include Discard::Model\nend\n```\n\nYou can either generate a migration using:\n```\nrails generate migration add_discarded_at_to_posts discarded_at:datetime:index\n```\n\nor create one yourself like the one below:\n``` ruby\nclass AddDiscardToPosts \u003C ActiveRecord::Migration[5.0]\n  def change\n    add_column :posts, :discarded_at, :datetime\n    add_index :posts, :discarded_at\n  end\nend\n```\n\n\n#### Discard a record\n\n```ruby\nPost.all             # => [#\u003CPost id: 1, ...>]\nPost.kept            # => [#\u003CPost id: 1, ...>]\nPost.discarded       # => []\n\npost = Post.first   # => #\u003CPost id: 1, ...>\npost.discard        # => true\npost.discard!       # => Discard::RecordNotDiscarded: Failed to discard the record\npost.discarded?     # => true\npost.undiscarded?   # => false\npost.kept?          # => false\npost.discarded_at   # => 2017-04-18 18:49:49 -0700\n\nPost.all             # => [#\u003CPost id: 1, ...>]\nPost.kept            # => []\nPost.discarded       # => [#\u003CPost id: 1, ...>]\n```\n\n***From a controller***\n\nController actions need a small modification to discard records instead of deleting them. Just replace `destroy` with `discard`.\n\n``` ruby\ndef destroy\n  @post.discard\n  redirect_to users_url, notice: \"Post removed\"\nend\n```\n\n\n#### Undiscard a record\n\n```ruby\npost = Post.first   # => #\u003CPost id: 1, ...>\npost.undiscard      # => true\npost.undiscard!     # => Discard::RecordNotUndiscarded: Failed to undiscard the record\npost.discarded_at   # => nil\n```\n\n***From a controller***\n\n```ruby\ndef update\n  @post.undiscard\n  redirect_to users_url, notice: \"Post undiscarded\"\nend\n```\n\n#### Working with associations\n\nUnder paranoia, soft deleting a record will destroy any `dependent: :destroy`\nassociations. Probably not what you want! This leads to all dependent records\nalso needing to be `acts_as_paranoid`, which makes restoring awkward: paranoia\nhandles this by restoring any records which have their deleted_at set to a\nsimilar timestamp. Also, it doesn't always make sense to mark these records as\ndeleted, it depends on the application.\n\nA better approach is to simply mark the one record as discarded, and use SQL\njoins to restrict finding these if that's desired.\n\nFor example, in a blog comment system, with `Post`s and `Comment`s, you might\nwant to discard the records independently. A user's comment history could\ninclude comments on deleted posts.\n\n``` ruby\nPost.kept # SELECT * FROM posts WHERE discarded_at IS NULL\nComment.kept # SELECT * FROM comments WHERE discarded_at IS NULL\n```\n\nOr you could decide that comments are dependent on their posts not being\ndiscarded. Just override the `kept` scope on the Comment model.\n\n``` ruby\nclass Comment \u003C ActiveRecord::Base\n  belongs_to :post\n\n  include Discard::Model\n  scope :kept, -> { undiscarded.joins(:post).merge(Post.kept) }\n\n  def kept?\n    undiscarded? && post.kept?\n  end\nend\n\nComment.kept\n# SELECT * FROM comments\n#    INNER JOIN posts ON comments.post_id = posts.id\n# WHERE\n#    comments.discarded_at IS NULL AND\n#       posts.discarded_at IS NULL\n```\n\nSQL databases are very good at this, and performance should not be an issue.\n\nIn both of these cases restoring either of these records will do right thing!\n\n\n#### Default scope\n\nIt's usually undesirable to add a default scope. It will take more effort to\nwork around and will cause more headaches. If you know you need a default scope, it's easy to add yourself ❤.\n\n``` ruby\nclass Post \u003C ActiveRecord::Base\n  include Discard::Model\n  default_scope -> { kept }\nend\n\nPost.all                       # Only kept posts\nPost.with_discarded            # All Posts\nPost.with_discarded.discarded  # Only discarded posts\n```\n\n#### Custom column\n\nIf you're migrating from paranoia, you might want to continue using the same\ncolumn.\n\n``` ruby\nclass Post \u003C ActiveRecord::Base\n  include Discard::Model\n  self.discard_column = :deleted_at\nend\n```\n\n#### Callbacks\n\nCallbacks can be run before, after, or around the discard and undiscard operations.\nA likely use is discarding or deleting associated records (but see \"Working with associations\" for an alternative).\n\n``` ruby\nclass Comment \u003C ActiveRecord::Base\n  include Discard::Model\nend\n\nclass Post \u003C ActiveRecord::Base\n  include Discard::Model\n\n  has_many :comments\n\n  after_discard do\n    comments.discard_all\n  end\n\n  after_undiscard do\n    comments.undiscard_all\n  end\nend\n```\n\n*Warning:* Please note that callbacks for save and update are run when discarding\u002Fundiscarding a record\n\n\n#### Performance tuning\n`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:\n\n`scope.update_all(discarded_at: Time.current)`\nor\n`scope.update_all(discarded_at: nil)`\n\n#### Working with Devise\n\nA 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.\nIf you are using Devise and wish for discarded users to be unable to login and stop their session you can override Devise's method.\n\n```ruby\nclass User \u003C ActiveRecord::Base\n  def active_for_authentication?\n    super && !discarded?\n  end\nend\n```\n\n## Non-features\n\n* Special handling of AR counter cache columns - The counter cache counts the total number of records, both kept and discarded.\n* Recursive discards (like AR's dependent: destroy) - This can be avoided using queries (See \"Working with associations\") or emulated using callbacks.\n* Recursive restores - This concept is fundamentally broken, but not necessary if the recursive discards are avoided.\n\n## Extensions\n\nDiscard 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.\n\n- [discard-rails-observers](https:\u002F\u002Fgithub.com\u002Fpelargir\u002Fdiscard-rails-observers) integrates discard with the [rails-observers gem](https:\u002F\u002Fgithub.com\u002Frails\u002Frails-observers)\n\n## Why not paranoia or acts_as_paranoid?\n\nI've worked with and have helped maintain\n[paranoia](https:\u002F\u002Fgithub.com\u002Frubysherpas\u002Fparanoia) for a while. I'm convinced\nit does the wrong thing for most cases.\n\nParanoia and\n[acts_as_paranoid](https:\u002F\u002Fgithub.com\u002FActsAsParanoid\u002Facts_as_paranoid) both\nattempt to emulate deletes by setting a column and adding a default scope on the\nmodel. This requires some ActiveRecord hackery, and leads to some surprising\nand awkward behaviour.\n\n* A default scope is added to hide soft-deleted records, which necessitates\n  adding `.with_deleted` to associations or anywhere soft-deleted records\n  should be found. :disappointed:\n  * Adding `belongs_to :child, -> { with_deleted }` helps, but doesn't work for\n    joins and eager-loading [before Rails 5.2](https:\u002F\u002Fgithub.com\u002Frubysherpas\u002Fparanoia\u002Fissues\u002F355)\n* `delete` is overridden (`really_delete` will actually delete the record) :unamused:\n* `destroy` is overridden (`really_destroy` will actually delete the record) :pensive:\n* `dependent: :destroy` associations are deleted when performing soft-destroys :scream:\n  * requiring any dependent records to also be `acts_as_paranoid` to avoid losing data. :grimacing:\n\nThere are some use cases where these behaviours make sense: if you really did\nwant to _almost_ delete the record. More often developers are just looking to\nhide some records, or mark them as inactive.\n\nDiscard takes a different approach. It doesn't override any ActiveRecord\nmethods and instead simply provides convenience methods and scopes for\ndiscarding (hiding), restoring, and querying records.\n\nYou can find more information about the history and purpose of Discard in [this blog post](https:\u002F\u002Fsupergood.software\u002Fintroduction-to-discard\u002F).\n\n## Development\n\nAfter checking out the repo, run `bin\u002Fsetup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin\u002Fconsole` for an interactive prompt that will allow you to experiment.\n\n## Contributing\n\nPlease 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.\n\nDiscard 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!\n\nIf 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:\u002F\u002Fgithub.com\u002Fjhawthorn\u002Fdiscard\u002Fissues).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http:\u002F\u002Fopensource.org\u002Flicenses\u002FMIT).\n\n## Acknowledgments\n\n* [Ben Morgan](https:\u002F\u002Fgithub.com\u002FBenMorganIO) who has done a great job maintaining paranoia\n* [Ryan Bigg](http:\u002F\u002Fgithub.com\u002Fradar), the original author of paranoia (and many things), as a simpler replacement of acts_as_paranoid\n* All paranoia users and contributors\n","Discard 是一个为 ActiveRecord 提供软删除功能的 Ruby 宝石。其核心功能是在数据库中通过添加 `discarded_at` 字段来标记已删除的记录，而不是直接从数据库中移除它们，从而实现数据的逻辑删除。技术特点包括简单易用的 API 设计，如 `discard` 和 `undiscard` 方法用于标记和恢复记录状态，并且支持与 ActiveRecord 的关联操作。此项目适用于需要保留历史数据但又希望在应用层面隐藏或排除某些记录的场景，例如博客系统中的文章和评论管理，能够有效避免误删数据带来的风险。",2,"2026-06-11 03:15:31","top_language"]