From b7dc57c4482e655c797e0fef9f38d9c61fe47ee2 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Thu, 6 Aug 2015 10:59:54 -0400 Subject: [PATCH 1/5] Docs: Organize readme re: limiting what/when Group together readme sections about limiting what is versioned, and when. [ci skip] --- README.md | 243 +++++++++++++++++++++++++++--------------------------- 1 file changed, 122 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index e585f49f..d4b6e914 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,12 @@ revert it to any version, and even undelete it after it's been destroyed. - [Installation](#installation) - [API Summary](#api-summary) - [Basic Usage](#basic-usage) -- [Choosing Lifecycle Events To Monitor](#choosing-lifecycle-events-to-monitor) -- [Choosing When To Save New Versions](#choosing-when-to-save-new-versions) -- [Choosing Attributes To Monitor](#choosing-attributes-to-monitor) +- Limiting what is versioned, and when + - [Choosing Lifecycle Events To Monitor](#choosing-lifecycle-events-to-monitor) + - [Choosing When To Save New Versions](#choosing-when-to-save-new-versions) + - [Choosing Attributes To Monitor](#choosing-attributes-to-monitor) + - [Turning PaperTrail Off/On](#turning-papertrail-offon) + - [Limiting the Number of Versions Created](#limiting-the-number-of-versions-created) - [Reverting And Undeleting A Model](#reverting-and-undeleting-a-model) - [Navigating Versions](#navigating-versions) - [Finding Out Who Was Responsible For A Change](#finding-out-who-was-responsible-for-a-change) @@ -21,10 +24,8 @@ revert it to any version, and even undelete it after it's been destroyed. - [Associations](#associations) - [Storing metadata](#storing-metadata) - [Diffing Versions](#diffing-versions) -- [Turning PaperTrail Off/On](#turning-papertrail-offon) - [Using a custom serializer](#using-a-custom-serializer) - [SerializedAttributes support](#serializedattributes-support) -- [Limiting the Number of Versions Created](#limiting-the-number-of-versions-created) - [Deleting Old Versions](#deleting-old-versions) - [Testing](#testing) @@ -437,6 +438,122 @@ class Article < ActiveRecord::Base end ``` +## Turning PaperTrail Off/On + +Sometimes you don't want to store changes. Perhaps you are only interested in +changes made by your users and don't need to store changes you make yourself in, +say, a migration -- or when testing your application. + +You can turn PaperTrail on or off in three ways: globally, per request, or per +class. + +### Globally + +On a global level you can turn PaperTrail off like this: + +```ruby +PaperTrail.enabled = false +``` + +For example, you might want to disable PaperTrail in your Rails application's +test environment to speed up your tests. This will do it (note: this gets done +automatically for `RSpec` and `Cucumber`, please see the [Testing +section](#testing)): + +```ruby +# in config/environments/test.rb +config.after_initialize do + PaperTrail.enabled = false +end +``` + +If you disable PaperTrail in your test environment but want to enable it for +specific tests, you can add a helper like this to your test helper: + +```ruby +# in test/test_helper.rb +def with_versioning + was_enabled = PaperTrail.enabled? + was_enabled_for_controller = PaperTrail.enabled_for_controller? + PaperTrail.enabled = true + PaperTrail.enabled_for_controller = true + begin + yield + ensure + PaperTrail.enabled = was_enabled + PaperTrail.enabled_for_controller = was_enabled_for_controller + end +end +``` + +And then use it in your tests like this: + +```ruby +test "something that needs versioning" do + with_versioning do + # your test + end +end +``` + +### Per request + +You can turn PaperTrail on or off per request by adding a +`paper_trail_enabled_for_controller` method to your controller which returns +`true` or `false`: + +```ruby +class ApplicationController < ActionController::Base + def paper_trail_enabled_for_controller + request.user_agent != 'Disable User-Agent' + end +end +``` + +### Per class + +If you are about to change some widgets and you don't want a paper trail of your +changes, you can turn PaperTrail off like this: + +```ruby +Widget.paper_trail_off! +``` + +And on again like this: + +```ruby +Widget.paper_trail_on! +``` + +### Per method call + +You can call a method without creating a new version using `without_versioning`. + It takes either a method name as a symbol: + +```ruby +@widget.without_versioning :destroy +``` + +Or a block: + +```ruby +@widget.without_versioning do + @widget.update_attributes :name => 'Ford' +end +``` + +## Limiting the Number of Versions Created + +Configure `version_limit` to cap the number of versions saved per record. This +does not apply to `create` events. + +```ruby +# Limit: 4 versions per record (3 most recent, plus a `create` event) +PaperTrail.config.version_limit = 3 +# Remove the limit +PaperTrail.config.version_limit = nil +``` + ## Reverting And Undeleting A Model PaperTrail makes reverting to a previous version easy: @@ -956,110 +1073,6 @@ If you wish to selectively record changes for some models but not others you can opt out of recording changes by passing `:save_changes => false` to your `has_paper_trail` method declaration. -## Turning PaperTrail Off/On - -Sometimes you don't want to store changes. Perhaps you are only interested in -changes made by your users and don't need to store changes you make yourself in, -say, a migration -- or when testing your application. - -You can turn PaperTrail on or off in three ways: globally, per request, or per -class. - -### Globally - -On a global level you can turn PaperTrail off like this: - -```ruby -PaperTrail.enabled = false -``` - -For example, you might want to disable PaperTrail in your Rails application's -test environment to speed up your tests. This will do it (note: this gets done -automatically for `RSpec` and `Cucumber`, please see the [Testing -section](#testing)): - -```ruby -# in config/environments/test.rb -config.after_initialize do - PaperTrail.enabled = false -end -``` - -If you disable PaperTrail in your test environment but want to enable it for -specific tests, you can add a helper like this to your test helper: - -```ruby -# in test/test_helper.rb -def with_versioning - was_enabled = PaperTrail.enabled? - was_enabled_for_controller = PaperTrail.enabled_for_controller? - PaperTrail.enabled = true - PaperTrail.enabled_for_controller = true - begin - yield - ensure - PaperTrail.enabled = was_enabled - PaperTrail.enabled_for_controller = was_enabled_for_controller - end -end -``` - -And then use it in your tests like this: - -```ruby -test "something that needs versioning" do - with_versioning do - # your test - end -end -``` - -### Per request - -You can turn PaperTrail on or off per request by adding a -`paper_trail_enabled_for_controller` method to your controller which returns -`true` or `false`: - -```ruby -class ApplicationController < ActionController::Base - def paper_trail_enabled_for_controller - request.user_agent != 'Disable User-Agent' - end -end -``` - -### Per class - -If you are about to change some widgets and you don't want a paper trail of your -changes, you can turn PaperTrail off like this: - -```ruby -Widget.paper_trail_off! -``` - -And on again like this: - -```ruby -Widget.paper_trail_on! -``` - -### Per method call - -You can call a method without creating a new version using `without_versioning`. - It takes either a method name as a symbol: - -```ruby -@widget.without_versioning :destroy -``` - -Or a block: - -```ruby -@widget.without_versioning do - @widget.update_attributes :name => 'Ford' -end -``` - ## Using a custom serializer By default, PaperTrail stores your changes as a `YAML` dump. You can override @@ -1112,18 +1125,6 @@ PaperTrail.config.serialized_attributes = false PaperTrail.serialized_attributes? ``` -## Limiting the Number of Versions Created - -Configure `version_limit` to cap the number of versions saved per record. This -does not apply to `create` events. - -```ruby -# Limit: 4 versions per record (3 most recent, plus a `create` event) -PaperTrail.config.version_limit = 3 -# Remove the limit -PaperTrail.config.version_limit = nil -``` - ## Deleting Old Versions Over time your `versions` table will grow to an unwieldy size. Because each From b6070c454825f2c24e8361fc6c1b07b0b11c8ccd Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Thu, 6 Aug 2015 11:11:26 -0400 Subject: [PATCH 2/5] Docs: Organize compatibility info into a table [ci skip] --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d4b6e914..fb3d2e38 100644 --- a/README.md +++ b/README.md @@ -55,17 +55,16 @@ revert it to any version, and even undelete it after it's been destroyed. ## Compatibility -Works with `ActiveRecord` 3+. Note: this code is on the `master` branch and tagged `v4.x`. - -Version 3 is on the branch named [`3.0-stable`][9] and is tagged `v3.x`, and works ActiveRecord 4 and ActiveRecord 3. - -Version 2 is on the branch named [`2.7-stable`][10] and is tagged `v2.x`, and works with Rails 3. - -The Rails 2.3 code is on the [`rails2`][11] branch and tagged `v1.x`. These branches are both stable with their respective versions of Rails but will not have new features added/backported to them. +| paper_trail | branch | tags | ruby | activerecord | +| ----------- | ---------- | ------ | -------- | ------------ | +| 4 | master | v4.x | >= 1.8.7 | >= 3.0 | +| 3 | 3.0-stable | v3.x | >= 1.8.7 | >= 3.0 | +| 2 | 2.7-stable | v2.x | >= 1.8.7 | >= 3.0, < 4 | +| 1 | rails2 | v1.x | >= 1.8.7 | >= 2.3, < 3 | ## Installation -### Rails 3 & 4 +### Rails 3 and 4 1. Add PaperTrail to your `Gemfile`. From d8651f22a3440d910072b8f7b80df896ea412be8 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Thu, 6 Aug 2015 11:33:32 -0400 Subject: [PATCH 3/5] Docs: Organize the feature list It's still too long for my taste, but this way people can just scan the top-level bullet points. [ci skip] --- README.md | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fb3d2e38..831cab04 100644 --- a/README.md +++ b/README.md @@ -31,27 +31,28 @@ revert it to any version, and even undelete it after it's been destroyed. ## Features -* Stores every create, update and destroy (or only the lifecycle events you specify). -* Does not store updates which don't change anything. -* Allows you to specify attributes (by inclusion or exclusion) which must change for a Version to be stored. -* Allows you to get at every version, including the original, even once destroyed. -* Allows you to get at every version even if the schema has since changed. -* Allows you to get at the version as of a particular time. -* Option to automatically restore `has_one`, `has_many` and `has_many :through` associations as they were at the time. -* Automatically records who was responsible via your controller. PaperTrail calls `current_user` by default, if it exists, but you can have it call any method you like. -* Allows you to set who is responsible at model-level (useful for migrations). -* Allows you to store arbitrary model-level metadata with each version (useful for filtering versions). -* Allows you to store arbitrary controller-level information with each version, e.g. remote IP. -* Can be turned off/on per class (useful for migrations). -* Can be turned off/on per request (useful for testing with an external service). -* Can be turned off/on globally (useful for testing). -* No configuration necessary. -* Stores everything in a single database table by default (generates migration for you), or can use separate tables for separate models. -* Supports custom version classes so different models' versions can have different behaviour. -* Supports custom name for versions association. -* Thoroughly tested. -* Threadsafe. - +* Stores create, update and destroy events + * Does not store updates which don't change anything + * Support for versioning associated records +* Can store metadata with each version record + * Who was responsible for a change + * Arbitrary model-level metadata (useful for filtering versions) + * Arbitrary controller-level information e.g. remote IP +* Configurable + * No configuration necessary, but if you want to .. + * Configure which events (create, update and destroy) are versioned + * Configure which attributes must change for an update to be versioned + * Turn off/on by model, request, or globally + * Use separate tables for separate models +* Extensible + * Write a custom version class for complete control + * Write custom version classes for each of your models +* Work with versions + * Restore any version, including the original, even once destroyed + * Restore any version even if the schema has since changed + * Restore the version as of a particular time +* Thoroughly tested +* Threadsafe ## Compatibility From a6aeb6e5f61737e8bb0bc664f2d2fe5a934cf266 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Thu, 6 Aug 2015 11:41:39 -0400 Subject: [PATCH 4/5] Docs: Organize readme re: Working with versions [ci skip] --- README.md | 147 +++++++++++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 831cab04..ce4d7e9c 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,23 @@ revert it to any version, and even undelete it after it's been destroyed. - [Installation](#installation) - [API Summary](#api-summary) - [Basic Usage](#basic-usage) -- Limiting what is versioned, and when +- Limiting What is Versioned, and When - [Choosing Lifecycle Events To Monitor](#choosing-lifecycle-events-to-monitor) - [Choosing When To Save New Versions](#choosing-when-to-save-new-versions) - [Choosing Attributes To Monitor](#choosing-attributes-to-monitor) - [Turning PaperTrail Off/On](#turning-papertrail-offon) - [Limiting the Number of Versions Created](#limiting-the-number-of-versions-created) -- [Reverting And Undeleting A Model](#reverting-and-undeleting-a-model) -- [Navigating Versions](#navigating-versions) +- Working With Versions + - [Reverting And Undeleting A Model](#reverting-and-undeleting-a-model) + - [Navigating Versions](#navigating-versions) + - [Diffing Versions](#diffing-versions) + - [Deleting Old Versions](#deleting-old-versions) - [Finding Out Who Was Responsible For A Change](#finding-out-who-was-responsible-for-a-change) - [Custom Version Classes](#custom-version-classes) - [Associations](#associations) - [Storing metadata](#storing-metadata) -- [Diffing Versions](#diffing-versions) - [Using a custom serializer](#using-a-custom-serializer) - [SerializedAttributes support](#serializedattributes-support) -- [Deleting Old Versions](#deleting-old-versions) - [Testing](#testing) ## Features @@ -647,6 +648,74 @@ And you can perform `WHERE` queries for object versions based on attributes: PaperTrail::Version.where_object(content: "Hello", title: "Article") ``` +## Diffing Versions + +There are two scenarios: diffing adjacent versions and diffing non-adjacent +versions. + +The best way to diff adjacent versions is to get PaperTrail to do it for you. +If you add an `object_changes` text column to your `versions` table, either at +installation time with the `rails generate paper_trail:install --with-changes` +option or manually, PaperTrail will store the `changes` diff (excluding any +attributes PaperTrail is ignoring) in each `update` version. You can use the +`version.changeset` method to retrieve it. For example: + +```ruby +widget = Widget.create :name => 'Bob' +widget.versions.last.changeset # {'name' => [nil, 'Bob']} +widget.update_attributes :name => 'Robert' +widget.versions.last.changeset # {'name' => ['Bob', 'Robert']} +widget.destroy +widget.versions.last.changeset # {} +``` + +Note PaperTrail only stores the changes for creation and updates; it doesn't +store anything when an object is destroyed. + +Please be aware that PaperTrail doesn't use diffs internally. When I designed +PaperTrail I wanted simplicity and robustness so I decided to make each version +of an object self-contained. A version stores all of its object's data, not a +diff from the previous version. This means you can delete any version without +affecting any other. + +To diff non-adjacent versions you'll have to write your own code. These +libraries may help: + +For diffing two strings: + +* [htmldiff][19]: expects but doesn't require HTML input and produces HTML + output. Works very well but slows down significantly on large (e.g. 5,000 + word) inputs. +* [differ][20]: expects plain text input and produces plain + text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise, + or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input. +* [diff-lcs][21]: old-school, line-wise diffs. + +For diffing two ActiveRecord objects: + +* [Jeremy Weiskotten's PaperTrail fork][22]: uses ActiveSupport's diff to return + an array of hashes of the changes. +* [activerecord-diff][23]: rather like ActiveRecord::Dirty but also allows you + to specify which columns to compare. + +If you wish to selectively record changes for some models but not others you +can opt out of recording changes by passing `:save_changes => false` to your +`has_paper_trail` method declaration. + +## Deleting Old Versions + +Over time your `versions` table will grow to an unwieldy size. Because each +version is self-contained (see the Diffing section above for more) you can +simply delete any records you don't want any more. For example: + +```sql +sql> delete from versions where created_at < 2010-06-01; +``` + +```ruby +PaperTrail::Version.delete_all ["created_at < ?", 1.week.ago] +``` + ## Finding Out Who Was Responsible For A Change If your `ApplicationController` has a `current_user` method, PaperTrail will @@ -1019,60 +1088,6 @@ end If you're using [strong_parameters][18] instead of [protected_attributes][17] then there is no need to use `attr_accessible`. -## Diffing Versions - -There are two scenarios: diffing adjacent versions and diffing non-adjacent -versions. - -The best way to diff adjacent versions is to get PaperTrail to do it for you. -If you add an `object_changes` text column to your `versions` table, either at -installation time with the `rails generate paper_trail:install --with-changes` -option or manually, PaperTrail will store the `changes` diff (excluding any -attributes PaperTrail is ignoring) in each `update` version. You can use the -`version.changeset` method to retrieve it. For example: - -```ruby -widget = Widget.create :name => 'Bob' -widget.versions.last.changeset # {'name' => [nil, 'Bob']} -widget.update_attributes :name => 'Robert' -widget.versions.last.changeset # {'name' => ['Bob', 'Robert']} -widget.destroy -widget.versions.last.changeset # {} -``` - -Note PaperTrail only stores the changes for creation and updates; it doesn't -store anything when an object is destroyed. - -Please be aware that PaperTrail doesn't use diffs internally. When I designed -PaperTrail I wanted simplicity and robustness so I decided to make each version -of an object self-contained. A version stores all of its object's data, not a -diff from the previous version. This means you can delete any version without -affecting any other. - -To diff non-adjacent versions you'll have to write your own code. These -libraries may help: - -For diffing two strings: - -* [htmldiff][19]: expects but doesn't require HTML input and produces HTML - output. Works very well but slows down significantly on large (e.g. 5,000 - word) inputs. -* [differ][20]: expects plain text input and produces plain - text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise, - or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input. -* [diff-lcs][21]: old-school, line-wise diffs. - -For diffing two ActiveRecord objects: - -* [Jeremy Weiskotten's PaperTrail fork][22]: uses ActiveSupport's diff to return - an array of hashes of the changes. -* [activerecord-diff][23]: rather like ActiveRecord::Dirty but also allows you - to specify which columns to compare. - -If you wish to selectively record changes for some models but not others you -can opt out of recording changes by passing `:save_changes => false` to your -`has_paper_trail` method declaration. - ## Using a custom serializer By default, PaperTrail stores your changes as a `YAML` dump. You can override @@ -1125,20 +1140,6 @@ PaperTrail.config.serialized_attributes = false PaperTrail.serialized_attributes? ``` -## Deleting Old Versions - -Over time your `versions` table will grow to an unwieldy size. Because each -version is self-contained (see the Diffing section above for more) you can -simply delete any records you don't want any more. For example: - -```sql -sql> delete from versions where created_at < 2010-06-01; -``` - -```ruby -PaperTrail::Version.delete_all ["created_at < ?", 1.week.ago] -``` - ## Testing You may want to turn PaperTrail off to speed up your tests. See the [Turning From af8154741c2460c7c56d2e12cc19a46463711c15 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Thu, 6 Aug 2015 11:45:23 -0400 Subject: [PATCH 5/5] Docs: A more concise intro paragraph [ci skip] --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ce4d7e9c..8fe9d54a 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build Status][4]][5] [![Dependency Status][6]][7] -PaperTrail lets you track changes to your models' data. It's good for auditing -or versioning. You can see how a model looked at any stage in its lifecycle, -revert it to any version, and even undelete it after it's been destroyed. +Track changes to your models, for auditing or versioning. See how a model looked +at any stage in its lifecycle, revert it to any version, or restore it after it +has been destroyed. - [Features](#features) - [Compatibility](#compatibility)