{"id":15353768,"url":"https://github.com/westonganger/paper_trail-association_tracking","last_synced_at":"2025-05-15T03:05:13.811Z","repository":{"id":32517745,"uuid":"134115034","full_name":"westonganger/paper_trail-association_tracking","owner":"westonganger","description":"Plugin for the PaperTrail gem to track and reify associations","archived":false,"fork":false,"pushed_at":"2025-04-17T20:30:24.000Z","size":234,"stargazers_count":141,"open_issues_count":8,"forks_count":39,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-15T03:04:58.376Z","etag":null,"topics":["activerecord","associations","paper-trail","paper-trail-plugin","rails","ruby"],"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/westonganger.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"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,"zenodo":null}},"created_at":"2018-05-20T02:57:46.000Z","updated_at":"2025-05-09T09:58:53.000Z","dependencies_parsed_at":"2023-01-14T21:30:45.520Z","dependency_job_id":"90444449-f6d6-4a79-9072-e0f59b2d8781","html_url":"https://github.com/westonganger/paper_trail-association_tracking","commit_stats":{"total_commits":96,"total_committers":14,"mean_commits":6.857142857142857,"dds":0.1875,"last_synced_commit":"5c96b9fa64921bbe52458d297dbd205816f39a73"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpaper_trail-association_tracking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpaper_trail-association_tracking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpaper_trail-association_tracking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westonganger%2Fpaper_trail-association_tracking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/westonganger","download_url":"https://codeload.github.com/westonganger/paper_trail-association_tracking/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264765,"owners_count":22041793,"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":["activerecord","associations","paper-trail","paper-trail-plugin","rails","ruby"],"created_at":"2024-10-01T12:15:08.434Z","updated_at":"2025-05-15T03:05:13.777Z","avatar_url":"https://github.com/westonganger.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PaperTrail-AssociationTracking\n\n\u003ca href=\"https://badge.fury.io/rb/paper_trail-association_tracking\" target=\"_blank\"\u003e\u003cimg height=\"21\" style='border:0px;height:21px;' border='0' src=\"https://badge.fury.io/rb/paper_trail-association_tracking.svg\" alt=\"Gem Version\"\u003e\u003c/a\u003e\n\u003ca href='https://github.com/westonganger/paper_trail-association_tracking/actions' target='_blank'\u003e\u003cimg src=\"https://github.com/westonganger/paper_trail-association_tracking/actions/workflows/test.yml/badge.svg?branch=master\" style=\"max-width:100%;\" height='21' style='border:0px;height:21px;' border='0' alt=\"CI Status\"\u003e\u003c/a\u003e\n\u003ca href='https://rubygems.org/gems/paper_trail-association_tracking' target='_blank'\u003e\u003cimg height='21' style='border:0px;height:21px;' src='https://img.shields.io/gem/dt/paper_trail-association_tracking?color=brightgreen\u0026label=Rubygems%20Downloads' border='0' alt='RubyGems Downloads' /\u003e\u003c/a\u003e\n\nPlugin for the [PaperTrail](https://github.com/paper-trail-gem/paper_trail.git) gem to track and reify associations. This gem was extracted from PaperTrail for v9.2.0 to simplify things in PaperTrail and association tracking separately.\n\n**PR's will happily be accepted**\n\nPaperTrail-AssociationTracking can restore three types of associations: Has-One, Has-Many, and Has-Many-Through.\n\nIt will store in the `version_associations` table additional information to correlate versions of the association and versions of the model when the associated record is changed. When reifying the model, it will utilize this table, together with the `transaction_id` to find the correct version of the association and reify it. The `transaction_id` is a unique id for version records created in the same transaction. It is used to associate the version of the model and the version of the association that are created in the same transaction.\n\n## Table of Contents\n\n- [Alternative Solution](#alternative-solution)\n- [Install](#install)\n- [Usage](#usage)\n- [Limitations](#limitations)\n- [Known Issues](#known-issues)\n- [Contributing](#contributing)\n- [Credits](#credits)\n\n# Alternative Solution\n\nModel versioning and restoration require concious thought, design, and understanding. You should understand your versioning and restoration process completely. This gem paper_trail-association-tracking is mostly a blackbox solution which encourages you to set it up and then assume its Just Working\u003csup\u003eTM\u003c/sup\u003e. This can make for major data problems later.\n\nInstead I recommend a newer gem that I have created for handling snapshots of records and associations called [active_snapshot](https://github.com/westonganger/active_snapshot). This gem does not utilize `paper_trail` at all. The focus of the [active_snapshot](https://github.com/westonganger/active_snapshot) gem is to have a simple and fully understandable design is easy to customize and know inside and out for your projects needs.\n\n# Install\n\n```ruby\ngem 'paper_trail'\ngem 'paper_trail-association_tracking'\n```\n\nThen run `rails generate paper_trail_association_tracking:install` which will do the following two things for you:\n\n1. Create a `version_associations` table\n2. Set `PaperTrail.config.track_associations = true` in an initializer\n\n# Usage\n\nFirst, ensure that you have added `has_paper_trail` to your main model and all associated models that are to be tracked.\n\nTo restore associations as they were at the time you must pass any of the following options to the `reify` method.\n\n- To restore Has-Many and Has-Many-Through associations, use option `has_many: true`\n- To restore Has-One associations , use option `has_one: true` to `reify`\n- To restore Belongs-To associations, use option `belongs_to: true`\n\nFor example:\n\n```ruby\nitem.versions.last.reify(has_many: true, has_one: true, belongs_to: false)\n```\n\nIf you want the reified associations to be saved upon calling `save` on the parent model then you must set `autosave: true` on all required associations. A little tip, `accepts_nested_attributes` automatically sets `autosave` to true but you should probably still state it explicitly.\n\nFor example:\n\n```ruby\nclass Product\n  has_many :photos, autosave: true\nend\n\nproduct = Product.first.versions.last.reify(has_many: true, has_one: true, belongs_to: false)\nproduct.save! ### now this will also save all reified photos\n```\n\nIf you do not set `autosave: true` true on the association then you will have to save/delete them manually.\n\nFor example:\n\n```ruby\nclass Product \u003c ActiveRecord::Base\n  has_paper_trail\n  has_many :photos, autosave: false ### or if autosave not set\nend\n\nproduct = Product.create(name: 'product_0')\nproduct.photos.create(name: 'photo')\nproduct.update(name: 'product_a')\nproduct.photos.create(name: 'photo')\n\nreified_product = product.versions.last.reify(has_many: true, mark_for_destruction: true)\nreified_product.save!\nreified_product.name # product_a\nreified_product.photos.size # 2\nreified_product.photos.reload\nreified_product.photos.size # 1 ### bad, didnt save the associations\n\nproduct = Product.create(name: 'product_1')\nproduct.update(name: 'product_b')\nproduct.photos.create(name: 'photo')\n\nreified_product = product.versions.last.reify(has_many: true, mark_for_destruction: true)\nreified_product.save!\nreified_product.name # product_b\nreified_product.photos.size # 1\nreified_product.photos.each{|x| x.marked_for_destruction? ? x.destroy! : x.save! }\nreified_product.photos.size # 0\n```\n\nIt will also respect AR transactions by utilizing the aforementioned `transaction_id` to reify the models as they were before the transaction (instead of before the update to the model).\n\nFor example:\n\n```ruby\nitem.amount                  # 100\nitem.location.latitude       # 12.345\n\nItem.transaction do\n  item.location.update(latitude: 54.321)\n  item.update(amount: 153)\nend\n\nt = item.versions.last.reify(has_one: true)\nt.amount                         # 100\nt.location.latitude              # 12.345, instead of 54.321\n```\n\n# Configuration\n\nYou can configure a different version association class by using the following configuration:\n\n```ruby\nclass ProductionVersionAssociation \u003c PaperTrail::VersionAssociation\n  # You can change the table name, i.e.:\n  self.table_name = \"product_version_associations\"\nend\n\nclass Product \u003c ActiveRecord::Base\n  has_paper_trail version_associations: { class_name: \"ProductVersionAssociation\" }\nend\n```\n\n# Limitations\n\n1. Only reifies the first level of associations. If you want to include nested associations simply add `:through` relationships to your model.\n1. Currently we only supports a single `version_associations` table. Therefore, you can only use a single table to store the versions for all related models.\n1. Relies on the callbacks on the association model (and the `:through` association model for Has-Many-Through associations) to record the versions and the relationship between the versions. If the association is changed without invoking the callbacks, then reification won't work. Example:\n\n    ```ruby\n    class Book \u003c ActiveRecord::Base\n      has_many :authorships, dependent: :destroy\n      has_many :authors, through: :authorships, source: :person\n      has_paper_trail\n    end\n\n    class Authorship \u003c ActiveRecord::Base\n      belongs_to :book\n      belongs_to :person\n      has_paper_trail      # NOTE\n    end\n\n    class Person \u003c ActiveRecord::Base\n      has_many :authorships, dependent: :destroy\n      has_many :books, through: :authorships\n      has_paper_trail\n    end\n\n    ### Each of the following will store authorship versions:\n    @book.authors \u003c\u003c @john\n    @book.authors.create(name: 'Jack')\n    @book.authorships.last.destroy\n    @book.authorships.clear\n    @book.author_ids = [@john.id, @joe.id]\n\n    ### But none of these will:\n    @book.authors.delete @john\n    @book.author_ids = []\n    @book.authors = []\n    ```\n\n\n# Known Issues\n\n1. Sometimes the has_one association will find more than one possible candidate and will raise a `PaperTrailAssociationTracking::Reifiers::HasOne::FoundMoreThanOne` error. For example, see `spec/models/person_spec.rb`\n    - If you are not using STI, you may want to just assume the first result of multiple is the correct one and continue. PaperTrail \u003c= v8 did this without error or warning. To do so add the following line to your initializer: `PaperTrail.config.association_reify_error_behaviour = :warn`. Valid options are: `[:error, :warn, :ignore]`\n    - When using STI, even if you enable `:warn` you will likely still end up recieving an `ActiveRecord::AssociationTypeMismatch` error. See [PT Issue #594](https://github.com/airblade/paper_trail/issues/594). I strongly recommend that you do not use STI, however if you do need to decide to use STI, please see https://github.com/paper-trail-gem/paper_trail#4b1-the-optional-item_subtype-column\n1. Not compatible with transactional tests, see [PT Issue #542](https://github.com/airblade/paper_trail/issues/542). However, apparently there has been some success by using the [transactional_capybara](https://rubygems.org/gems/transactional_capybara) gem.\n\n\n# Credits\n\nMaintained by [Weston Ganger](https://westonganger.com) - [@westonganger](https://github.com/westonganger)\n\nPlugin authored by [Weston Ganger](https://westonganger.com) - [@westonganger](https://github.com/westonganger)\n\nAssociations code originally contributed by Ben Atkins, Jared Beck, Andy Stewart \u0026 more\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestonganger%2Fpaper_trail-association_tracking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwestonganger%2Fpaper_trail-association_tracking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestonganger%2Fpaper_trail-association_tracking/lists"}