Integrate versioning into AR touch method (#1063)

* integrate versioning into AR touch method

* add touch to list of :on events, deprecate touch_with_version

* integrate versioning into AR touch method
This commit is contained in:
Weston Ganger 2018-03-16 15:21:45 -07:00 committed by Jared Beck
parent 87f097c05f
commit 02b6de2dac
12 changed files with 84 additions and 15 deletions

View File

@ -25,12 +25,14 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
### Deprecated
- [#1063](https://github.com/airblade/paper_trail/pull/1063) - `touch_with_version` is deprecated in favor of using the original AR `touch` method
- [#1033](https://github.com/airblade/paper_trail/pull/1033) - Request variables
are now set using eg. `PaperTrail.request.whodunnit=` and the old way,
`PaperTrail.whodunnit=` is deprecated.
### Added
- [#1063](https://github.com/airblade/paper_trail/pull/1063) - AR `touch` method now creates a version if the `:on` option includes `touch`. By default it also includes the `touch` event now.
- [#1033](https://github.com/airblade/paper_trail/pull/1033) -
Set request variables temporarily using a block, eg.
`PaperTrail.request(whodunnit: 'Jared') do .. end`

View File

@ -207,10 +207,6 @@ widget.paper_trail.previous_version
# Returns the widget (not a version) as it became next.
widget.paper_trail.next_version
# Generates a version for a `touch` event (`widget.touch` does NOT generate a
# version)
widget.paper_trail.touch_with_version
# Enable/disable PaperTrail, for Widget, for the current request (not all threads)
PaperTrail.request.disable_model(Widget)
PaperTrail.request.enable_model(Widget)
@ -292,7 +288,7 @@ ignore `create` events:
```ruby
class Article < ActiveRecord::Base
has_paper_trail on: [:update, :destroy]
has_paper_trail on: [:update, :destroy, :touch]
end
```
@ -337,6 +333,7 @@ class Article < ActiveRecord::Base
paper_trail.on_destroy # add destroy callback
paper_trail.on_update # etc.
paper_trail.on_create
paper_trail.on_touch
end
```
@ -706,6 +703,22 @@ sql> delete from versions where created_at < 2010-06-01;
PaperTrail::Version.delete_all ['created_at < ?', 1.week.ago]
```
### 3.e. Trigger Version creation
At some point you may want to trigger a new version to be created. To do this we utilize the AR `touch` method.
```ruby
widget.touch
```
The new versions event will be saved as `update`.
If you are using the `:on` option in your model you must specify the `touch` event also. For example:
```ruby
has_paper_trail on: [:update, :destroy, :touch]
```
## 4. Saving More Information About Versions
### 4.a. Finding Out Who Was Responsible For A Change

View File

@ -116,11 +116,19 @@ module PaperTrail
@model_class.paper_trail_options[:on] << :update
end
# Adds a callback that records a version after a "touch" event.
# @api public
def on_touch
@model_class.after_touch { |r|
r.paper_trail.record_update(force: true, in_after_callback: true)
}
end
# Set up `@model_class` for PaperTrail. Installs callbacks, associations,
# "class attributes", instance methods, and more.
# @api private
def setup(options = {})
options[:on] ||= %i[create update destroy]
options[:on] ||= %i[create update destroy touch]
options[:on] = Array(options[:on]) # Support single symbol
@model_class.send :include, ::PaperTrail::Model::InstanceMethods
setup_options(options)

View File

@ -7,6 +7,12 @@ module PaperTrail
my_model_instance.paper_trail.whodunnit('John') is deprecated,
please use PaperTrail.request(whodunnit: 'John')
STR
DPR_TOUCH_WITH_VERSION = <<-STR.squish.freeze
my_model_instance.paper_trail.touch_with_version is deprecated,
please use my_model_instance.touch
STR
RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
def initialize(record)
@ -461,8 +467,9 @@ module PaperTrail
# version records are inserted. It's unclear under which specific
# circumstances this technique should be adopted.
#
# @api public
# @deprecated
def touch_with_version(name = nil)
::ActiveSupport::Deprecation.warn(DPR_TOUCH_WITH_VERSION, caller(1))
unless @record.persisted?
raise ::ActiveRecord::ActiveRecordError, "can not touch on a new record object"
end
@ -571,9 +578,8 @@ module PaperTrail
if @in_after_callback
@record.attribute_before_last_save(attr_name.to_s)
else
# Either we are doing a `touch_with_version` or `record_destroy`.
# Other events, like `record_create`, can only be done in an
# after-callback.
# We are performing a `record_destroy`. Other events,
# like `record_create`, can only be done in an after-callback.
@record.attribute_in_database(attr_name.to_s)
end
else

View File

@ -189,9 +189,12 @@ RSpec.describe Article, type: :model, versioning: true do
describe "#touch_with_version" do
it "creates a version, ignoring the :only option" do
article = described_class.create
allow(::ActiveSupport::Deprecation).to receive(:warn)
expect { article.paper_trail.touch_with_version }.to change {
::PaperTrail::Version.count
}.by(+1)
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
end
end
end

View File

@ -67,7 +67,7 @@ RSpec.describe CallbackModifier, type: :model, versioning: true do
context "when no callback-method used" do
it "sets paper_trail_options[:on] to [:create, :update, :destroy]" do
modifier = DefaultModifier.create!(some_content: FFaker::Lorem.sentence)
expect(modifier.paper_trail_options[:on]).to eq %i[create update destroy]
expect(modifier.paper_trail_options[:on]).to eq %i[create update destroy touch]
end
it "tracks destroy" do

View File

@ -7,17 +7,21 @@ RSpec.describe NotOnUpdate, type: :model do
let!(:record) { described_class.create! }
it "creates a version, regardless" do
allow(::ActiveSupport::Deprecation).to receive(:warn)
expect { record.paper_trail.touch_with_version }.to change {
PaperTrail::Version.count
}.by(+1)
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
end
it "increments the `:updated_at` timestamp" do
before = record.updated_at
allow(::ActiveSupport::Deprecation).to receive(:warn)
# Travel 1 second because MySQL lacks sub-second resolution
Timecop.travel(1) do
record.paper_trail.touch_with_version
end
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
expect(record.updated_at).to be > before
end
end

View File

@ -15,7 +15,9 @@ module On
describe "#touch_with_version" do
it "creates a version record" do
record = described_class.create(name: "Alice")
allow(::ActiveSupport::Deprecation).to receive(:warn)
record.paper_trail.touch_with_version
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
expect(record.versions.length).to(eq(1))
v = record.versions.first
expect(v.event).to(eq("update"))

View File

@ -25,5 +25,13 @@ module On
expect(record.versions.last.event).to(eq("banana"))
end
end
describe "#touch" do
it "does not create a version for the touch event" do
record = described_class.create(name: "Alice")
count = record.versions.count
expect(count).to eq(count)
end
end
end
end

View File

@ -37,7 +37,9 @@ RSpec.describe PostWithStatus, type: :model do
expect(post.versions.count).to eq(1)
expect(post.status).to eq("draft")
Timecop.travel 1.second.since # because MySQL lacks fractional seconds precision
allow(::ActiveSupport::Deprecation).to receive(:warn)
post.paper_trail.touch_with_version
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
expect(post.versions.count).to eq(2)
expect(post.versions.last[:object]).to include("status: 0")
expect(post.paper_trail.previous_version.status).to eq("draft")

View File

@ -232,21 +232,40 @@ RSpec.describe Widget, type: :model do
describe "#touch_with_version", versioning: true do
it "creates a version" do
allow(::ActiveSupport::Deprecation).to receive(:warn)
count = widget.versions.size
# Travel 1 second because MySQL lacks sub-second resolution
Timecop.travel(1) do
widget.paper_trail.touch_with_version
end
expect(widget.versions.size).to eq(count + 1)
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
end
it "increments the `:updated_at` timestamp" do
allow(::ActiveSupport::Deprecation).to receive(:warn)
time_was = widget.updated_at
# Travel 1 second because MySQL lacks sub-second resolution
Timecop.travel(1) do
widget.paper_trail.touch_with_version
end
expect(widget.updated_at).to be > time_was
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
end
end
describe "touch", versioning: true do
it "creates a version" do
expect { widget.touch }.to change {
widget.versions.count
}.by(+1)
end
it "does not create a version using without_versioning" do
count = widget.versions.count
widget.paper_trail.without_versioning do
widget.touch
end
expect(count).to eq(count)
end
end

View File

@ -56,15 +56,15 @@ RSpec.describe(::PaperTrail, versioning: true) do
context "and then updated without any changes" do
before { @widget.touch }
it "not have a new version" do
expect(@widget.versions.length).to(eq(1))
it "to have two previous versions" do
expect(@widget.versions.length).to(eq(2))
end
end
context "and then updated with changes" do
before { @widget.update_attributes(name: "Harry") }
it "have two previous versions" do
it "have three previous versions" do
expect(@widget.versions.length).to(eq(2))
end
@ -381,7 +381,9 @@ RSpec.describe(::PaperTrail, versioning: true) do
context "given a symbol, specifying a method name" do
it "does not create a new version" do
allow(::ActiveSupport::Deprecation).to receive(:warn)
@widget.paper_trail.without_versioning(:touch_with_version)
expect(::ActiveSupport::Deprecation).to have_received(:warn).once
expect(@widget.versions.length).to(eq(@count))
expect(::PaperTrail.request.enabled_for_model?(Widget)).to eq(true)
end