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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rspec +1 -2
- data/.travis.yml +5 -0
- data/CHANGELOG.md +37 -23
- data/README.md +170 -63
- data/gemfiles/3.0.gemfile +10 -4
- data/lib/generators/paper_trail/install_generator.rb +19 -3
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
- data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
- data/lib/paper_trail.rb +24 -4
- data/lib/paper_trail/cleaner.rb +3 -3
- data/lib/paper_trail/config.rb +17 -0
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -0
- data/lib/paper_trail/frameworks/rspec.rb +5 -0
- data/lib/paper_trail/has_paper_trail.rb +112 -38
- data/lib/paper_trail/version_association_concern.rb +13 -0
- data/lib/paper_trail/version_concern.rb +145 -38
- data/lib/paper_trail/version_number.rb +3 -3
- data/paper_trail.gemspec +11 -4
- data/spec/generators/install_generator_spec.rb +4 -4
- data/spec/models/fluxor_spec.rb +19 -0
- data/spec/models/gadget_spec.rb +10 -10
- data/spec/models/joined_version_spec.rb +9 -9
- data/spec/models/post_with_status_spec.rb +3 -3
- data/spec/models/version_spec.rb +49 -71
- data/spec/models/widget_spec.rb +124 -71
- data/spec/modules/version_concern_spec.rb +8 -8
- data/spec/modules/version_number_spec.rb +16 -16
- data/spec/paper_trail_spec.rb +17 -17
- data/spec/rails_helper.rb +34 -0
- data/spec/requests/articles_spec.rb +11 -11
- data/spec/spec_helper.rb +77 -36
- data/test/dummy/app/models/animal.rb +0 -2
- data/test/dummy/app/models/book.rb +4 -0
- data/test/dummy/app/models/customer.rb +4 -0
- data/test/dummy/app/models/editor.rb +4 -0
- data/test/dummy/app/models/editorship.rb +5 -0
- data/test/dummy/app/models/line_item.rb +4 -0
- data/test/dummy/app/models/order.rb +5 -0
- data/test/dummy/app/models/person.rb +1 -1
- data/test/dummy/app/models/post.rb +0 -1
- data/test/dummy/app/models/song.rb +0 -20
- data/test/dummy/app/models/widget.rb +4 -0
- data/test/dummy/config/application.rb +3 -0
- data/test/dummy/config/initializers/paper_trail.rb +1 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +41 -0
- data/test/dummy/db/schema.rb +95 -25
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +2 -0
- data/test/dummy/public/javascripts/controls.js +965 -0
- data/test/dummy/public/javascripts/dragdrop.js +974 -0
- data/test/dummy/public/javascripts/effects.js +1123 -0
- data/test/dummy/public/javascripts/rails.js +175 -0
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/test_helper.rb +2 -2
- data/test/time_travel_helper.rb +15 -0
- data/test/unit/model_test.rb +613 -185
- data/test/unit/serializer_test.rb +3 -3
- metadata +104 -54
- data/spec/models/animal_spec.rb +0 -19
- 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', '
|
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', '~>
|
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
|
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,
|
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
|
-
|
15
|
-
|
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
|
-
|
131
|
-
require 'paper_trail/frameworks/
|
132
|
-
|
148
|
+
if defined? Rails
|
149
|
+
require 'paper_trail/frameworks/rails'
|
150
|
+
else
|
151
|
+
require 'paper_trail/frameworks/active_record'
|
152
|
+
end
|
data/lib/paper_trail/cleaner.rb
CHANGED
@@ -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,
|
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
|
-
|
18
|
-
|
17
|
+
_versions.pop(options[:keeping])
|
18
|
+
_versions.map(&:destroy)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
data/lib/paper_trail/config.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
184
|
+
def originator
|
179
185
|
(source_version || send(self.class.versions_association_name).last).try(:whodunnit)
|
180
186
|
end
|
181
187
|
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
267
|
-
: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
|
283
|
-
:object
|
284
|
-
: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).
|
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
|
-
|
297
|
-
|
298
|
-
|
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
|
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
|
316
|
-
:item_type
|
317
|
-
:event
|
318
|
-
:object
|
319
|
-
: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
|
-
|
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
|
-
|
349
|
-
|
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(
|
360
|
-
|
361
|
-
|
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
|
-
#
|
367
|
-
#
|
368
|
-
#
|
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?
|