paper_trail 3.0.9 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -2
  4. data/.travis.yml +5 -0
  5. data/CHANGELOG.md +37 -23
  6. data/README.md +170 -63
  7. data/gemfiles/3.0.gemfile +10 -4
  8. data/lib/generators/paper_trail/install_generator.rb +19 -3
  9. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
  10. data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
  11. data/lib/paper_trail.rb +24 -4
  12. data/lib/paper_trail/cleaner.rb +3 -3
  13. data/lib/paper_trail/config.rb +17 -0
  14. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
  15. data/lib/paper_trail/frameworks/rails.rb +1 -0
  16. data/lib/paper_trail/frameworks/rspec.rb +5 -0
  17. data/lib/paper_trail/has_paper_trail.rb +112 -38
  18. data/lib/paper_trail/version_association_concern.rb +13 -0
  19. data/lib/paper_trail/version_concern.rb +145 -38
  20. data/lib/paper_trail/version_number.rb +3 -3
  21. data/paper_trail.gemspec +11 -4
  22. data/spec/generators/install_generator_spec.rb +4 -4
  23. data/spec/models/fluxor_spec.rb +19 -0
  24. data/spec/models/gadget_spec.rb +10 -10
  25. data/spec/models/joined_version_spec.rb +9 -9
  26. data/spec/models/post_with_status_spec.rb +3 -3
  27. data/spec/models/version_spec.rb +49 -71
  28. data/spec/models/widget_spec.rb +124 -71
  29. data/spec/modules/version_concern_spec.rb +8 -8
  30. data/spec/modules/version_number_spec.rb +16 -16
  31. data/spec/paper_trail_spec.rb +17 -17
  32. data/spec/rails_helper.rb +34 -0
  33. data/spec/requests/articles_spec.rb +11 -11
  34. data/spec/spec_helper.rb +77 -36
  35. data/test/dummy/app/models/animal.rb +0 -2
  36. data/test/dummy/app/models/book.rb +4 -0
  37. data/test/dummy/app/models/customer.rb +4 -0
  38. data/test/dummy/app/models/editor.rb +4 -0
  39. data/test/dummy/app/models/editorship.rb +5 -0
  40. data/test/dummy/app/models/line_item.rb +4 -0
  41. data/test/dummy/app/models/order.rb +5 -0
  42. data/test/dummy/app/models/person.rb +1 -1
  43. data/test/dummy/app/models/post.rb +0 -1
  44. data/test/dummy/app/models/song.rb +0 -20
  45. data/test/dummy/app/models/widget.rb +4 -0
  46. data/test/dummy/config/application.rb +3 -0
  47. data/test/dummy/config/initializers/paper_trail.rb +1 -1
  48. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +41 -0
  49. data/test/dummy/db/schema.rb +95 -25
  50. data/test/dummy/public/404.html +26 -0
  51. data/test/dummy/public/422.html +26 -0
  52. data/test/dummy/public/500.html +26 -0
  53. data/test/dummy/public/favicon.ico +0 -0
  54. data/test/dummy/public/javascripts/application.js +2 -0
  55. data/test/dummy/public/javascripts/controls.js +965 -0
  56. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  57. data/test/dummy/public/javascripts/effects.js +1123 -0
  58. data/test/dummy/public/javascripts/rails.js +175 -0
  59. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  60. data/test/test_helper.rb +2 -2
  61. data/test/time_travel_helper.rb +15 -0
  62. data/test/unit/model_test.rb +613 -185
  63. data/test/unit/serializer_test.rb +3 -3
  64. metadata +104 -54
  65. data/spec/models/animal_spec.rb +0 -19
  66. data/test/dummy/public/javascripts/prototype.js +0 -6001
data/gemfiles/3.0.gemfile CHANGED
@@ -6,7 +6,7 @@ gem 'i18n', '~> 0.6.11'
6
6
  group :development, :test do
7
7
  gem 'rake', '~> 10.1.1'
8
8
  gem 'shoulda', '~> 3.5'
9
- gem 'ffaker', '<= 1.31.0'
9
+ gem 'ffaker', '>= 1.15'
10
10
 
11
11
  # Testing of Rails
12
12
  gem 'railties', '~> 3.0'
@@ -16,16 +16,23 @@ group :development, :test do
16
16
  gem 'rack-test', '>= 0.6'
17
17
 
18
18
  # RSpec testing
19
- gem 'rspec-rails', '~> 2.14'
19
+ gem 'rspec-rails', '~> 3.1.0'
20
20
  gem 'generator_spec'
21
21
 
22
22
  # To do proper transactional testing with ActiveSupport::TestCase on MySQL
23
23
  gem 'database_cleaner', '~> 1.2.0'
24
24
 
25
+ # Allow time travel in testing. timecop is only supported after 1.9.2 but does a better cleanup at 'return'
26
+ if RUBY_VERSION < "1.9.2"
27
+ gem 'delorean'
28
+ else
29
+ gem 'timecop'
30
+ end
31
+
25
32
  platforms :ruby do
26
33
  gem 'sqlite3', '~> 1.2'
27
34
  gem 'mysql2', '~> 0.3'
28
- gem 'pg', '~> 0.17.1'
35
+ gem 'pg', '~> 0.17'
29
36
  end
30
37
 
31
38
  platforms :jruby, :ruby_18 do
@@ -39,6 +46,5 @@ group :development, :test do
39
46
  gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
40
47
  gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
41
48
  gem 'activerecord-jdbcmysql-adapter', '~> 1.3'
42
- gem 'activerecord-jdbc-adapter', '1.3.15'
43
49
  end
44
50
  end
@@ -6,17 +6,33 @@ module PaperTrail
6
6
  include ::Rails::Generators::Migration
7
7
 
8
8
  source_root File.expand_path('../templates', __FILE__)
9
- class_option :with_changes, :type => :boolean, :default => false, :desc => "Store changeset (diff) with each version"
9
+ class_option :with_changes, :type => :boolean, :default => false,
10
+ :desc => "Store changeset (diff) with each version"
11
+ class_option :with_associations, :type => :boolean, :default => false,
12
+ :desc => "Store transactional IDs to support association restoration"
10
13
 
11
14
  desc 'Generates (but does not run) a migration to add a versions table.'
12
15
 
13
16
  def create_migration_file
14
- migration_template 'create_versions.rb', 'db/migrate/create_versions.rb'
15
- migration_template 'add_object_changes_to_versions.rb', 'db/migrate/add_object_changes_to_versions.rb' if options.with_changes?
17
+ add_paper_trail_migration('create_versions')
18
+ add_paper_trail_migration('add_object_changes_to_versions') if options.with_changes?
19
+ if options.with_associations?
20
+ add_paper_trail_migration('create_version_associations')
21
+ add_paper_trail_migration('add_transaction_id_column_to_versions')
22
+ end
16
23
  end
17
24
 
18
25
  def self.next_migration_number(dirname)
19
26
  ::ActiveRecord::Generators::Base.next_migration_number(dirname)
20
27
  end
28
+
29
+ protected
30
+ def add_paper_trail_migration(template)
31
+ migration_dir = File.expand_path('db/migrate')
32
+
33
+ if !self.class.migration_exists?(migration_dir, template)
34
+ migration_template "#{template}.rb", "db/migrate/#{template}.rb"
35
+ end
36
+ end
21
37
  end
22
38
  end
@@ -0,0 +1,11 @@
1
+ class AddTransactionIdColumnToVersions < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :versions, :transaction_id, :integer
4
+ add_index :versions, [:transaction_id]
5
+ end
6
+
7
+ def self.down
8
+ remove_index :versions, [:transaction_id]
9
+ remove_column :versions, :transaction_id
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ class CreateVersionAssociations < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :version_associations do |t|
4
+ t.integer :version_id
5
+ t.string :foreign_key_name, :null => false
6
+ t.integer :foreign_key_id
7
+ end
8
+ add_index :version_associations, [:version_id]
9
+ add_index :version_associations, [:foreign_key_name, :foreign_key_id], :name => 'index_version_associations_on_foreign_key'
10
+ end
11
+
12
+ def self.down
13
+ remove_index :version_associations, [:version_id]
14
+ remove_index :version_associations, :name => 'index_version_associations_on_foreign_key'
15
+ drop_table :version_associations
16
+ end
17
+ end
data/lib/paper_trail.rb CHANGED
@@ -22,6 +22,13 @@ module PaperTrail
22
22
  !!PaperTrail.config.enabled
23
23
  end
24
24
 
25
+ # ActiveRecord 5 drops support for serialized attributes; for previous
26
+ # versions of ActiveRecord it is supported, we have a config option
27
+ # to enable it within PaperTrail.
28
+ def self.serialized_attributes?
29
+ !!PaperTrail.config.serialized_attributes && ::ActiveRecord::VERSION::MAJOR < 5
30
+ end
31
+
25
32
  # Sets whether PaperTrail is enabled or disabled for the current request.
26
33
  def self.enabled_for_controller=(value)
27
34
  paper_trail_store[:request_enabled_for_controller] = value
@@ -94,6 +101,18 @@ module PaperTrail
94
101
  @active_record_protected_attributes ||= ::ActiveRecord::VERSION::MAJOR < 4 || !!defined?(ProtectedAttributes)
95
102
  end
96
103
 
104
+ def self.transaction?
105
+ ActiveRecord::Base.connection.open_transactions > 0
106
+ end
107
+
108
+ def self.transaction_id
109
+ paper_trail_store[:transaction_id]
110
+ end
111
+
112
+ def self.transaction_id=(id)
113
+ paper_trail_store[:transaction_id] = id
114
+ end
115
+
97
116
  private
98
117
 
99
118
  # Thread-safe hash to hold PaperTrail's data.
@@ -125,8 +144,9 @@ ActiveSupport.on_load(:active_record) do
125
144
  end
126
145
 
127
146
  # Require frameworks
128
- require 'paper_trail/frameworks/active_record'
129
147
  require 'paper_trail/frameworks/sinatra'
130
- require 'paper_trail/frameworks/rails' if defined? Rails
131
- require 'paper_trail/frameworks/rspec' if defined? RSpec::Core
132
- require 'paper_trail/frameworks/cucumber' if defined? World
148
+ if defined? Rails
149
+ require 'paper_trail/frameworks/rails'
150
+ else
151
+ require 'paper_trail/frameworks/active_record'
152
+ end
@@ -12,10 +12,10 @@ module PaperTrail
12
12
  def clean_versions!(options = {})
13
13
  options = {:keeping => 1, :date => :all}.merge(options)
14
14
  gather_versions(options[:item_id], options[:date]).each do |item_id, versions|
15
- versions.group_by { |v| v.send(PaperTrail.timestamp_field).to_date }.each do |date, versions|
15
+ versions.group_by { |v| v.send(PaperTrail.timestamp_field).to_date }.each do |date, _versions|
16
16
  # remove the number of versions we wish to keep from the collection of versions prior to destruction
17
- versions.pop(options[:keeping])
18
- versions.map(&:destroy)
17
+ _versions.pop(options[:keeping])
18
+ _versions.map(&:destroy)
19
19
  end
20
20
  end
21
21
  end
@@ -4,11 +4,28 @@ module PaperTrail
4
4
  class Config
5
5
  include Singleton
6
6
  attr_accessor :enabled, :timestamp_field, :serializer, :version_limit
7
+ attr_reader :serialized_attributes
7
8
 
8
9
  def initialize
9
10
  @enabled = true # Indicates whether PaperTrail is on or off.
10
11
  @timestamp_field = :created_at
11
12
  @serializer = PaperTrail::Serializers::YAML
13
+
14
+ # This setting only defaults to false on AR 4.2+, because that's when
15
+ # it was deprecated. We want it to function with older versions of
16
+ # ActiveRecord by default.
17
+ if ::ActiveRecord::VERSION::STRING < '4.2'
18
+ @serialized_attributes = true
19
+ end
20
+ end
21
+
22
+ def serialized_attributes=(value)
23
+ if ::ActiveRecord::VERSION::MAJOR >= 5
24
+ warn("DEPRECATED: ActiveRecord 5.0 deprecated `serialized_attributes` " +
25
+ "without replacement, so this PaperTrail config setting does " +
26
+ "nothing with this version, and is always turned off")
27
+ end
28
+ @serialized_attributes = value
12
29
  end
13
30
  end
14
31
  end
@@ -0,0 +1,7 @@
1
+ require 'paper_trail/version_association_concern'
2
+
3
+ module PaperTrail
4
+ class VersionAssociation < ::ActiveRecord::Base
5
+ include PaperTrail::VersionAssociationConcern
6
+ end
7
+ end
@@ -1,4 +1,5 @@
1
1
  require 'paper_trail/frameworks/rails/controller'
2
+ require 'paper_trail/frameworks/rails/engine'
2
3
 
3
4
  module PaperTrail
4
5
  module Rails
@@ -22,3 +22,8 @@ RSpec::Matchers.define :be_versioned do
22
22
  # check to see if the model has `has_paper_trail` declared on it
23
23
  match { |actual| actual.kind_of?(::PaperTrail::Model::InstanceMethods) }
24
24
  end
25
+
26
+ RSpec::Matchers.define :have_a_version_with do |attributes|
27
+ # check if the model has a version with the specified attributes
28
+ match { |actual| actual.versions.where_object(attributes).any? }
29
+ end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/object' # provides the `try` method
2
-
3
1
  module PaperTrail
4
2
  module Model
5
3
 
@@ -76,10 +74,15 @@ module PaperTrail
76
74
  after_create :record_create, :if => :save_version? if options_on.empty? || options_on.include?(:create)
77
75
  if options_on.empty? || options_on.include?(:update)
78
76
  before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
79
- before_update :record_update, :if => :save_version?
77
+ after_update :record_update, :if => :save_version?
80
78
  after_update :clear_version_instance!
81
79
  end
82
80
  after_destroy :record_destroy, :if => :save_version? if options_on.empty? || options_on.include?(:destroy)
81
+
82
+ # Reset the transaction id when the transaction is closed
83
+ after_commit :reset_transaction_id
84
+ after_rollback :reset_transaction_id
85
+ after_rollback :clear_rolled_back_versions
83
86
  end
84
87
 
85
88
  # Switches PaperTrail off for this class.
@@ -103,6 +106,7 @@ module PaperTrail
103
106
  end
104
107
 
105
108
  def paper_trail_enabled_for_model?
109
+ return false unless self.include?(PaperTrail::Model::InstanceMethods)
106
110
  PaperTrail.enabled_for_model?(self)
107
111
  end
108
112
 
@@ -117,7 +121,8 @@ module PaperTrail
117
121
 
118
122
  serialized_attributes.each do |key, coder|
119
123
  if attributes.key?(key)
120
- coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
124
+ # Fall back to current serializer if `coder` has no `dump` method
125
+ coder = PaperTrail.serializer unless coder.respond_to?(:dump)
121
126
  attributes[key] = coder.dump(attributes[key])
122
127
  end
123
128
  end
@@ -129,7 +134,7 @@ module PaperTrail
129
134
 
130
135
  serialized_attributes.each do |key, coder|
131
136
  if attributes.key?(key)
132
- coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump)
137
+ coder = PaperTrail.serializer unless coder.respond_to?(:dump)
133
138
  attributes[key] = coder.load(attributes[key])
134
139
  end
135
140
  end
@@ -142,7 +147,8 @@ module PaperTrail
142
147
 
143
148
  serialized_attributes.each do |key, coder|
144
149
  if changes.key?(key)
145
- coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
150
+ # Fall back to current serializer if `coder` has no `dump` method
151
+ coder = PaperTrail.serializer unless coder.respond_to?(:dump)
146
152
  old_value, new_value = changes[key]
147
153
  changes[key] = [coder.dump(old_value),
148
154
  coder.dump(new_value)]
@@ -156,7 +162,7 @@ module PaperTrail
156
162
 
157
163
  serialized_attributes.each do |key, coder|
158
164
  if changes.key?(key)
159
- coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump)
165
+ coder = PaperTrail.serializer unless coder.respond_to?(:dump)
160
166
  old_value, new_value = changes[key]
161
167
  changes[key] = [coder.load(old_value),
162
168
  coder.load(new_value)]
@@ -175,13 +181,14 @@ module PaperTrail
175
181
  end
176
182
 
177
183
  # Returns who put the object into its current state.
178
- def paper_trail_originator
184
+ def originator
179
185
  (source_version || send(self.class.versions_association_name).last).try(:whodunnit)
180
186
  end
181
187
 
182
- def originator
183
- warn "DEPRECATED: use `paper_trail_originator` instead of `originator`. Support for `originator` will be removed in PaperTrail 4.0"
184
- self.paper_trail_originator
188
+ # Invoked after rollbacks to ensure versions records are not created
189
+ # for changes that never actually took place
190
+ def clear_rolled_back_versions
191
+ send(self.class.versions_association_name).reload
185
192
  end
186
193
 
187
194
  # Returns the object (not a Version) as it was at the given timestamp.
@@ -228,6 +235,16 @@ module PaperTrail
228
235
  self.class.paper_trail_on! if paper_trail_was_enabled
229
236
  end
230
237
 
238
+ # Utility method for reifying. Anything executed inside the block will appear like a new record
239
+ def appear_as_new_record
240
+ instance_eval {
241
+ alias :old_new_record? :new_record?
242
+ alias :new_record? :present?
243
+ }
244
+ yield
245
+ instance_eval { alias :new_record? :old_new_record? }
246
+ end
247
+
231
248
  # Temporarily overwrites the value of whodunnit and then executes the provided block.
232
249
  def whodunnit(value)
233
250
  raise ArgumentError, 'expected to receive a block' unless block_given?
@@ -263,15 +280,20 @@ module PaperTrail
263
280
  def record_create
264
281
  if paper_trail_switched_on?
265
282
  data = {
266
- :event => paper_trail_event || 'create',
267
- :whodunnit => PaperTrail.whodunnit
283
+ :event => paper_trail_event || 'create',
284
+ :whodunnit => PaperTrail.whodunnit,
285
+ :transaction_id => PaperTrail.transaction_id
268
286
  }
269
-
287
+ if respond_to?(:created_at)
288
+ data[PaperTrail.timestamp_field] = created_at
289
+ end
270
290
  if changed_notably? and self.class.paper_trail_version_class.column_names.include?('object_changes')
271
291
  data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
272
292
  PaperTrail.serializer.dump(changes_for_paper_trail)
273
293
  end
274
- send(self.class.versions_association_name).create! merge_metadata(data)
294
+ version = send(self.class.versions_association_name).create! merge_metadata(data)
295
+ set_transaction_id(version)
296
+ save_associations(version)
275
297
  end
276
298
  end
277
299
 
@@ -279,23 +301,30 @@ module PaperTrail
279
301
  if paper_trail_switched_on? && changed_notably?
280
302
  object_attrs = object_attrs_for_paper_trail(item_before_change)
281
303
  data = {
282
- :event => paper_trail_event || 'update',
283
- :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
284
- :whodunnit => PaperTrail.whodunnit
304
+ :event => paper_trail_event || 'update',
305
+ :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
306
+ :whodunnit => PaperTrail.whodunnit,
307
+ :transaction_id => PaperTrail.transaction_id
285
308
  }
286
-
309
+ if respond_to?(:updated_at)
310
+ data[PaperTrail.timestamp_field] = updated_at
311
+ end
287
312
  if self.class.paper_trail_version_class.column_names.include?('object_changes')
288
313
  data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
289
314
  PaperTrail.serializer.dump(changes_for_paper_trail)
290
315
  end
291
- send(self.class.versions_association_name).build merge_metadata(data)
316
+ version = send(self.class.versions_association_name).create merge_metadata(data)
317
+ set_transaction_id(version)
318
+ save_associations(version)
292
319
  end
293
320
  end
294
321
 
295
322
  def changes_for_paper_trail
296
- self.changes.delete_if do |key, value|
297
- !notably_changed.include?(key)
298
- end.tap { |changes| self.class.serialize_attribute_changes(changes) }
323
+ _changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
324
+ if PaperTrail.serialized_attributes?
325
+ self.class.serialize_attribute_changes(_changes)
326
+ end
327
+ _changes.to_hash
299
328
  end
300
329
 
301
330
  # Invoked via`after_update` callback for when a previous version is reified and then saved
@@ -305,24 +334,59 @@ module PaperTrail
305
334
 
306
335
  def reset_timestamp_attrs_for_update_if_needed!
307
336
  return if self.live? # invoked via callback when a user attempts to persist a reified `Version`
308
- timestamp_attributes_for_update_in_model.each { |column| send("reset_#{column}!") }
337
+ timestamp_attributes_for_update_in_model.each do |column|
338
+ # ActiveRecord 4.2 deprecated `reset_column!` in favor of `restore_column!`
339
+ if respond_to?("restore_#{column}!")
340
+ send("restore_#{column}!")
341
+ else
342
+ send("reset_#{column}!")
343
+ end
344
+ end
309
345
  end
310
346
 
311
347
  def record_destroy
312
348
  if paper_trail_switched_on? and not new_record?
313
349
  object_attrs = object_attrs_for_paper_trail(item_before_change)
314
350
  data = {
315
- :item_id => self.id,
316
- :item_type => self.class.base_class.name,
317
- :event => paper_trail_event || 'destroy',
318
- :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
319
- :whodunnit => PaperTrail.whodunnit
351
+ :item_id => self.id,
352
+ :item_type => self.class.base_class.name,
353
+ :event => paper_trail_event || 'destroy',
354
+ :object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
355
+ :whodunnit => PaperTrail.whodunnit,
356
+ :transaction_id => PaperTrail.transaction_id
320
357
  }
321
- send("#{self.class.version_association_name}=", self.class.paper_trail_version_class.create(merge_metadata(data)))
358
+ version = self.class.paper_trail_version_class.create(merge_metadata(data))
359
+ send("#{self.class.version_association_name}=", version)
322
360
  send(self.class.versions_association_name).send :load_target
361
+ set_transaction_id(version)
362
+ save_associations(version)
363
+ end
364
+ end
365
+
366
+ def save_associations(version)
367
+ self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
368
+ if assoc.klass.paper_trail_enabled_for_model?
369
+ PaperTrail::VersionAssociation.create(
370
+ :version_id => version.id,
371
+ :foreign_key_name => assoc.foreign_key,
372
+ :foreign_key_id => self.send(assoc.foreign_key)
373
+ )
374
+ end
375
+ end
376
+ end
377
+
378
+ def set_transaction_id(version)
379
+ if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
380
+ PaperTrail.transaction_id = version.id
381
+ version.transaction_id = version.id
382
+ version.save
323
383
  end
324
384
  end
325
385
 
386
+ def reset_transaction_id
387
+ PaperTrail.transaction_id = nil
388
+ end
389
+
326
390
  def merge_metadata(data)
327
391
  # First we merge the model-level metadata in `meta`.
328
392
  paper_trail_options[:meta].each do |k,v|
@@ -345,8 +409,16 @@ module PaperTrail
345
409
  end
346
410
 
347
411
  def item_before_change
348
- attributes.tap do |prev|
349
- enums = self.respond_to?(:defined_enums) ? self.defined_enums : {}
412
+ previous = self.dup
413
+ # `dup` clears timestamps so we add them back.
414
+ all_timestamp_attributes.each do |column|
415
+ if self.class.column_names.include?(column.to_s) and not send("#{column}_was").nil?
416
+ previous[column] = send("#{column}_was")
417
+ end
418
+ end
419
+ enums = previous.respond_to?(:defined_enums) ? previous.defined_enums : {}
420
+ previous.tap do |prev|
421
+ prev.id = id # `dup` clears the `id` so we add that back
350
422
  changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
351
423
  before = enums[attr][before] if enums[attr]
352
424
  prev[attr] = before
@@ -356,16 +428,18 @@ module PaperTrail
356
428
 
357
429
  # returns hash of attributes (with appropriate attributes serialized),
358
430
  # ommitting attributes to be skipped
359
- def object_attrs_for_paper_trail(attributes_hash)
360
- attributes_hash.except(*self.paper_trail_options[:skip]).tap do |attributes|
361
- self.class.serialize_attributes_for_paper_trail(attributes)
431
+ def object_attrs_for_paper_trail(object)
432
+ attrs = object.attributes.except(*self.paper_trail_options[:skip])
433
+ if PaperTrail.serialized_attributes?
434
+ self.class.serialize_attributes_for_paper_trail(attrs)
362
435
  end
436
+ attrs
363
437
  end
364
438
 
365
439
  # This method is invoked in order to determine whether it is appropriate to generate a new version instance.
366
- # In ActiveRecord 4, the way ActiveModel::Dirty handles the `changes` hash has changed so that
367
- # `timestamp_attributes_for_update` get inserted PRIOR to the persistence happening, so we must accomodate
368
- # this by checking to ensure attributes other than those ignored and update timestamps are really being modified.
440
+ # Because we are now using `after_(create/update/etc)` callbacks, we need to go out of our way to
441
+ # ensure that during updates timestamp attributes are not acknowledged as a notable changes
442
+ # to raise false positives when attributes are ignored.
369
443
  def changed_notably?
370
444
  if self.paper_trail_options[:ignore].any? && (changed & self.paper_trail_options[:ignore]).any?
371
445
  (notably_changed - timestamp_attributes_for_update_in_model.map(&:to_s)).any?