### Install PaperTrail-AssociationTracking Gem Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md Add these gems to your Gemfile to enable association tracking functionality. ```ruby gem 'paper_trail' gem 'paper_trail-association_tracking' ``` -------------------------------- ### Run Install Generator and Migrations Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Generate the necessary database tables and initializer file, then run migrations. ```bash # Run the install generator rails generate paper_trail_association_tracking:install # This creates two migrations and an initializer: # - db/migrate/xxx_create_version_associations.rb # - db/migrate/xxx_add_transaction_id_column_to_versions.rb # - config/initializers/paper_trail.rb (sets track_associations = true) # Run migrations rails db:migrate ``` -------------------------------- ### Basic Model Setup for Association Tracking Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Add `has_paper_trail` to your main model and all associated models that require tracking. This is essential for the gem to function correctly. ```ruby # app/models/customer.rb class Customer < ActiveRecord::Base has_many :orders, dependent: :destroy has_paper_trail end # app/models/order.rb class Order < ActiveRecord::Base belongs_to :customer has_many :line_items has_paper_trail # Required for association tracking end # app/models/line_item.rb class LineItem < ActiveRecord::Base belongs_to :order has_paper_trail # Required for association tracking end ``` -------------------------------- ### Association Tracking with Callbacks Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md This example demonstrates how changes to associations trigger versioning if `has_paper_trail` is defined on the association model. Changes made without invoking callbacks will not be versioned. ```ruby class Book < ActiveRecord::Base has_many :authorships, dependent: :destroy has_many :authors, through: :authorships, source: :person has_paper_trail end class Authorship < ActiveRecord::Base belongs_to :book belongs_to :person has_paper_trail # NOTE end class Person < ActiveRecord::Base has_many :authorships, dependent: :destroy has_many :books, through: :authorships has_paper_trail end ### Each of the following will store authorship versions: @book.authors << @john @book.authors.create(name: 'Jack') @book.authorships.last.destroy @book.authorships.clear @book.author_ids = [@john.id, @joe.id] ### But none of these will: @book.authors.delete @john @book.author_ids = [] @book.authors = [] ``` -------------------------------- ### Create Version Associations Table Migration Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Defines the database migration for creating the `version_associations` table, which stores relationships between versions and their associated records. Includes necessary indexes for efficient querying. ```ruby # Migration generated by the install generator class CreateVersionAssociations < ActiveRecord::Migration[7.0] def self.up create_table :version_associations do |t| t.integer :version_id t.string :foreign_key_name, null: false t.integer :foreign_key_id t.string :foreign_type end add_index :version_associations, [:version_id] add_index :version_associations, %i(foreign_key_name foreign_key_id foreign_type), name: "index_version_associations_on_foreign_key" end def self.down remove_index :version_associations, [:version_id] remove_index :version_associations, name: "index_version_associations_on_foreign_key" drop_table :version_associations end end ``` -------------------------------- ### Configure Custom Version Association Class Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Use a custom version association class for advanced scenarios, such as connecting to a separate database for version associations. Ensure the `table_name` is correctly set. ```ruby # app/models/product_version_association.rb class ProductVersionAssociation < PaperTrail::VersionAssociation # Connect to a different database self.table_name = "product_version_associations" # Or establish a separate connection # connects_to database: { writing: :versions_db } end # app/models/product.rb class Product < ActiveRecord::Base has_paper_trail version_associations: { class_name: "ProductVersionAssociation" } has_many :photos, autosave: true end ``` -------------------------------- ### Handle Reification Errors Gracefully Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Demonstrates how to rescue the specific PaperTrailAssociationTracking::Reifiers::HasOne::FoundMoreThanOne error during reification and log a detailed error message. ```ruby begin person.versions.last.reify(has_one: true) rescue PaperTrailAssociationTracking::Reifiers::HasOne::FoundMoreThanOne => e Rails.logger.error "Reification conflict: #{e.message}" # Handle the error appropriately end ``` -------------------------------- ### Add Gem to Gemfile Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Include the paper_trail and paper_trail-association_tracking gems in your Gemfile. ```ruby # Gemfile gem 'paper_trail' gem 'paper_trail-association_tracking' ``` -------------------------------- ### Configure Custom Version Association Class Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md Define a custom class inheriting from `PaperTrail::VersionAssociation` and specify it using the `version_associations` option in `has_paper_trail`. ```ruby class ProductionVersionAssociation < PaperTrail::VersionAssociation # You can change the table name, i.e.: self.table_name = "product_version_associations" end class Product < ActiveRecord::Base has_paper_trail version_associations: { class_name: "ProductVersionAssociation" } end ``` -------------------------------- ### Enable Association Tracking Globally Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Configure PaperTrail to track associations globally in the initializer. You can also set error behavior for has_one reification. ```ruby # config/initializers/paper_trail.rb # Enable association tracking (required) PaperTrail.config.track_associations = true # Configure error behavior when has_one reification finds multiple candidates # Options: :error (default), :warn, :ignore PaperTrail.config.association_reify_error_behaviour = :error # Check if association tracking is enabled PaperTrail.config.track_associations? # => true ``` -------------------------------- ### Manual Association Saving Without Autosave Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Illustrates the process of manually saving associations when `autosave: false` (the default). This requires iterating through associations and explicitly saving or destroying them. ```ruby # Without autosave, you must manually save associations class Item < ActiveRecord::Base has_many :parts, autosave: false # default has_paper_trail end item = Item.first reified = item.versions.last.reify(has_many: true, mark_for_destruction: true) reified.save! # Only saves the item, not the parts # Manually handle each part reified.parts.each do |part| if part.marked_for_destruction? part.destroy! else part.save! end end ``` -------------------------------- ### Configure Association Reification Error Behavior Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Set the behavior for handling errors when reifying associations. Options include ':warn' to log a warning and use the first result, or ':ignore' to silently use the first result. ```ruby PaperTrail.config.association_reify_error_behaviour = :warn ``` ```ruby PaperTrail.config.association_reify_error_behaviour = :ignore ``` -------------------------------- ### Reify Associations with Specific Options Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md Use this to restore associations to their state at a specific version. Pass `has_many: true`, `has_one: true`, or `belongs_to: true` as needed. ```ruby item.versions.last.reify(has_many: true, has_one: true, belongs_to: false) ``` -------------------------------- ### Track has_and_belongs_to_many Associations Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Configure tracking for HABTM associations using the `join_tables` option on `has_paper_trail`. This allows reification of HABTM relationships. ```ruby # app/models/foo_habtm.rb class FooHabtm < ActiveRecord::Base has_and_belongs_to_many :bar_habtms has_paper_trail join_tables: [:bar_habtms] end # app/models/bar_habtm.rb class BarHabtm < ActiveRecord::Base has_and_belongs_to_many :foo_habtms has_paper_trail end # Usage foo = FooHabtm.create(name: 'Foo') bar1 = BarHabtm.create(name: 'Bar 1') bar2 = BarHabtm.create(name: 'Bar 2') foo.bar_habtms << bar1 foo.update(name: 'Updated Foo') foo.bar_habtms << bar2 # Reify with HABTM reified = foo.versions.last.reify(has_and_belongs_to_many: true) reified.bar_habtms.map(&:name) # => ["Bar 1"] ``` -------------------------------- ### Combine Multiple Association Options for Reification Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Simultaneously reify a model with multiple association types by passing several boolean options to `reify`. The `mark_for_destruction` option can be used to mark records that should be removed. ```ruby # Reify with all association types reified = item.versions.last.reify( has_one: true, has_many: true, belongs_to: true, has_and_belongs_to_many: false, mark_for_destruction: true # Mark removed records for destruction ) # The mark_for_destruction option # When true, records that should be removed are marked rather than excluded product = Product.create(name: 'Gadget') photo1 = product.photos.create(name: 'photo1.jpg') product.update(name: 'New Gadget') photo2 = product.photos.create(name: 'photo2.jpg') reified = product.versions.last.reify(has_many: true, mark_for_destruction: true) reified.photos.size # => 2 # Find and destroy marked records reified.photos.each do |photo| if photo.marked_for_destruction? photo.destroy! else photo.save! end end reified.photos.reload.size # => 1 (only photo1.jpg remains) ``` -------------------------------- ### Add Transaction ID Column to Versions Table Migration Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Defines the database migration for adding a `transaction_id` column to the `versions` table, which is used for grouping related changes into a single transaction. ```ruby # Transaction ID column added to versions table class AddTransactionIdColumnToVersions < ActiveRecord::Migration[7.0] def self.up add_column :versions, :transaction_id, :bigint add_index :versions, [:transaction_id] end def self.down remove_index :versions, [:transaction_id] remove_column :versions, :transaction_id end end ``` -------------------------------- ### Configure PaperTrail Association Reify Error Behavior Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md Set the behavior for when a has_one association finds multiple candidates. Options include :error, :warn, or :ignore. Defaults to :error. ```ruby PaperTrail.config.association_reify_error_behaviour = :warn ``` -------------------------------- ### Manual Saving of Reified Associations (No Autosave) Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md If `autosave` is not enabled, reified associations must be saved or destroyed manually after reification and saving the parent model. ```ruby class Product < ActiveRecord::Base has_paper_trail has_many :photos, autosave: false ### or if autosave not set end product = Product.create(name: 'product_0') product.photos.create(name: 'photo') product.update(name: 'product_a') product.photos.create(name: 'photo') reified_product = product.versions.last.reify(has_many: true, mark_for_destruction: true) reified_product.save! reified_product.name # product_a reified_product.photos.size # 2 reified_product.photos.reload reified_product.photos.size # 1 ### bad, didnt save the associations product = Product.create(name: 'product_1') product.update(name: 'product_b') product.photos.create(name: 'photo') reified_product = product.versions.last.reify(has_many: true, mark_for_destruction: true) reified_product.save! reified_product.name # product_b reified_product.photos.size # 1 reified_product.photos.each{|x| x.marked_for_destruction? ? x.destroy! : x.save! } reified_product.photos.size # 0 ``` -------------------------------- ### Enable Autosave for Reified Associations Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Configures associations with `autosave: true` to automatically persist reified associations when the parent model is saved. This ensures that changes to associated records are saved along with the parent. ```ruby # app/models/product.rb class Product < ActiveRecord::Base # With autosave: true, saving the parent saves associated records has_many :photos, autosave: true has_one :specification, autosave: true has_paper_trail end ``` ```ruby # Usage with autosave product = Product.first reified = product.versions.last.reify(has_many: true, has_one: true) # This single save persists the product AND all reified associations reified.save! reified.photos.reload # All photos are restored to their previous state ``` -------------------------------- ### Enable Autosave for Reified Associations Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md Set `autosave: true` on associations to automatically save reified associations when the parent model is saved. `accepts_nested_attributes` also enables autosave. ```ruby class Product has_many :photos, autosave: true end product = Product.first.versions.last.reify(has_many: true, has_one: true, belongs_to: false) product.save! ### now this will also save all reified photos ``` -------------------------------- ### Reify Has-One Associations Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Restore a model and its `has_one` association to a previous state using the `has_one: true` option during reification. Ensure `autosave: true` is set on the association for persistence. ```ruby # app/models/widget.rb class Widget < ActiveRecord::Base has_paper_trail has_one :wotsit, autosave: true end # app/models/wotsit.rb class Wotsit < ActiveRecord::Base belongs_to :widget has_paper_trail end # Usage widget = Widget.create(name: 'Gadget') widget.create_wotsit(name: 'Original Wotsit') widget.update(name: 'New Gadget') widget.wotsit.update(name: 'Updated Wotsit') ``` -------------------------------- ### Configure has_one Reification Error Behavior Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Set `PaperTrail.config.association_reify_error_behaviour` to `:error` (default) to raise an error on has_one reification conflicts. Other options may be available for different handling strategies. ```ruby # config/initializers/paper_trail.rb # Option 1: Raise an error (default, strictest) PaperTrail.config.association_reify_error_behaviour = :error ``` -------------------------------- ### Reify has_many :through Association Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Restore models with has_many through associations, including the join model records, by using `has_many: true`. Ensure the join model also has `has_paper_trail` enabled. ```ruby # app/models/book.rb class Book < ActiveRecord::Base has_many :authorships, dependent: :destroy has_many :authors, through: :authorships has_paper_trail end # app/models/authorship.rb class Authorship < ActiveRecord::Base belongs_to :book belongs_to :author, class_name: "Person" has_paper_trail # Critical: join model must have paper_trail end # app/models/person.rb class Person < ActiveRecord::Base has_many :authorships, foreign_key: :author_id, dependent: :destroy has_many :books, through: :authorships has_paper_trail end # Usage book = Book.create(title: 'Ruby Guide') john = Person.create(name: 'John') jane = Person.create(name: 'Jane') book.authors << john book.update(title: 'Advanced Ruby Guide') book.authors << jane # Reify to when only John was an author reified = book.versions.last.reify(has_many: true) reified.title # => "Ruby Guide" reified.authors.map(&:name) # => ["John"] ``` -------------------------------- ### Reify Has-Many Associations Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Restore a model and its `has_many` associations to a previous state by using the `has_many: true` option during reification. Note that `autosave: true` on the association or manual saving is required to persist reified associations. ```ruby # Create product with photos product = Product.create(name: 'Widget') product.photos.create(name: 'front_view.jpg') product.photos.create(name: 'back_view.jpg') # Modify the product and add more photos product.update(name: 'Super Widget') product.photos.create(name: 'side_view.jpg') product.photos.first.destroy # product.photos.count => 2 (back_view.jpg, side_view.jpg) # Reify to the previous version with has_many associations reified = product.versions.last.reify(has_many: true) reified.name # => "Widget" reified.photos.map(&:name) # => ["front_view.jpg", "back_view.jpg"] # To persist the reified associations, use autosave: true on the association # or manually save each associated record reified.save! # Only saves the parent unless autosave: true ``` -------------------------------- ### Reify has_one Association Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Use `has_one: true` to reify a model with its has_one association. This restores the associated record as it was at the time of the version. ```ruby reified = widget.versions.last.reify(has_one: true) reified.name # => "Gadget" reified.wotsit.name # => "Original Wotsit" ``` -------------------------------- ### Reify belongs_to Association Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt Restore a model with its belongs_to association using `belongs_to: true`. This ensures the associated record is correctly restored to its state at the time of the version. ```ruby # app/models/order.rb class Order < ActiveRecord::Base belongs_to :customer has_paper_trail end # Usage customer = Customer.create(name: 'Acme Corp', credit_limit: 1000) order = Order.create(customer: customer, total: 500) customer.update(name: 'Acme Corporation', credit_limit: 5000) order.update(total: 750) # Reify order with its customer as it was at the time reified_order = order.versions.last.reify(belongs_to: true) reified_order.total # => 500 reified_order.customer.name # => "Acme Corp" reified_order.customer.credit_limit # => 1000 ``` -------------------------------- ### Reify within Database Transactions Source: https://context7.com/westonganger/paper_trail-association_tracking/llms.txt PaperTrail respects ActiveRecord transactions. Use `transaction_id` to restore models to their state before a specific transaction. This ensures that all changes within a transaction are reverted together. ```ruby # Models changed together in a transaction are reified together item = Item.create(amount: 100) item.create_location(latitude: 12.345) Item.transaction do item.location.update(latitude: 54.321) item.update(amount: 153) end # Both changes happened in the same transaction # Reifying restores both to their pre-transaction state reified = item.versions.last.reify(has_one: true) reified.amount # => 100 reified.location.latitude # => 12.345 (not 54.321) # Query versions within the same transaction transaction_id = item.versions.last.transaction_id related_versions = PaperTrail::Version.within_transaction(transaction_id) ``` -------------------------------- ### Reify Associations within Transactions Source: https://github.com/westonganger/paper_trail-association_tracking/blob/master/README.md PaperTrail respects ActiveRecord transactions. Reifying an item within a transaction will restore it to its state before the transaction began, not just before the last update. ```ruby item.amount # 100 item.location.latitude # 12.345 Item.transaction do item.location.update(latitude: 54.321) item.update(amount: 153) end t = item.versions.last.reify(has_one: true) t.amount # 100 t.location.latitude # 12.345, instead of 54.321 ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.