paper_trail 4.0.0.rc1 → 4.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -58
  3. data/README.md +528 -346
  4. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
  5. data/lib/generators/paper_trail/templates/create_versions.rb +8 -1
  6. data/lib/paper_trail.rb +2 -2
  7. data/lib/paper_trail/config.rb +15 -4
  8. data/lib/paper_trail/frameworks/active_record.rb +2 -3
  9. data/lib/paper_trail/has_paper_trail.rb +32 -13
  10. data/lib/paper_trail/version_concern.rb +1 -1
  11. data/lib/paper_trail/version_number.rb +1 -1
  12. data/paper_trail.gemspec +1 -1
  13. data/spec/models/gadget_spec.rb +1 -1
  14. data/spec/models/kitchen/banana_spec.rb +14 -0
  15. data/spec/models/not_on_update_spec.rb +19 -0
  16. data/spec/models/skipper_spec.rb +17 -0
  17. data/spec/models/version_spec.rb +5 -8
  18. data/spec/models/widget_spec.rb +5 -8
  19. data/test/dummy/app/models/kitchen/banana.rb +5 -0
  20. data/test/dummy/app/models/not_on_update.rb +4 -0
  21. data/test/dummy/app/models/skipper.rb +6 -0
  22. data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
  23. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +25 -0
  24. data/test/paper_trail_test.rb +7 -0
  25. metadata +18 -24
  26. data/test/dummy/public/404.html +0 -26
  27. data/test/dummy/public/422.html +0 -26
  28. data/test/dummy/public/500.html +0 -26
  29. data/test/dummy/public/favicon.ico +0 -0
  30. data/test/dummy/public/javascripts/application.js +0 -2
  31. data/test/dummy/public/javascripts/controls.js +0 -965
  32. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  33. data/test/dummy/public/javascripts/effects.js +0 -1123
  34. data/test/dummy/public/javascripts/rails.js +0 -175
  35. data/test/dummy/public/stylesheets/.gitkeep +0 -0
@@ -1,5 +1,10 @@
1
1
  class AddObjectChangesToVersions < ActiveRecord::Migration
2
+
3
+ # The largest text column available in all supported RDBMS.
4
+ # See `create_versions.rb` for details.
5
+ TEXT_BYTES = 1_073_741_823
6
+
2
7
  def change
3
- add_column :versions, :object_changes, :text
8
+ add_column :versions, :object_changes, :text, limit: TEXT_BYTES
4
9
  end
5
10
  end
@@ -1,11 +1,18 @@
1
1
  class CreateVersions < ActiveRecord::Migration
2
+
3
+ # The largest text column available in all supported RDBMS is
4
+ # 1024^3 - 1 bytes, roughly one gibibyte. We specify a size
5
+ # so that MySQL will use `longtext` instead of `text`. Otherwise,
6
+ # when serializing very large objects, `text` might not be big enough.
7
+ TEXT_BYTES = 1_073_741_823
8
+
2
9
  def change
3
10
  create_table :versions do |t|
4
11
  t.string :item_type, :null => false
5
12
  t.integer :item_id, :null => false
6
13
  t.string :event, :null => false
7
14
  t.string :whodunnit
8
- t.text :object
15
+ t.text :object, :limit => TEXT_BYTES
9
16
  t.datetime :created_at
10
17
  end
11
18
  add_index :versions, [:item_type, :item_id]
data/lib/paper_trail.rb CHANGED
@@ -104,7 +104,7 @@ module PaperTrail
104
104
  end
105
105
 
106
106
  def self.transaction?
107
- ActiveRecord::Base.connection.open_transactions > 0
107
+ ::ActiveRecord::Base.connection.open_transactions > 0
108
108
  end
109
109
 
110
110
  def self.transaction_id
@@ -149,7 +149,7 @@ end
149
149
 
150
150
  # Require frameworks
151
151
  require 'paper_trail/frameworks/sinatra'
152
- if defined? Rails
152
+ if defined? ::Rails
153
153
  require 'paper_trail/frameworks/rails'
154
154
  else
155
155
  require 'paper_trail/frameworks/active_record'
@@ -1,14 +1,14 @@
1
1
  require 'singleton'
2
+ require 'paper_trail/serializers/yaml'
2
3
 
3
4
  module PaperTrail
4
5
  class Config
5
6
  include Singleton
6
- attr_accessor :enabled, :timestamp_field, :serializer, :version_limit
7
+ attr_accessor :timestamp_field, :serializer, :version_limit
7
8
  attr_reader :serialized_attributes
8
9
  attr_writer :track_associations
9
10
 
10
11
  def initialize
11
- @enabled = true # Indicates whether PaperTrail is on or off.
12
12
  @timestamp_field = :created_at
13
13
  @serializer = PaperTrail::Serializers::YAML
14
14
 
@@ -22,9 +22,11 @@ module PaperTrail
22
22
 
23
23
  def serialized_attributes=(value)
24
24
  if ::ActiveRecord::VERSION::MAJOR >= 5
25
- warn("DEPRECATED: ActiveRecord 5.0 deprecated `serialized_attributes` " +
25
+ ::ActiveSupport::Deprecation.warn(
26
+ "ActiveRecord 5.0 deprecated `serialized_attributes` " +
26
27
  "without replacement, so this PaperTrail config setting does " +
27
- "nothing with this version, and is always turned off")
28
+ "nothing with this version, and is always turned off"
29
+ )
28
30
  end
29
31
  @serialized_attributes = value
30
32
  end
@@ -33,5 +35,14 @@ module PaperTrail
33
35
  @track_associations ||= PaperTrail::VersionAssociation.table_exists?
34
36
  end
35
37
  alias_method :track_associations?, :track_associations
38
+
39
+ # Indicates whether PaperTrail is on or off.
40
+ def enabled
41
+ PaperTrail.paper_trail_store[:paper_trail_enabled].nil? || PaperTrail.paper_trail_store[:paper_trail_enabled]
42
+ end
43
+
44
+ def enabled= enable
45
+ PaperTrail.paper_trail_store[:paper_trail_enabled] = enable
46
+ end
36
47
  end
37
48
  end
@@ -1,5 +1,4 @@
1
1
  # This file only needs to be loaded if the gem is being used outside of Rails, since otherwise
2
2
  # the model(s) will get loaded in via the `Rails::Engine`
3
- Dir[File.join(File.dirname(__FILE__), 'active_record', 'models', 'paper_trail', '*.rb')].each do |file|
4
- require "paper_trail/frameworks/active_record/models/paper_trail/#{File.basename(file, '.rb')}"
5
- end
3
+ require "paper_trail/frameworks/active_record/models/paper_trail/version_association"
4
+ require "paper_trail/frameworks/active_record/models/paper_trail/version"
@@ -192,7 +192,7 @@ module PaperTrail
192
192
  end
193
193
 
194
194
  def originator
195
- warn "DEPRECATED: use `paper_trail_originator` instead of `originator`. Support for `originator` will be removed in PaperTrail 4.0"
195
+ ::ActiveSupport::Deprecation.warn "Use paper_trail_originator instead of originator."
196
196
  self.paper_trail_originator
197
197
  end
198
198
 
@@ -266,11 +266,14 @@ module PaperTrail
266
266
  PaperTrail.whodunnit = current_whodunnit
267
267
  end
268
268
 
269
- # Mimicks behavior of `touch` method from `ActiveRecord::Persistence`, but generates a version
269
+ # Mimics the `touch` method from `ActiveRecord::Persistence`, but also
270
+ # creates a version. A version is created regardless of options such as
271
+ # `:on`, `:if`, or `:unless`.
270
272
  #
271
- # TODO: lookinto leveraging the `after_touch` callback from `ActiveRecord` to allow the
272
- # regular `touch` method go generate a version as normal. May make sense to switch the `record_update`
273
- # method to leverage an `after_update` callback anyways (likely for v4.0.0)
273
+ # TODO: look into leveraging the `after_touch` callback from
274
+ # `ActiveRecord` to allow the regular `touch` method go generate a version
275
+ # as normal. May make sense to switch the `record_update` method to
276
+ # leverage an `after_update` callback anyways (likely for v4.0.0)
274
277
  def touch_with_version(name = nil)
275
278
  raise ActiveRecordError, "can not touch on a new record object" unless persisted?
276
279
 
@@ -279,13 +282,20 @@ module PaperTrail
279
282
  current_time = current_time_from_proper_timezone
280
283
 
281
284
  attributes.each { |column| write_attribute(column, current_time) }
282
- # ensure a version is written even if the `:on` collection is empty
283
- record_update(true) if paper_trail_options[:on] == []
285
+
286
+ record_update(true) unless will_record_after_update?
284
287
  save!(:validate => false)
285
288
  end
286
289
 
287
290
  private
288
291
 
292
+ # Returns true if `save` will cause `record_update`
293
+ # to be called via the `after_update` callback.
294
+ def will_record_after_update?
295
+ on = paper_trail_options[:on]
296
+ on.nil? || on.include?(:update)
297
+ end
298
+
289
299
  def source_version
290
300
  send self.class.version_association_name
291
301
  end
@@ -459,18 +469,27 @@ module PaperTrail
459
469
  attrs
460
470
  end
461
471
 
462
- # This method is invoked in order to determine whether it is appropriate to generate a new version instance.
463
- # Because we are now using `after_(create/update/etc)` callbacks, we need to go out of our way to
464
- # ensure that during updates timestamp attributes are not acknowledged as a notable changes
465
- # to raise false positives when attributes are ignored.
472
+ # This method determines whether it is appropriate to generate a new
473
+ # version instance. A timestamp-only update (e.g. only `updated_at`
474
+ # changed) is considered notable unless an ignored attribute was also
475
+ # changed.
466
476
  def changed_notably?
467
- if self.paper_trail_options[:ignore].any? && (changed & self.paper_trail_options[:ignore]).any?
468
- (notably_changed - timestamp_attributes_for_update_in_model.map(&:to_s)).any?
477
+ if ignored_attr_has_changed?
478
+ timestamps = timestamp_attributes_for_update_in_model.map(&:to_s)
479
+ (notably_changed - timestamps).any?
469
480
  else
470
481
  notably_changed.any?
471
482
  end
472
483
  end
473
484
 
485
+ # An attributed is "ignored" if it is listed in the `:ignore` option
486
+ # and/or the `:skip` option. Returns true if an ignored attribute
487
+ # has changed.
488
+ def ignored_attr_has_changed?
489
+ ignored = self.paper_trail_options[:ignore] + self.paper_trail_options[:skip]
490
+ ignored.any? && (changed & ignored).any?
491
+ end
492
+
474
493
  def notably_changed
475
494
  only = self.paper_trail_options[:only].dup
476
495
  # remove Hash arguments and then evaluate whether the attributes (the keys of the hash) should also get pushed into the collection
@@ -257,7 +257,7 @@ module PaperTrail
257
257
  end
258
258
 
259
259
  def originator
260
- warn "DEPRECATED: use `paper_trail_originator` instead of `originator`. Support for `originator` will be removed in PaperTrail 4.0"
260
+ ::ActiveSupport::Deprecation.warn "Use paper_trail_originator instead of originator."
261
261
  self.paper_trail_originator
262
262
  end
263
263
 
@@ -3,7 +3,7 @@ module PaperTrail
3
3
  MAJOR = 4
4
4
  MINOR = 0
5
5
  TINY = 0
6
- PRE = 'rc1'
6
+ PRE = 'rc2'
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
9
9
 
data/paper_trail.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency 'activerecord', ['>= 3.0', '< 6.0']
23
23
  s.add_dependency 'activesupport', ['>= 3.0', '< 6.0']
24
- s.add_dependency 'request_store', '~> 1.1.0'
24
+ s.add_dependency 'request_store', '~> 1.1'
25
25
 
26
26
  s.add_development_dependency 'rake', '~> 10.1.1'
27
27
  s.add_development_dependency 'shoulda', '~> 3.5'
@@ -45,7 +45,7 @@ describe Gadget, :type => :model do
45
45
  expect(subject.send(:changed_notably?)).to be true
46
46
  end
47
47
 
48
- it "should not acknowledge ignored attrs and timestamps only" do
48
+ it "should not acknowledge ignored attr (brand)" do
49
49
  subject.brand = 'Acme'
50
50
  expect(subject.send(:changed_notably?)).to be false
51
51
  end
@@ -0,0 +1,14 @@
1
+ require 'rails_helper'
2
+
3
+ module Kitchen
4
+ describe Banana, :type => :model do
5
+ it { is_expected.to be_versioned }
6
+
7
+ describe '#versions' do
8
+ it "returns instances of Kitchen::BananaVersion", :versioning => true do
9
+ banana = described_class.create!
10
+ expect(banana.versions.first).to be_a(Kitchen::BananaVersion)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'rails_helper'
2
+
3
+ describe NotOnUpdate, :type => :model do
4
+ describe "#touch_with_version", :versioning => true do
5
+ let!(:record) { described_class.create! }
6
+
7
+ it "should create a version, regardless" do
8
+ expect { record.touch_with_version }.to change {
9
+ PaperTrail::Version.count
10
+ }.by(+1)
11
+ end
12
+
13
+ it "increments the `:updated_at` timestamp" do
14
+ before = record.updated_at
15
+ record.touch_with_version
16
+ expect(record.updated_at).to be > before
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails_helper'
2
+
3
+ describe Skipper, :type => :model do
4
+ describe "#update_attributes!", :versioning => true do
5
+ context "updating a skipped attribute" do
6
+ let(:t1) { Time.zone.local(2015, 7, 15, 20, 34, 0) }
7
+ let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }
8
+
9
+ it "should not create a version" do
10
+ skipper = Skipper.create!(:another_timestamp => t1)
11
+ expect {
12
+ skipper.update_attributes!(:another_timestamp => t2)
13
+ }.to_not change { skipper.versions.length }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -58,10 +58,10 @@ describe PaperTrail::Version, :type => :model do
58
58
 
59
59
  context "Has previous version", :versioning => true do
60
60
  let(:name) { Faker::Name.name }
61
- let(:widget) { Widget.create!(name: Faker::Name.name) }
61
+ let(:widget) { Widget.create!(:name => Faker::Name.name) }
62
62
  before do
63
63
  widget.versions.first.update_attributes!(:whodunnit => name)
64
- widget.update_attributes!(name: Faker::Name.first_name)
64
+ widget.update_attributes!(:name => Faker::Name.first_name)
65
65
  end
66
66
  subject { widget.versions.last }
67
67
 
@@ -75,19 +75,16 @@ describe PaperTrail::Version, :type => :model do
75
75
 
76
76
  describe "#originator" do
77
77
  it { is_expected.to respond_to(:originator) }
78
- let(:warning_msg) do
79
- "DEPRECATED: use `paper_trail_originator` instead of `originator`." +
80
- " Support for `originator` will be removed in PaperTrail 4.0"
81
- end
82
78
 
83
79
  it 'should set the invoke `paper_trail_originator`' do
84
- is_expected.to receive(:warn)
80
+ allow(ActiveSupport::Deprecation).to receive(:warn)
85
81
  is_expected.to receive(:paper_trail_originator)
86
82
  subject.originator
87
83
  end
88
84
 
89
85
  it 'should display a deprecation warning' do
90
- is_expected.to receive(:warn).with(warning_msg)
86
+ expect(ActiveSupport::Deprecation).to receive(:warn).
87
+ with(/Use paper_trail_originator instead of originator/)
91
88
  subject.originator
92
89
  end
93
90
  end
@@ -177,19 +177,16 @@ describe Widget, :type => :model do
177
177
  subject { widget }
178
178
 
179
179
  it { is_expected.to respond_to(:originator) }
180
- let(:warning_msg) do
181
- "DEPRECATED: use `paper_trail_originator` instead of `originator`." +
182
- " Support for `originator` will be removed in PaperTrail 4.0"
183
- end
184
180
 
185
181
  it 'should set the invoke `paper_trail_originator`' do
186
- is_expected.to receive(:warn)
182
+ allow(::ActiveSupport::Deprecation).to receive(:warn)
187
183
  is_expected.to receive(:paper_trail_originator)
188
184
  subject.originator
189
185
  end
190
186
 
191
187
  it 'should display a deprecation warning' do
192
- is_expected.to receive(:warn).with(warning_msg)
188
+ expect(::ActiveSupport::Deprecation).to receive(:warn).
189
+ with(/Use paper_trail_originator instead of originator/)
193
190
  subject.originator
194
191
  end
195
192
  end
@@ -252,13 +249,13 @@ describe Widget, :type => :model do
252
249
  describe '#touch_with_version' do
253
250
  it { is_expected.to respond_to(:touch_with_version) }
254
251
 
255
- it "should generate a version" do
252
+ it "creates a version" do
256
253
  count = widget.versions.size
257
254
  widget.touch_with_version
258
255
  expect(widget.versions.size).to eq(count + 1)
259
256
  end
260
257
 
261
- it "should increment the `:updated_at` timestamp" do
258
+ it "increments the `:updated_at` timestamp" do
262
259
  time_was = widget.updated_at
263
260
  widget.touch_with_version
264
261
  expect(widget.updated_at).to be > time_was
@@ -0,0 +1,5 @@
1
+ module Kitchen
2
+ class Banana < ActiveRecord::Base
3
+ has_paper_trail :class_name => "Kitchen::BananaVersion"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ # This model does not record versions when updated.
2
+ class NotOnUpdate < ActiveRecord::Base
3
+ has_paper_trail :on => [:create, :destroy]
4
+ end
@@ -0,0 +1,6 @@
1
+ class Skipper < ActiveRecord::Base
2
+ has_paper_trail(
3
+ :ignore => [:created_at],
4
+ :skip => [:another_timestamp]
5
+ )
6
+ end
@@ -0,0 +1,5 @@
1
+ module Kitchen
2
+ class BananaVersion < PaperTrail::Version
3
+ self.table_name = 'banana_versions'
4
+ end
5
+ end
@@ -1,5 +1,10 @@
1
1
  class SetUpTestTables < ActiveRecord::Migration
2
2
  def self.up
3
+ create_table :skippers, :force => true do |t|
4
+ t.datetime :another_timestamp
5
+ t.timestamps :null => true
6
+ end
7
+
3
8
  create_table :widgets, :force => true do |t|
4
9
  t.string :name
5
10
  t.text :a_text
@@ -73,6 +78,24 @@ class SetUpTestTables < ActiveRecord::Migration
73
78
  add_index :json_versions, [:item_type, :item_id]
74
79
  end
75
80
 
81
+ create_table :not_on_updates, :force => true do |t|
82
+ t.timestamps :null => true
83
+ end
84
+
85
+ create_table :bananas, :force => true do |t|
86
+ t.timestamps :null => true
87
+ end
88
+
89
+ create_table :banana_versions, :force => true do |t|
90
+ t.string :item_type, :null => false
91
+ t.integer :item_id, :null => false
92
+ t.string :event, :null => false
93
+ t.string :whodunnit
94
+ t.text :object
95
+ t.datetime :created_at
96
+ end
97
+ add_index :banana_versions, [:item_type, :item_id]
98
+
76
99
  create_table :wotsits, :force => true do |t|
77
100
  t.integer :widget_id
78
101
  t.string :name
@@ -191,6 +214,8 @@ class SetUpTestTables < ActiveRecord::Migration
191
214
 
192
215
  def self.down
193
216
  drop_table :animals
217
+ drop_table :skippers
218
+ drop_table :not_on_updates
194
219
  drop_table :posts
195
220
  drop_table :songs
196
221
  drop_table :editors
@@ -8,6 +8,13 @@ class PaperTrailTest < ActiveSupport::TestCase
8
8
  test 'Version Number' do
9
9
  assert PaperTrail.const_defined?(:VERSION)
10
10
  end
11
+
12
+ test 'enabled is thread-safe' do
13
+ Thread.new do
14
+ PaperTrail.enabled = false
15
+ end.join
16
+ assert PaperTrail.enabled?
17
+ end
11
18
 
12
19
  test 'create with plain model class' do
13
20
  widget = Widget.create