• Stars
    star
    118
  • Rank 289,453 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created almost 6 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Plugin for the PaperTrail gem to track and reify associations

PaperTrail-AssociationTracking

Gem Version CI Status RubyGems Downloads

Plugin for the PaperTrail 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.

PR's will happily be accepted

PaperTrail-AssociationTracking can restore three types of associations: Has-One, Has-Many, and Has-Many-Through.

It 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.

Table of Contents

Install

gem 'paper_trail'
gem 'paper_trail-association_tracking'

Then run rails generate paper_trail_association_tracking:install which will do the following two things for you:

  1. Create a version_associations table
  2. Set PaperTrail.config.track_associations = true in an initializer

Usage

First, ensure that you have added has_paper_trail to your main model and all associated models that are to be tracked.

To restore associations as they were at the time you must pass any of the following options to the reify method.

  • To restore Has-Many and Has-Many-Through associations, use option has_many: true
  • To restore Has-One associations , use option has_one: true to reify
  • To restore Belongs-To associations, use option belongs_to: true

For example:

item.versions.last.reify(has_many: true, has_one: true, belongs_to: false)

If 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.

For example:

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

If you do not set autosave: true true on the association then you will have to save/delete them manually.

For example:

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

It 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).

For example:

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

Limitations

  1. Only reifies the first level of associations. If you want to include nested associations simply add :through relationships to your model.

  2. 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.

  3. 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:

    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 = []

Known Issues

  1. 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
    • If you are not using STI, you may want to just assume the first result of multiple is the correct one and continue. PaperTrail <= 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]
    • When using STI, even if you enable :warn you will likely still end up recieving an ActiveRecord::AssociationTypeMismatch error. See PT Issue #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
  2. Not compatible with transactional tests, see PT Issue #542. However, apparently there has been some success by using the transactional_capybara gem.

Contributing

We use the appraisal gem for testing multiple versions of paper_trail and activerecord. Please use the following steps to test using appraisal.

  1. bundle exec appraisal install
  2. bundle exec appraisal rake test

Credits

Maintained by Weston Ganger - @westonganger

Plugin authored by Weston Ganger - @westonganger

Associations code originally contributed by Ben Atkins, Jared Beck, Andy Stewart & more

Alternative Solution

Model Versioning and Restoration require concious thought, design, and understanding. You should understand your versioning and restoration process completely. Because PT-AT it is mostly a blackbox solution which encourages you to set it up and then assume its "Just Working". This can make for major data problems later.

Instead I recommend a newer gem that I have created for handling snapshots of records and associations called active_snapshot. This gem does not utilize paper_trail at all. The focus of this gem is to have a simple and fully understandable design is easy to customize and know inside and out for your projects needs.

More Repositories

1

spreadsheet_architect

Spreadsheet Architect is a library that allows you to create XLSX, ODS, or CSV spreadsheets super easily from ActiveRecord relations, plain Ruby objects, or tabular data.
Ruby
1,298
star
2

rails_i18n_manager

Web interface to manage i18n translations helping to facilitate the editors of your translations. Provides a low-tech and complete workflow for importing, translating, and exporting your I18n translation files. Designed to allow you to keep the translation files inside your projects git repository where they should be.
Ruby
205
star
3

rearmed-js

A collection of helpful methods and monkey patches for Arrays, Objects, Numbers, and Strings in Javascript
JavaScript
104
star
4

active_snapshot

Simplified snapshots and restoration for ActiveRecord models and associations with a transparent white-box implementation
Ruby
96
star
5

rodf

ODF generation library for Ruby
Ruby
53
star
6

protected_attributes_continued

The community continued version of protected_attributes for Rails 5+
Ruby
45
star
7

rearmed-rb

A collection of helpful methods and monkey patches for Arrays, Hash, Enumerables, Strings, Objects & Dates in Ruby
Ruby
41
star
8

sexy_form.rb

Dead simple HTML form field builder for Ruby with built-in support for many popular UI libraries such as Bootstrap
Ruby
38
star
9

rearmed_rails

A collection of helpful methods and monkey patches for Rails
Ruby
33
star
10

form_builder.cr

Dead simple HTML form builder for Crystal with built-in support for many popular UI libraries such as Bootstrap
Crystal
31
star
11

bootstrap-directional-buttons

Directional / Arrow buttons for Bootstrap
HTML
22
star
12

capistrano-precompile-chooser

Capistrano plugin to precompile your Rails assets locally, remotely, or not at all provided with a very convenient default terminal prompt.
Ruby
13
star
13

active_sort_order

The "easy-peasy" dynamic sorting pattern for ActiveRecord that your Rails apps deserve
Ruby
13
star
14

input-autogrow

jQuery plugin for autogrowing inputs
JavaScript
8
star
15

chosen-bootstrap-theme

A Bootstrap theme for Chosen Select that actually looks like Bootstrap
CSS
8
star
16

js-try

JS-Try is a Javascript implementation of the try method from Rails for safe navigation
JavaScript
6
star
17

chosen-material-theme

A Material theme for Chosen Select
CSS
6
star
18

search_architect

Dead simple, powerful and fully customizable searching for your Rails or ActiveRecord models and associations.
Ruby
6
star
19

pairer

Pairer is Rails app/engine to Easily rotate and keep track of working pairs
Ruby
5
star
20

rails_uuid_to_integer_primary_keys

A Rails Migration to convert your UUID primary keys back to integer / bigint primary keys
Ruby
5
star
21

chosen-remote-source

Provides remote data source support for chosen-js selects
JavaScript
4
star
22

input-case-enforcer

Enforce uppercase, lowercase, or Capitalized inputs & textareas
JavaScript
4
star
23

paperclip_utils

Collection of Paperclip processors and a Helper class for easier dynamic processors and styles on your Paperclip uploads
Ruby
4
star
24

active_record_simple_execute

Sanitize and Execute your raw SQL queries in ActiveRecord and Rails with a much more intuitive and shortened syntax
Ruby
4
star
25

active_record_case_insensitive_finders

Adds case-insensitive finder methods to Rails and ActiveRecord
Ruby
3
star
26

chosen-readonly

Readonly support for Chosen selects
JavaScript
3
star
27

better_exception_notifier

An exception notifier for Rails and Rack apps
Ruby
3
star
28

rails_nestable_layouts

Rails Nestable Layouts - Dead simple nested layouts for Rails
Ruby
3
star
29

minitest_change_assertions

Provides assertions for your Minitest suite to determine if an object has been changed
Ruby
2
star
30

common_website_scripts_and_styles

JavaScript
2
star
31

rails_template

Efficient Rails Template
Ruby
2
star
32

rearmed-css

CSS Utility Classes
CSS
2
star
33

basic_ruby_and_rails_style_guide

2
star
34

simple_assets

Dead simple HTML-based assets helper for Ruby. The main idea here is to promote re-usability for projects.
Ruby
2
star
35

active_record_alias_join

Easily add custom SQL table aliases for your joins and includes
2
star
36

github_activity_scraper.rb

Scrape your entire GitHub activity information for all-time. Helps for aggregating how much open source sh!t you get done.
Ruby
2
star
37

devise_whos_here

Devise extension for logging current active users logged in using only the fast Rails cache and not your database
Ruby
2
star
38

accepts_nested_attributes_for_public_id

A patch for Rails to support using a public ID column instead of ID for use with accepts_nested_attributes_for
Ruby
2
star
39

rails_upgrade

Ruby
1
star
40

rubocop_template_for_productive_teams

Rubocop template designed speed up your teams development process instead of dragging it down
Ruby
1
star
41

github_actions_ci_example

Ruby
1
star
42

jekyll_template

CSS
1
star
43

ruby_view_template_converters

Complete solutions to convert ERB, SLIM, AND HAML with the least amount of manual effort
Ruby
1
star
44

westonganger

1
star
45

prawn_invoice

Dead simple Prawn based PDF invoice generator with support for custom invoice templates
Ruby
1
star
46

rails_dummy_app

Rails Dummy App and test_helper.rb for testing gems
Ruby
1
star
47

wagon_starter

Starter Template for LocomotiveCMS Wagon Sites
JavaScript
1
star
48

essential_capybara_helpers

A set of essential capybara helpers for everyday use
1
star
49

no_bullshit_middleman_template

Template for efficient static website generation using Middleman
HTML
1
star
50

fast_try

FastTry is a simple method wrapper to the safe navigation operator in Ruby
Ruby
1
star
51

lazy_serialize

Lazy Serialize is an alternative to ActiveRecord's serialize method which does not serialize each column until the first call to the attribute.
Ruby
1
star
52

prawn_resume

Dead simple Prawn based PDF resume generator with support for custom resume templates
Ruby
1
star
53

automatic_rails_route_testing

Template for easy exception testing for all routes within a Rails app
Ruby
1
star
54

select-sync

Javascript plugin for HTML `select` elements to synchronize by selected or disabled options
JavaScript
1
star
55

facebook_marketplace_scraper

A locally run Rails app to automatically search facebook marketplace for deals and aggregate them all in a list.
Ruby
1
star
56

rails_scrabble_with_friends

Simple web-based scrabble for you and your friends with zero friction authentication
Ruby
1
star
57

wagon_kitchen_sink

Example website for LocomotiveCMS / Wagon
JavaScript
1
star
58

rails_custom_form_builder

A good example/starter pack for a custom form builder for use with Rails form_for
Ruby
1
star
59

rails_clientside_javascript_error_handler

Easily handle client-side Javascript exceptions within your Rails app
Ruby
1
star