paper_trail 4.0.0.beta2 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +9 -7
  4. data/CHANGELOG.md +38 -2
  5. data/README.md +30 -12
  6. data/Rakefile +1 -1
  7. data/gemfiles/3.0.gemfile +3 -1
  8. data/lib/generators/paper_trail/install_generator.rb +4 -1
  9. data/lib/paper_trail/cleaner.rb +1 -1
  10. data/lib/paper_trail/config.rb +6 -0
  11. data/lib/paper_trail/frameworks/sinatra.rb +1 -0
  12. data/lib/paper_trail/has_paper_trail.rb +80 -67
  13. data/lib/paper_trail/serializers/yaml.rb +2 -1
  14. data/lib/paper_trail/version_concern.rb +64 -19
  15. data/lib/paper_trail/version_number.rb +1 -1
  16. data/lib/paper_trail.rb +7 -3
  17. data/paper_trail.gemspec +2 -1
  18. data/spec/models/animal_spec.rb +19 -0
  19. data/spec/models/boolit_spec.rb +48 -0
  20. data/spec/models/json_version_spec.rb +80 -0
  21. data/spec/models/thing_spec.rb +11 -0
  22. data/spec/models/version_spec.rb +153 -78
  23. data/spec/models/widget_spec.rb +27 -6
  24. data/spec/modules/paper_trail_spec.rb +27 -0
  25. data/spec/requests/articles_spec.rb +0 -2
  26. data/test/dummy/app/models/animal.rb +2 -0
  27. data/test/dummy/app/models/boolit.rb +4 -0
  28. data/test/dummy/app/models/fruit.rb +5 -0
  29. data/test/dummy/app/models/song.rb +20 -0
  30. data/test/dummy/app/models/thing.rb +3 -0
  31. data/test/dummy/app/models/whatchamajigger.rb +4 -0
  32. data/test/dummy/app/models/widget.rb +1 -0
  33. data/test/dummy/app/versions/json_version.rb +3 -0
  34. data/test/dummy/config/initializers/paper_trail.rb +3 -0
  35. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +44 -3
  36. data/test/dummy/db/schema.rb +13 -4
  37. data/test/functional/controller_test.rb +4 -2
  38. data/test/unit/model_test.rb +76 -12
  39. data/test/unit/serializer_test.rb +3 -3
  40. metadata +40 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b96c067cab9f8789c0942d19fe7db30c83b0854
4
- data.tar.gz: a2f6e330b8c28de3c575c9a7c13ba54c352702ed
3
+ metadata.gz: c4a505b4f0b1cb4242e1a8eeb291fa5b95df54de
4
+ data.tar.gz: 26ef2a95bf0a15137d707b52b7a8f1aeff207b46
5
5
  SHA512:
6
- metadata.gz: 2eb6e5433a4e085a9ccaa41b5c1862f2c1b25fd4f77d08061470d4cf411560f02aa1cd18179f5fb058b446c0d3e26ba58ea917fbe20c4c13032128e8d3837c3a
7
- data.tar.gz: 435e06ff6d5e3071f09cb3d4a1b415c132114efb6d01b36d9b15d794a6b249db0392ddad6a1f75f6d0fdbf826befa6509b01c1235cce54034f621e5ab5e04594
6
+ metadata.gz: 24bf40f194ddd24066e4ff0737eb35a603cb8b0e54b14060315a0d5ad57a98b53b8aaecbf7a07d69d65d3ab1d99ac6cead49b0396560a4d2f8545c3580d70041
7
+ data.tar.gz: f7efc50eab66db98cea15c876201ebe5773c7b5f64c0320f98743b607819a1f8084438096e9e3849b1b233d3b0c2e9f86114be07e8de72d09f18ed24c04175f4
data/.gitignore CHANGED
@@ -17,3 +17,5 @@ vendor/*
17
17
  .rvmrc
18
18
  .tags
19
19
  .tags_sorted_by_file
20
+ .ruby-version
21
+ .ruby-gemset
data/.travis.yml CHANGED
@@ -6,9 +6,12 @@ rvm:
6
6
  - jruby-19mode
7
7
  - jruby-18mode
8
8
  env:
9
- - DB=mysql
10
- - DB=postgres
11
- - DB=sqlite
9
+ global:
10
+ - TRAVIS=true
11
+ matrix:
12
+ - DB=mysql
13
+ - DB=postgres
14
+ - DB=sqlite
12
15
 
13
16
  sudo: false
14
17
 
@@ -27,11 +30,10 @@ gemfile:
27
30
  matrix:
28
31
  fast_finish: true
29
32
  allow_failures:
30
- - rvm: jruby-19mode
31
- env: DB=postgres
32
- gemfile: Gemfile
33
33
  - rvm: jruby-18mode
34
34
  gemfile: Gemfile
35
35
  - rvm: 1.8.7
36
36
  gemfile: Gemfile
37
-
37
+
38
+ addons:
39
+ postgresql: "9.4"
data/CHANGELOG.md CHANGED
@@ -9,13 +9,35 @@ PaperTrail::Rails::Engine.eager_load!
9
9
 
10
10
  If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually load them into your test helper](https://github.com/airblade/paper_trail#testing).
11
11
 
12
+ - [#525](https://github.com/airblade/paper_trail/issues/525) / [#512](https://github.com/airblade/paper_trail/pull/512) -
13
+ Support for virtual accessors and redefined setter and getter methods.
14
+ - [#518](https://github.com/airblade/paper_trail/pull/518) - Support for querying against PostgreSQL's
15
+ [`JSON` and `JSONB` column types](http://www.postgresql.org/docs/9.4/static/datatype-json.html) via
16
+ `PaperTrail::VersionConcern#where_object` and `PaperTrail::VersionConcern#where_object_changes`
17
+ - [#507](https://github.com/airblade/paper_trail/pull/507) - Support for opting out of saving changesets on models by choice
18
+ when the `object_changes` column exists on the default `versions` table.
19
+ - [#500](https://github.com/airblade/paper_trail/pull/500) - Support for passing `on: []` as an argument, with only manual
20
+ versioning via calls to `touch_with_version`
21
+ - [#494](https://github.com/airblade/paper_trail/issues/494) - The install generator will warn the user if the migration they are
22
+ attempting to generate already exists.
23
+ - [#484](https://github.com/airblade/paper_trail/pull/484) - Support for
24
+ [PostgreSQL's `JSONB` Type](http://www.postgresql.org/docs/9.4/static/datatype-json.html) for storing `object`
25
+ and `object_changes`.
26
+ - [#479](https://github.com/airblade/paper_trail/issues/479) - Deprecated `originator` method in favor of `paper_trail_originator`
27
+ Deprecation warning informs users that the `originator` of the methods will be removed in version `4.0`
12
28
  - [#458](https://github.com/airblade/paper_trail/pull/458) - For `create` events, metadata pointing at attributes should attempt
13
29
  to grab the current value instead of looking at the value prior to the change (which would always be `nil`)
30
+ - [#451](https://github.com/airblade/paper_trail/issues/451) - Fix `reify` method in context of model where the base class
31
+ has a default scope, and the live instance is not scoped within that default scope
14
32
  - [#440](https://github.com/airblade/paper_trail/pull/440) - `versions` association should clear/reload after a transaction rollback.
15
33
  - [#439](https://github.com/airblade/paper_trail/pull/439) / [#12](https://github.com/airblade/paper_trail/issues/12) -
16
34
  Support for versioning of associations (Has Many, Has One, HABTM, etc.)
17
- - [#438](https://github.com/airblade/paper_trail/issues/438) - `Model.paper_trail_enabled_for_model?` should return `false` if
35
+ - [#438](https://github.com/airblade/paper_trail/issues/438) - `ModelKlass.paper_trail_enabled_for_model?` should return `false` if
18
36
  `has_paper_trail` has not been declared on the class.
37
+ - [#404](https://github.com/airblade/paper_trail/issues/404) / [#428](https://github.com/airblade/paper_trail/issues/428) -
38
+ `model_instance.dup` does not need to be invoked when examining what the instance looked like before changes were persisted,
39
+ which avoids issues if a 3rd party has overriden the `dup` behavior. Also fixes errors occuring when a user attempts to
40
+ update the inheritance column on an STI model instance in `ActiveRecord` 4.1.x
19
41
  - [#427](https://github.com/airblade/paper_trail/pull/427) - Fix `reify` method in context of model where a column has been removed.
20
42
  - [#420](https://github.com/airblade/paper_trail/issues/420) - Add `VersionConcern#where_object_changes` instance method;
21
43
  acts as a helper for querying against the `object_changes` column in versions table.
@@ -24,6 +46,8 @@ If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually l
24
46
  deprecated in `ActiveRecord` version `4.2` and will be removed in version `5.0`
25
47
  - [#414](https://github.com/airblade/paper_trail/issues/414) - Fix functionality `ignore` argument to `has_paper_trail`
26
48
  in `ActiveRecord` 4.
49
+ - [#413](https://github.com/airblade/paper_trail/issues/413) - Utilize [RequestStore](https://github.com/steveklabnik/request_store)
50
+ to ensure that the `PaperTrail.whodunnit` is set in a thread safe manner within Rails & Sinatra.
27
51
  - [#399](https://github.com/airblade/paper_trail/pull/399) - Add `:dup` argument for options hash to `reify` which forces a new model instance.
28
52
  - [#394](https://github.com/airblade/paper_trail/pull/394) - Add RSpec matcher `have_a_version_with` for easier testing.
29
53
  - [#391](https://github.com/airblade/paper_trail/issues/391) - `object_changes` value should dump to `YAML` as a normal `Hash`
@@ -38,6 +62,18 @@ If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually l
38
62
  the gem is used with `Rails`.
39
63
  - Methods handling serialized attributes should fallback to the currently set Serializer instead of always falling back
40
64
  to `PaperTrail::Serializers::YAML`.
65
+ - Both `PaperTrail.config` and `PaperTrail.configure` are now identical, and will both return the `PaperTrail::Config`
66
+ instance and also yield it if a block is provided.
67
+
68
+ ## 3.0.8
69
+
70
+ - [#525](https://github.com/airblade/paper_trail/issues/525) / [#512](https://github.com/airblade/paper_trail/pull/512) -
71
+ Support for virtual accessors and redefined setter and getter methods.
72
+
73
+ ## 3.0.7
74
+
75
+ - [#404](https://github.com/airblade/paper_trail/issues/404) / [#428](https://github.com/airblade/paper_trail/issues/428) -
76
+ Fix errors occuring when a user attempts to update the inheritance column on an STI model instance in `ActiveRecord` 4.1.x
41
77
 
42
78
  ## 3.0.6
43
79
 
@@ -46,7 +82,7 @@ If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually l
46
82
 
47
83
  ## 3.0.5
48
84
 
49
- - [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406)
85
+ - [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406) -
50
86
  `PaperTrail::Version` class is not loaded via a `Rails::Engine`, even when the gem is used with in Rails. This feature has
51
87
  will be re-introduced in version `4.0`.
52
88
  - [#398](https://github.com/airblade/paper_trail/pull/398) - Only require the `RSpec` helper if `RSpec::Core` is required.
data/README.md CHANGED
@@ -44,7 +44,7 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/
44
44
 
45
45
  1. Add PaperTrail to your `Gemfile`.
46
46
 
47
- `gem 'paper_trail', '~> 3.0.6'`
47
+ `gem 'paper_trail', '~> 4.0.0.rc'`
48
48
 
49
49
  2. Generate a migration which will add a `versions` table to your database.
50
50
 
@@ -66,7 +66,7 @@ your applications `ActiveRecord` connection in a manner similar to the way `Rail
66
66
 
67
67
  1. Add PaperTrail to your `Gemfile`.
68
68
 
69
- `gem 'paper_trail', '~> 3.0.6'`
69
+ `gem 'paper_trail', '~> 4.0.0.rc'`
70
70
 
71
71
  2. Generate a migration to add a `versions` table to your database.
72
72
 
@@ -117,7 +117,7 @@ widget.version
117
117
  widget.live?
118
118
 
119
119
  # Returns who put the widget into its current state.
120
- widget.originator
120
+ widget.paper_trail_originator
121
121
 
122
122
  # Returns the widget (not a version) as it looked at the given timestamp.
123
123
  widget.version_at(timestamp)
@@ -152,7 +152,7 @@ version.reify(options = {})
152
152
  version.reify(dup: true)
153
153
 
154
154
  # Returns who put the item into the state stored in this version.
155
- version.originator
155
+ version.paper_trail_originator
156
156
 
157
157
  # Returns who changed the item from the state it had in this version.
158
158
  version.terminator
@@ -473,7 +473,7 @@ And you can perform `WHERE` queries for object versions based on attributes:
473
473
 
474
474
  ## Finding Out Who Was Responsible For A Change
475
475
 
476
- If your `ApplicationController` has a `current_user` method, PaperTrail will store the value it returns in the version's `whodunnit` column. Note that this column is of type `String`, so you will have to convert it to an integer if it's an id and you want to look up the user later on:
476
+ If your `ApplicationController` has a `current_user` method, PaperTrail will attempt to store the value returned by `current_user.id` in the version's `whodunnit` column (if it returns). Note that this column is of type `String`, so you will have to convert it to an integer if it's an id and you want to look up the user later on:
477
477
 
478
478
  ```ruby
479
479
  >> last_change = widget.versions.last
@@ -529,22 +529,22 @@ Sometimes you want to define who is responsible for a change in a small scope wi
529
529
 
530
530
  A version's `whodunnit` records who changed the object causing the `version` to be stored. Because a version stores the object as it looked before the change (see the table above), `whodunnit` returns who stopped the object looking like this -- not who made it look like this. Hence `whodunnit` is aliased as `terminator`.
531
531
 
532
- To find out who made a version's object look that way, use `version.originator`. And to find out who made a "live" object look like it does, use `originator` on the object.
532
+ To find out who made a version's object look that way, use `version.paper_trail_originator`. And to find out who made a "live" object look like it does, call `paper_trail_originator` on the object.
533
533
 
534
534
  ```ruby
535
535
  >> widget = Widget.find 153 # assume widget has 0 versions
536
536
  >> PaperTrail.whodunnit = 'Alice'
537
537
  >> widget.update_attributes :name => 'Yankee'
538
- >> widget.originator # 'Alice'
538
+ >> widget..paper_trail_originator # 'Alice'
539
539
  >> PaperTrail.whodunnit = 'Bob'
540
540
  >> widget.update_attributes :name => 'Zulu'
541
- >> widget.originator # 'Bob'
541
+ >> widget.paper_trail_originator # 'Bob'
542
542
  >> first_version, last_version = widget.versions.first, widget.versions.last
543
543
  >> first_version.whodunnit # 'Alice'
544
- >> first_version.originator # nil
544
+ >> first_version.paper_trail_originator # nil
545
545
  >> first_version.terminator # 'Alice'
546
546
  >> last_version.whodunnit # 'Bob'
547
- >> last_version.originator # 'Alice'
547
+ >> last_version.paper_trail_originator # 'Alice'
548
548
  >> last_version.terminator # 'Bob'
549
549
  ```
550
550
 
@@ -570,7 +570,7 @@ If you are using Postgres, you should also define the sequence that your custom
570
570
  ```ruby
571
571
  class PostVersion < PaperTrail::Version
572
572
  self.table_name = :post_versions
573
- self.sequence_name = :post_version_id_seq
573
+ self.sequence_name = :post_versions_id_seq
574
574
  end
575
575
  ```
576
576
 
@@ -655,7 +655,7 @@ If the parent and child are updated in one go, PaperTrail can use the aforementi
655
655
  >> t.location.latitude # 12.345, instead of 54.321
656
656
  ```
657
657
 
658
- By default, PaperTrail excludes an associated record from the reified parent model if the associated record exists in the live model but did not exist as at the time the version was created. This is usually what you want if you just want to look at the reified version. But if you want to persist it, it would be better to pass in option `:mark_for_destruction => true` so that the associated record is included and marked for destruction.
658
+ By default, PaperTrail excludes an associated record from the reified parent model if the associated record exists in the live model but did not exist as at the time the version was created. This is usually what you want if you just want to look at the reified version. But if you want to persist it, it would be better to pass in option `:mark_for_destruction => true` so that the associated record is included and marked for destruction. Note that `mark_for_destruction` only has [an effect on associations marked with `autosave: true`](http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-mark_for_destruction).
659
659
 
660
660
  ```ruby
661
661
  class Widget < ActiveRecord::Base
@@ -844,6 +844,7 @@ For diffing two ActiveRecord objects:
844
844
  * [Jeremy Weiskotten's PaperTrail fork](http://github.com/jeremyw/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb#L151-156): uses ActiveSupport's diff to return an array of hashes of the changes.
845
845
  * [activerecord-diff](http://github.com/tim/activerecord-diff): rather like ActiveRecord::Dirty but also allows you to specify which columns to compare.
846
846
 
847
+ 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.
847
848
 
848
849
  ## Turning PaperTrail Off/On
849
850
 
@@ -951,6 +952,23 @@ A valid serializer is a `module` (or `class`) that defines a `load` and `dump` m
951
952
  * [PaperTrail::Serializers::YAML](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/yaml.rb) - Default
952
953
  * [PaperTrail::Serializers::JSON](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/json.rb)
953
954
 
955
+ ### PostgreSQL JSON column type support
956
+
957
+ If you use PostgreSQL, and would like to store your `object` (and/or `object_changes`) data in a column of
958
+ [type `JSON` or type `JSONB`](http://www.postgresql.org/docs/9.4/static/datatype-json.html),
959
+ specify `json` instead of `text` for these columns in your migration:
960
+
961
+ ```ruby
962
+ create_table :versions do |t|
963
+ ...
964
+ t.json :object # Full object changes
965
+ t.json :object_changes # Optional column-level changes
966
+ ...
967
+ end
968
+ ```
969
+
970
+ Note: You don't need to use a particular serializer for the PostgreSQL `JSON` column type.
971
+
954
972
  ## SerializedAttributes support
955
973
 
956
974
  PaperTrail has a config option that can be used to enable/disable whether PaperTrail attempts to utilize
data/Rakefile CHANGED
@@ -23,7 +23,7 @@ Rake::TestTask.new(:test) do |t|
23
23
  end
24
24
 
25
25
  require 'rspec/core/rake_task'
26
- desc 'Run PaperTrail specs for the RSpec helper.'
26
+ desc 'Run tests on PaperTrail with RSpec'
27
27
  RSpec::Core::RakeTask.new(:spec)
28
28
 
29
29
  desc 'Default: run all available test suites'
data/gemfiles/3.0.gemfile CHANGED
@@ -2,11 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'activerecord', '~> 3.0'
4
4
  gem 'i18n', '~> 0.6.11'
5
+ gem 'request_store', '~> 1.1.0'
5
6
 
6
7
  group :development, :test do
7
8
  gem 'rake', '~> 10.1.1'
8
9
  gem 'shoulda', '~> 3.5'
9
- gem 'ffaker', '>= 1.15'
10
+ gem 'ffaker', '<= 1.31.0'
10
11
 
11
12
  # Testing of Rails
12
13
  gem 'railties', '~> 3.0'
@@ -46,5 +47,6 @@ group :development, :test do
46
47
  gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
47
48
  gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
48
49
  gem 'activerecord-jdbcmysql-adapter', '~> 1.3'
50
+ gem 'activerecord-jdbc-adapter', '1.3.15'
49
51
  end
50
52
  end
@@ -30,8 +30,11 @@ module PaperTrail
30
30
  def add_paper_trail_migration(template)
31
31
  migration_dir = File.expand_path('db/migrate')
32
32
 
33
- if !self.class.migration_exists?(migration_dir, template)
33
+ unless self.class.migration_exists?(migration_dir, template)
34
34
  migration_template "#{template}.rb", "db/migrate/#{template}.rb"
35
+ else
36
+ warn("ALERT: Migration already exists named '#{template}'." +
37
+ " Please check your migrations directory before re-running")
35
38
  end
36
39
  end
37
40
  end
@@ -25,7 +25,7 @@ module PaperTrail
25
25
  # Returns a hash of versions grouped by the `item_id` attribute formatted like this: {:item_id => PaperTrail::Version}.
26
26
  # If `item_id` or `date` is set, versions will be narrowed to those pointing at items with those ids that were created on specified date.
27
27
  def gather_versions(item_id = nil, date = :all)
28
- raise "`date` argument must receive a Timestamp or `:all`" unless date == :all || date.respond_to?(:to_date)
28
+ raise ArgumentError.new("`date` argument must receive a Timestamp or `:all`") unless date == :all || date.respond_to?(:to_date)
29
29
  versions = item_id ? PaperTrail::Version.where(:item_id => item_id) : PaperTrail::Version
30
30
  versions = versions.between(date.to_date, date.to_date + 1.day) unless date == :all
31
31
  versions = PaperTrail::Version.all if versions == PaperTrail::Version # if versions has not been converted to an ActiveRecord::Relation yet, do so now
@@ -5,6 +5,7 @@ module PaperTrail
5
5
  include Singleton
6
6
  attr_accessor :enabled, :timestamp_field, :serializer, :version_limit
7
7
  attr_reader :serialized_attributes
8
+ attr_writer :track_associations
8
9
 
9
10
  def initialize
10
11
  @enabled = true # Indicates whether PaperTrail is on or off.
@@ -27,5 +28,10 @@ module PaperTrail
27
28
  end
28
29
  @serialized_attributes = value
29
30
  end
31
+
32
+ def track_associations
33
+ @track_associations ||= PaperTrail::VersionAssociation.table_exists?
34
+ end
35
+ alias_method :track_associations?, :track_associations
30
36
  end
31
37
  end
@@ -5,6 +5,7 @@ module PaperTrail
5
5
 
6
6
  # Register this module inside your Sinatra application to gain access to controller-level methods used by PaperTrail
7
7
  def self.registered(app)
8
+ app.use RequestStore::Middleware
8
9
  app.helpers self
9
10
  app.before { set_paper_trail_whodunnit }
10
11
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/object' # provides the `try` method
2
+
1
3
  module PaperTrail
2
4
  module Model
3
5
 
@@ -10,26 +12,28 @@ module PaperTrail
10
12
  # the model is available in the `versions` association.
11
13
  #
12
14
  # Options:
13
- # :on the events to track (optional; defaults to all of them). Set to an array of
14
- # `:create`, `:update`, `:destroy` as desired.
15
- # :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`.
16
- # :ignore an array of attributes for which a new `Version` will not be created if only they change.
17
- # it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
18
- # which will only be ignored if the value is a `Proc` which returns truthily.
19
- # :if, :unless Procs that allow to specify conditions when to save versions for an object
20
- # :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
21
- # it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
22
- # which will only be counted if the value is a `Proc` which returns truthily.
23
- # :skip fields to ignore completely. As with `ignore`, updates to these fields will not create
24
- # a new `Version`. In addition, these fields will not be included in the serialized versions
25
- # of the object whenever a new `Version` is created.
26
- # :meta a hash of extra data to store. You must add a column to the `versions` table for each key.
27
- # Values are objects or procs (which are called with `self`, i.e. the model with the paper
28
- # trail). See `PaperTrail::Controller.info_for_paper_trail` for how to store data from
29
- # the controller.
30
- # :versions the name to use for the versions association. Default is `:versions`.
31
- # :version the name to use for the method which returns the version the instance was reified from.
32
- # Default is `:version`.
15
+ # :on the events to track (optional; defaults to all of them). Set to an array of
16
+ # `:create`, `:update`, `:destroy` as desired.
17
+ # :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`.
18
+ # :ignore an array of attributes for which a new `Version` will not be created if only they change.
19
+ # it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
20
+ # which will only be ignored if the value is a `Proc` which returns truthily.
21
+ # :if, :unless Procs that allow to specify conditions when to save versions for an object
22
+ # :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
23
+ # it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
24
+ # which will only be counted if the value is a `Proc` which returns truthily.
25
+ # :skip fields to ignore completely. As with `ignore`, updates to these fields will not create
26
+ # a new `Version`. In addition, these fields will not be included in the serialized versions
27
+ # of the object whenever a new `Version` is created.
28
+ # :meta a hash of extra data to store. You must add a column to the `versions` table for each key.
29
+ # Values are objects or procs (which are called with `self`, i.e. the model with the paper
30
+ # trail). See `PaperTrail::Controller.info_for_paper_trail` for how to store data from
31
+ # the controller.
32
+ # :versions the name to use for the versions association. Default is `:versions`.
33
+ # :version the name to use for the method which returns the version the instance was reified from.
34
+ # Default is `:version`.
35
+ # :save_changes whether or not to save changes to the object_changes column if it exists. Default is true
36
+ #
33
37
  def has_paper_trail(options = {})
34
38
  # Lazily include the instance methods so we don't clutter up
35
39
  # any more ActiveRecord models than we have to.
@@ -53,6 +57,7 @@ module PaperTrail
53
57
  end
54
58
 
55
59
  paper_trail_options[:meta] ||= {}
60
+ paper_trail_options[:save_changes] = true if paper_trail_options[:save_changes].nil?
56
61
 
57
62
  class_attribute :versions_association_name
58
63
  self.versions_association_name = options[:versions] || :versions
@@ -70,14 +75,15 @@ module PaperTrail
70
75
  :order => self.paper_trail_version_class.timestamp_sort_order
71
76
  end
72
77
 
78
+ options[:on] ||= [:create, :update, :destroy]
73
79
  options_on = Array(options[:on]) # so that a single symbol can be passed in without wrapping it in an `Array`
74
- after_create :record_create, :if => :save_version? if options_on.empty? || options_on.include?(:create)
75
- if options_on.empty? || options_on.include?(:update)
80
+ after_create :record_create, :if => :save_version? if options_on.include?(:create)
81
+ if options_on.include?(:update)
76
82
  before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
77
83
  after_update :record_update, :if => :save_version?
78
84
  after_update :clear_version_instance!
79
85
  end
80
- after_destroy :record_destroy, :if => :save_version? if options_on.empty? || options_on.include?(:destroy)
86
+ after_destroy :record_destroy, :if => :save_version? if options_on.include?(:destroy)
81
87
 
82
88
  # Reset the transaction id when the transaction is closed
83
89
  after_commit :reset_transaction_id
@@ -115,7 +121,7 @@ module PaperTrail
115
121
  end
116
122
 
117
123
  # Used for Version#object attribute
118
- def serialize_attributes_for_paper_trail(attributes)
124
+ def serialize_attributes_for_paper_trail!(attributes)
119
125
  # don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
120
126
  return attributes if self.paper_trail_version_class.object_col_is_json?
121
127
 
@@ -128,7 +134,7 @@ module PaperTrail
128
134
  end
129
135
  end
130
136
 
131
- def unserialize_attributes_for_paper_trail(attributes)
137
+ def unserialize_attributes_for_paper_trail!(attributes)
132
138
  # don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
133
139
  return attributes if self.paper_trail_version_class.object_col_is_json?
134
140
 
@@ -141,7 +147,7 @@ module PaperTrail
141
147
  end
142
148
 
143
149
  # Used for Version#object_changes attribute
144
- def serialize_attribute_changes(changes)
150
+ def serialize_attribute_changes_for_paper_trail!(changes)
145
151
  # don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
146
152
  return changes if self.paper_trail_version_class.object_changes_col_is_json?
147
153
 
@@ -156,7 +162,7 @@ module PaperTrail
156
162
  end
157
163
  end
158
164
 
159
- def unserialize_attribute_changes(changes)
165
+ def unserialize_attribute_changes_for_paper_trail!(changes)
160
166
  # don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
161
167
  return changes if self.paper_trail_version_class.object_changes_col_is_json?
162
168
 
@@ -181,10 +187,15 @@ module PaperTrail
181
187
  end
182
188
 
183
189
  # Returns who put the object into its current state.
184
- def originator
190
+ def paper_trail_originator
185
191
  (source_version || send(self.class.versions_association_name).last).try(:whodunnit)
186
192
  end
187
193
 
194
+ def originator
195
+ warn "DEPRECATED: use `paper_trail_originator` instead of `originator`. Support for `originator` will be removed in PaperTrail 4.0"
196
+ self.paper_trail_originator
197
+ end
198
+
188
199
  # Invoked after rollbacks to ensure versions records are not created
189
200
  # for changes that never actually took place
190
201
  def clear_rolled_back_versions
@@ -268,7 +279,9 @@ module PaperTrail
268
279
  current_time = current_time_from_proper_timezone
269
280
 
270
281
  attributes.each { |column| write_attribute(column, current_time) }
271
- save!
282
+ # ensure a version is written even if the `:on` collection is empty
283
+ record_update(true) if paper_trail_options[:on] == []
284
+ save!(:validate => false)
272
285
  end
273
286
 
274
287
  private
@@ -280,13 +293,13 @@ module PaperTrail
280
293
  def record_create
281
294
  if paper_trail_switched_on?
282
295
  data = {
283
- :event => paper_trail_event || 'create',
284
- :whodunnit => PaperTrail.whodunnit
296
+ :event => paper_trail_event || 'create',
297
+ :whodunnit => PaperTrail.whodunnit
285
298
  }
286
299
  if respond_to?(:created_at)
287
300
  data[PaperTrail.timestamp_field] = created_at
288
301
  end
289
- if changed_notably? and self.class.paper_trail_version_class.column_names.include?('object_changes')
302
+ if paper_trail_options[:save_changes] && changed_notably? && self.class.paper_trail_version_class.column_names.include?('object_changes')
290
303
  data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
291
304
  PaperTrail.serializer.dump(changes_for_paper_trail)
292
305
  end
@@ -299,18 +312,18 @@ module PaperTrail
299
312
  end
300
313
  end
301
314
 
302
- def record_update
303
- if paper_trail_switched_on? && changed_notably?
304
- object_attrs = object_attrs_for_paper_trail(item_before_change)
315
+ def record_update(force = nil)
316
+ if paper_trail_switched_on? && (force || changed_notably?)
317
+ object_attrs = object_attrs_for_paper_trail(attributes_before_change)
305
318
  data = {
306
- :event => paper_trail_event || 'update',
307
- :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
308
- :whodunnit => PaperTrail.whodunnit
319
+ :event => paper_trail_event || 'update',
320
+ :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
321
+ :whodunnit => PaperTrail.whodunnit
309
322
  }
310
323
  if respond_to?(:updated_at)
311
324
  data[PaperTrail.timestamp_field] = updated_at
312
325
  end
313
- if self.class.paper_trail_version_class.column_names.include?('object_changes')
326
+ if paper_trail_options[:save_changes] && self.class.paper_trail_version_class.column_names.include?('object_changes')
314
327
  data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
315
328
  PaperTrail.serializer.dump(changes_for_paper_trail)
316
329
  end
@@ -326,7 +339,7 @@ module PaperTrail
326
339
  def changes_for_paper_trail
327
340
  _changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
328
341
  if PaperTrail.serialized_attributes?
329
- self.class.serialize_attribute_changes(_changes)
342
+ self.class.serialize_attribute_changes_for_paper_trail!(_changes)
330
343
  end
331
344
  _changes.to_hash
332
345
  end
@@ -350,13 +363,13 @@ module PaperTrail
350
363
 
351
364
  def record_destroy
352
365
  if paper_trail_switched_on? and not new_record?
353
- object_attrs = object_attrs_for_paper_trail(item_before_change)
366
+ object_attrs = object_attrs_for_paper_trail(attributes_before_change)
354
367
  data = {
355
- :item_id => self.id,
356
- :item_type => self.class.base_class.name,
357
- :event => paper_trail_event || 'destroy',
358
- :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
359
- :whodunnit => PaperTrail.whodunnit
368
+ :item_id => self.id,
369
+ :item_type => self.class.base_class.name,
370
+ :event => paper_trail_event || 'destroy',
371
+ :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
372
+ :whodunnit => PaperTrail.whodunnit
360
373
  }
361
374
  if self.class.paper_trail_version_class.column_names.include?('transaction_id')
362
375
  data[:transaction_id] = PaperTrail.transaction_id
@@ -371,15 +384,23 @@ module PaperTrail
371
384
 
372
385
  # saves associations if the join table for `VersionAssociation` exists
373
386
  def save_associations(version)
374
- return unless PaperTrail::VersionAssociation.table_exists?
387
+ return unless PaperTrail.config.track_associations?
375
388
  self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
376
- if assoc.klass.paper_trail_enabled_for_model?
377
- PaperTrail::VersionAssociation.create(
389
+ assoc_version_args = {
378
390
  :version_id => version.id,
379
- :foreign_key_name => assoc.foreign_key,
380
- :foreign_key_id => self.send(assoc.foreign_key)
381
- )
391
+ :foreign_key_name => assoc.foreign_key
392
+ }
393
+
394
+ if assoc.options[:polymorphic]
395
+ associated_record = send(assoc.name) if send(assoc.foreign_type)
396
+ if associated_record && associated_record.class.paper_trail_enabled_for_model?
397
+ assoc_version_args.merge!(:foreign_key_id => associated_record.id)
398
+ end
399
+ elsif assoc.klass.paper_trail_enabled_for_model?
400
+ assoc_version_args.merge!(:foreign_key_id => send(assoc.foreign_key))
382
401
  end
402
+
403
+ PaperTrail::VersionAssociation.create(assoc_version_args) if assoc_version_args.has_key?(:foreign_key_id)
383
404
  end
384
405
  end
385
406
 
@@ -403,7 +424,7 @@ module PaperTrail
403
424
  if v.respond_to?(:call)
404
425
  v.call(self)
405
426
  elsif v.is_a?(Symbol) && respond_to?(v)
406
- # if it is an attribute that is changing in an existing object,
427
+ # if it is an attribute that is changing in an existing object,
407
428
  # be sure to grab the current version
408
429
  if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] != 'create'
409
430
  send("#{v}_was".to_sym)
@@ -418,17 +439,9 @@ module PaperTrail
418
439
  data.merge(PaperTrail.controller_info || {})
419
440
  end
420
441
 
421
- def item_before_change
422
- previous = self.dup
423
- # `dup` clears timestamps so we add them back.
424
- all_timestamp_attributes.each do |column|
425
- if self.class.column_names.include?(column.to_s) and not send("#{column}_was").nil?
426
- previous[column] = send("#{column}_was")
427
- end
428
- end
429
- enums = previous.respond_to?(:defined_enums) ? previous.defined_enums : {}
430
- previous.tap do |prev|
431
- prev.id = id # `dup` clears the `id` so we add that back
442
+ def attributes_before_change
443
+ attributes.tap do |prev|
444
+ enums = self.respond_to?(:defined_enums) ? self.defined_enums : {}
432
445
  changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
433
446
  before = enums[attr][before] if enums[attr]
434
447
  prev[attr] = before
@@ -438,10 +451,10 @@ module PaperTrail
438
451
 
439
452
  # returns hash of attributes (with appropriate attributes serialized),
440
453
  # ommitting attributes to be skipped
441
- def object_attrs_for_paper_trail(object)
442
- attrs = object.attributes.except(*self.paper_trail_options[:skip])
454
+ def object_attrs_for_paper_trail(attributes_hash)
455
+ attrs = attributes_hash.except(*self.paper_trail_options[:skip])
443
456
  if PaperTrail.serialized_attributes?
444
- self.class.serialize_attributes_for_paper_trail(attrs)
457
+ self.class.serialize_attributes_for_paper_trail!(attrs)
445
458
  end
446
459
  attrs
447
460
  end
@@ -23,7 +23,8 @@ module PaperTrail
23
23
  # in the serialized object_changes
24
24
  def where_object_changes_condition(arel_field, field, value)
25
25
  # Need to check first (before) and secondary (after) fields
26
- if defined?(::YAML::ENGINE) && ::YAML::ENGINE.yamler == 'psych'
26
+ if (defined?(::YAML::ENGINE) && ::YAML::ENGINE.yamler == 'psych') ||
27
+ (defined?(::Psych) && ::YAML == ::Psych)
27
28
  arel_field.matches("%\n#{field}:\n- #{value}\n%").
28
29
  or(arel_field.matches("%\n#{field}:\n-%\n- #{value}\n%"))
29
30
  else # Syck adds extra spaces into array dumps