{"id":13484314,"url":"https://github.com/ActsAsParanoid/acts_as_paranoid","last_synced_at":"2025-03-27T16:30:40.403Z","repository":{"id":385093,"uuid":"2376","full_name":"ActsAsParanoid/acts_as_paranoid","owner":"ActsAsParanoid","description":"ActiveRecord plugin allowing you to hide and restore records without actually deleting them.","archived":false,"fork":false,"pushed_at":"2024-10-10T14:47:02.000Z","size":688,"stargazers_count":1470,"open_issues_count":47,"forks_count":192,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-10-29T11:37:37.355Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ActsAsParanoid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2008-03-04T04:48:18.000Z","updated_at":"2024-10-29T11:29:16.000Z","dependencies_parsed_at":"2023-07-05T14:44:50.467Z","dependency_job_id":"70d1ad21-9b27-4abe-beab-fcc93bacc346","html_url":"https://github.com/ActsAsParanoid/acts_as_paranoid","commit_stats":{"total_commits":647,"total_committers":83,"mean_commits":7.795180722891566,"dds":0.6537867078825348,"last_synced_commit":"a785c749ef46d85e95ff4f2597c276514305122a"},"previous_names":["technoweenie/acts_as_paranoid"],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActsAsParanoid%2Facts_as_paranoid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActsAsParanoid%2Facts_as_paranoid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActsAsParanoid%2Facts_as_paranoid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActsAsParanoid%2Facts_as_paranoid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ActsAsParanoid","download_url":"https://codeload.github.com/ActsAsParanoid/acts_as_paranoid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245526517,"owners_count":20629837,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:01:22.345Z","updated_at":"2025-03-27T16:30:40.391Z","avatar_url":"https://github.com/ActsAsParanoid.png","language":"Ruby","funding_links":[],"categories":["ORM/ODM Extensions","Ruby","ActiveRecord"],"sub_categories":[],"readme":"# ActsAsParanoid\n\n[![CI](https://github.com/ActsAsParanoid/acts_as_paranoid/actions/workflows/ruby.yml/badge.svg)](https://github.com/ActsAsParanoid/acts_as_paranoid/actions/workflows/ruby.yml)\n\nA Rails plugin to add soft delete.\n\nThis gem can be used to hide records instead of deleting them, making them\nrecoverable later.\n\n## Support\n\n**This version targets Rails 6.1+ and Ruby 3.0+ only**\n\nIf you're working with Rails 6.0 and earlier, or with Ruby 2.7 or earlier,\nplease require an older version of the `acts_as_paranoid` gem.\n\n### Known issues\n\n* Using `acts_as_paranoid` and ActiveStorage on the same model\n  [leads to a SystemStackError](https://github.com/ActsAsParanoid/acts_as_paranoid/issues/103).\n* You cannot directly create a model in a deleted state, or update a model\n  after it's been deleted.\n\n## Usage\n\n#### Install gem\n\n```ruby\ngem \"acts_as_paranoid\", \"~\u003e 0.10.3\"\n```\n\n```shell\nbundle install\n```\n\n#### Create migration\n\n```shell\nbin/rails generate migration AddDeletedAtToParanoiac deleted_at:datetime:index\n```\n\n#### Enable ActsAsParanoid\n\n```ruby\nclass Paranoiac \u003c ActiveRecord::Base\n  acts_as_paranoid\nend\n```\n\nBy default, ActsAsParanoid assumes a record's *deletion* is stored in a\n`datetime` column called `deleted_at`.\n\n### Options\n\nIf you are using a different column name and type to store a record's\n*deletion*, you can specify them as follows:\n\n- `column:      'deleted'`\n- `column_type: 'boolean'`\n\nWhile *column* can be anything (as long as it exists in your database), *type*\nis restricted to:\n\n- `boolean`\n- `time` or\n- `string`\n\nNote that the `time` type corresponds to the database column type `datetime`\nin your Rails migrations and schema.\n\nIf your column type is a `string`, you can also specify which value to use when\nmarking an object as deleted by passing `:deleted_value` (default is\n\"deleted\"). Any records with a non-matching value in this column will be\ntreated normally, i.e., as not deleted.\n\nIf your column type is a `boolean`, it is possible to specify `allow_nulls`\noption which is `true` by default. When set to `false`, entities that have\n`false` value in this column will be considered not deleted, and those which\nhave `true` will be considered deleted. When `true` everything that has a\nnot-null value will be considered deleted.\n\n### Filtering\n\nIf a record is deleted by ActsAsParanoid, it won't be retrieved when accessing\nthe database.\n\nSo, `Paranoiac.all` will **not** include the **deleted records**.\n\nWhen you want to access them, you have 2 choices:\n\n```ruby\nParanoiac.only_deleted # retrieves only the deleted records\nParanoiac.with_deleted # retrieves all records, deleted or not\n```\n\nWhen using the default `column_type` of `'time'`, the following extra scopes\nare provided:\n\n```ruby\ntime = Time.now\n\nParanoiac.deleted_after_time(time)\nParanoiac.deleted_before_time(time)\n\n# Or roll it all up and get a nice window:\nParanoiac.deleted_inside_time_window(time, 2.minutes)\n```\n\n### Real deletion\n\nIn order to really delete a record, just use:\n\n```ruby\nparanoiac.destroy_fully!\nParanoiac.delete_all!(conditions)\n```\n\n**NOTE:** The `.destroy!` method is still usable, but equivalent to `.destroy`.\nIt just hides the object.\n\nAlternatively you can permanently delete a record by calling `destroy` or\n`delete_all` on the object **twice**.\n\nIf a record was already deleted (hidden by `ActsAsParanoid`) and you delete it\nagain, it will be removed from the database.\n\nTake this example:\n\n```ruby\np = Paranoiac.first\n\n# does NOT delete the first record, just hides it\np.destroy\n\n# deletes the first record from the database\nParanoiac.only_deleted.where(id: p.id).first.destroy\n```\n\nThis behaviour can be disabled by setting the configuration option. In a future\nversion, `false` will be the default setting.\n\n- `double_tap_destroys_fully: false`\n\n### Recovery\n\nRecovery is easy. Just invoke `recover` on it, like this:\n\n```ruby\nParanoiac.only_deleted.where(\"name = ?\", \"not dead yet\").first.recover\n```\n\nAll associations marked as `dependent: :destroy` are also recursively recovered.\n\nIf you would like to disable this behavior, you can call `recover` with the\n`recursive` option:\n\n```ruby\nParanoiac.only_deleted.where(\"name = ?\", \"not dead yet\").first.recover(recursive: false)\n```\n\nIf you would like to change this default behavior for one model, you can use\nthe `recover_dependent_associations` option\n\n```ruby\nclass Paranoiac \u003c ActiveRecord::Base\n  acts_as_paranoid recover_dependent_associations: false\nend\n```\n\nBy default, dependent records will be recovered if they were deleted within 2\nminutes of the object upon which they depend.\n\nThis restores the objects to the state before the recursive deletion without\nrestoring other objects that were deleted earlier.\n\nThe behavior is only available when both parent and dependant are using\ntimestamp fields to mark deletion, which is the default behavior.\n\nThis window can be changed with the `dependent_recovery_window` option:\n\n```ruby\nclass Paranoiac \u003c ActiveRecord::Base\n  acts_as_paranoid\n  has_many :paranoids, dependent: :destroy\nend\n\nclass Paranoid \u003c ActiveRecord::Base\n  belongs_to :paranoic\n\n  # Paranoid objects will be recovered alongside Paranoic objects\n  # if they were deleted within 10 minutes of the Paranoic object\n  acts_as_paranoid dependent_recovery_window: 10.minutes\nend\n```\n\nor in the recover statement\n\n```ruby\nParanoiac.only_deleted.where(\"name = ?\", \"not dead yet\").first\n  .recover(recovery_window: 30.seconds)\n```\n\n### recover!\n\nYou can invoke `recover!` if you wish to raise an error if the recovery fails.\nThe error generally stems from ActiveRecord.\n\n```ruby\nParanoiac.only_deleted.where(\"name = ?\", \"not dead yet\").first.recover!\n# =\u003e ActiveRecord::RecordInvalid: Validation failed: Name already exists\n```\n\nOptionally, you may also raise the error by passing `raise_error: true` to the\n`recover` method. This behaves the same as `recover!`.\n\n```ruby\nParanoiac.only_deleted.where(\"name = ?\", \"not dead yet\").first.recover(raise_error: true)\n```\n\n### Validation\n\nActiveRecord's built-in uniqueness validation does not account for records\ndeleted by ActsAsParanoid. If you want to check for uniqueness among\nnon-deleted records only, use the macro `validates_as_paranoid` in your model.\nThen, instead of using `validates_uniqueness_of`, use\n`validates_uniqueness_of_without_deleted`. This will keep deleted records from\ncounting against the uniqueness check.\n\n```ruby\nclass Paranoiac \u003c ActiveRecord::Base\n  acts_as_paranoid\n  validates_as_paranoid\n  validates_uniqueness_of_without_deleted :name\nend\n\np1 = Paranoiac.create(name: 'foo')\np1.destroy\n\np2 = Paranoiac.new(name: 'foo')\np2.valid? #=\u003e true\np2.save\n\np1.recover #=\u003e fails validation!\n```\n\n### Status\n\nA paranoid object could be deleted or destroyed fully.\n\nYou can check if the object is deleted with the `deleted?` helper\n\n```ruby\nParanoiac.create(name: 'foo').destroy\nParanoiac.with_deleted.first.deleted? #=\u003e true\n```\n\nAfter the first call to `.destroy` the object is `deleted?`.\n\nYou can check if the object is fully destroyed with `destroyed_fully?` or `deleted_fully?`.\n\n```ruby\nParanoiac.create(name: 'foo').destroy\nParanoiac.with_deleted.first.deleted? #=\u003e true\nParanoiac.with_deleted.first.destroyed_fully? #=\u003e false\np1 = Paranoiac.with_deleted.first\np1.destroy # this fully destroys the object\np1.destroyed_fully? #=\u003e true\np1.deleted_fully? #=\u003e true\n```\n\n### Scopes\n\nAs you've probably guessed, `with_deleted` and `only_deleted` are scopes. You\ncan, however, chain them freely with other scopes you might have.\n\nFor example:\n\n```ruby\nParanoiac.pretty.with_deleted\n```\n\nThis is exactly the same as:\n\n```ruby\nParanoiac.with_deleted.pretty\n```\n\nYou can work freely with scopes and it will just work:\n\n```ruby\nclass Paranoiac \u003c ActiveRecord::Base\n  acts_as_paranoid\n  scope :pretty, where(pretty: true)\nend\n\nParanoiac.create(pretty: true)\n\nParanoiac.pretty.count #=\u003e 1\nParanoiac.only_deleted.count #=\u003e 0\nParanoiac.pretty.only_deleted.count #=\u003e 0\n\nParanoiac.first.destroy\n\nParanoiac.pretty.count #=\u003e 0\nParanoiac.only_deleted.count #=\u003e 1\nParanoiac.pretty.only_deleted.count #=\u003e 1\n```\n\n### Associations\n\nAssociations are also supported.\n\nFrom the simplest behaviors you'd expect to more nifty things like the ones\nmentioned previously or the usage of the `:with_deleted` option with\n`belongs_to`\n\n```ruby\nclass Parent \u003c ActiveRecord::Base\n  has_many :children, class_name: \"ParanoiacChild\"\nend\n\nclass ParanoiacChild \u003c ActiveRecord::Base\n  acts_as_paranoid\n  belongs_to :parent\n\n  # You may need to provide a foreign_key like this\n  belongs_to :parent_including_deleted, class_name: \"Parent\",\n    foreign_key: 'parent_id', with_deleted: true\nend\n\nparent = Parent.first\nchild = parent.children.create\nparent.destroy\n\nchild.parent #=\u003e nil\nchild.parent_including_deleted #=\u003e Parent (it works!)\n```\n\n### Callbacks\n\nThere are couple of callbacks that you may use when dealing with deletion and\nrecovery of objects. There is `before_recover` and `after_recover` which will\nbe triggered before and after the recovery of an object respectively.\n\nDefault ActiveRecord callbacks such as `before_destroy` and `after_destroy` will\nbe triggered around `.destroy!` and `.destroy_fully!`.\n\n```ruby\nclass Paranoiac \u003c ActiveRecord::Base\n  acts_as_paranoid\n\n  before_recover :set_counts\n  after_recover :update_logs\nend\n```\n\n## Caveats\n\nWatch out for these caveats:\n\n- You cannot use scopes named `with_deleted` and `only_deleted`\n- You cannot use scopes named `deleted_inside_time_window`,\n  `deleted_before_time`, `deleted_after_time` **if** your paranoid column's\n  type is `time`\n- You cannot name association `*_with_deleted`\n- `unscoped` will return all records, deleted or not\n\n# Acknowledgements\n\n* To [Rick Olson](https://github.com/technoweenie) for creating `acts_as_paranoid`\n* To [cheerfulstoic](https://github.com/cheerfulstoic) for adding recursive recovery\n* To [Jonathan Vaught](https://github.com/gravelpup) for adding paranoid validations\n* To [Geoffrey Hichborn](https://github.com/phene) for improving the overral code quality and adding support for after_commit\n* To [flah00](https://github.com/flah00) for adding support for STI-based associations (with :dependent)\n* To [vikramdhillon](https://github.com/vikramdhillon) for the idea and initial implementation of support for string column type\n* To [Craig Walker](https://github.com/softcraft-development) for Rails 3.1 support and fixing various pending issues\n* To [Charles G.](https://github.com/chuckg) for Rails 3.2 support and for making a desperately needed global code refactoring\n* To [Gonçalo Silva](https://github.com/goncalossilva) for supporting this gem prior to v0.4.3\n* To [Jean Boussier](https://github.com/byroot) for initial Rails 4.0.0 support\n* To [Matijs van Zuijlen](https://github.com/mvz) for Rails 4.1 and 4.2 support\n* To [Andrey Ponomarenko](https://github.com/sjke) for Rails 5 support\n* To [Daniel Rice](https://github.com/danielricecodes), [Josh Bryant](https://github.com/jbryant92), and [Romain Alexandre](https://github.com/RomainAlexandre) for Rails 6.0 support.\n\nSee `LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FActsAsParanoid%2Facts_as_paranoid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FActsAsParanoid%2Facts_as_paranoid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FActsAsParanoid%2Facts_as_paranoid/lists"}