Introduce config.object_changes_adapter
Allows users to write third-party adapters giving them complete control over the contents of the `object_changes` column.
This commit is contained in:
parent
28aea8b271
commit
776189de57
|
@ -11,7 +11,11 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- None
|
- [#1093](https://github.com/paper-trail-gem/paper_trail/pull/1093) -
|
||||||
|
`PaperTrail.config.object_changes_adapter` - Allows specifying an adapter that will
|
||||||
|
determine how the changes for each version are stored in the object_changes column.
|
||||||
|
An example of this implementation using the hashdiff gem can be found here:
|
||||||
|
[paper_trail-hashdiff](https://github.com/hashwin/paper_trail-hashdiff)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
18
README.md
18
README.md
|
@ -54,6 +54,7 @@ has been destroyed.
|
||||||
- [6. Extensibility](#6-extensibility)
|
- [6. Extensibility](#6-extensibility)
|
||||||
- [6.a. Custom Version Classes](#6a-custom-version-classes)
|
- [6.a. Custom Version Classes](#6a-custom-version-classes)
|
||||||
- [6.b. Custom Serializer](#6b-custom-serializer)
|
- [6.b. Custom Serializer](#6b-custom-serializer)
|
||||||
|
- [6.c. Custom Object Changes](#6c-custom-object-changes)
|
||||||
- [7. Testing](#7-testing)
|
- [7. Testing](#7-testing)
|
||||||
- [7.a. Minitest](#7a-minitest)
|
- [7.a. Minitest](#7a-minitest)
|
||||||
- [7.b. RSpec](#7b-rspec)
|
- [7.b. RSpec](#7b-rspec)
|
||||||
|
@ -1378,6 +1379,23 @@ class ConvertVersionsObjectToJson < ActiveRecord::Migration
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 6.c. Custom Object Changes
|
||||||
|
|
||||||
|
By default, PaperTrail stores object changes in a before/after array of objects
|
||||||
|
containing keys of columns that have changed in that particular version. You can
|
||||||
|
override this behaviour by using the object_changes_adapter config option:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
PaperTrail.config.object_changes_adapter = MyObjectChangesAdapter.new
|
||||||
|
```
|
||||||
|
|
||||||
|
A valid adapter is a class that contains the following methods:
|
||||||
|
1. diff: Returns the changeset in the desired format given the changeset in the original format
|
||||||
|
2. load_changeset: Returns the changeset for a given version object
|
||||||
|
3. where_object_changes: Returns the records resulting from the given hash of attributes.
|
||||||
|
|
||||||
|
For an example of such an implementation, see [paper_trail-hashdiff](https://github.com/hashwin/paper_trail-hashdiff)
|
||||||
|
|
||||||
## 7. Testing
|
## 7. Testing
|
||||||
|
|
||||||
You may want to turn PaperTrail off to speed up your tests. See [Turning
|
You may want to turn PaperTrail off to speed up your tests. See [Turning
|
||||||
|
|
|
@ -26,7 +26,8 @@ module PaperTrail
|
||||||
STR
|
STR
|
||||||
|
|
||||||
include Singleton
|
include Singleton
|
||||||
attr_accessor :serializer, :version_limit, :association_reify_error_behaviour
|
attr_accessor :serializer, :version_limit, :association_reify_error_behaviour,
|
||||||
|
:object_changes_adapter
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
# Variables which affect all threads, whose access is synchronized.
|
# Variables which affect all threads, whose access is synchronized.
|
||||||
|
|
|
@ -23,6 +23,11 @@ module PaperTrail
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def execute
|
def execute
|
||||||
|
if PaperTrail.config.object_changes_adapter
|
||||||
|
return PaperTrail.config.object_changes_adapter.where_object_changes(
|
||||||
|
@version_model_class, @attributes
|
||||||
|
)
|
||||||
|
end
|
||||||
case @version_model_class.columns_hash["object_changes"].type
|
case @version_model_class.columns_hash["object_changes"].type
|
||||||
when :jsonb
|
when :jsonb
|
||||||
jsonb
|
jsonb
|
||||||
|
|
|
@ -418,6 +418,10 @@ module PaperTrail
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
def recordable_object_changes(changes)
|
def recordable_object_changes(changes)
|
||||||
|
if PaperTrail.config.object_changes_adapter
|
||||||
|
changes = PaperTrail.config.object_changes_adapter.diff(changes)
|
||||||
|
end
|
||||||
|
|
||||||
if @record.class.paper_trail.version_class.object_changes_col_is_json?
|
if @record.class.paper_trail.version_class.object_changes_col_is_json?
|
||||||
changes
|
changes
|
||||||
else
|
else
|
||||||
|
|
|
@ -265,6 +265,10 @@ module PaperTrail
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def load_changeset
|
def load_changeset
|
||||||
|
if PaperTrail.config.object_changes_adapter
|
||||||
|
return PaperTrail.config.object_changes_adapter.load_changeset(self)
|
||||||
|
end
|
||||||
|
|
||||||
# First, deserialize the `object_changes` column.
|
# First, deserialize the `object_changes` column.
|
||||||
changes = HashWithIndifferentAccess.new(object_changes_deserialized)
|
changes = HashWithIndifferentAccess.new(object_changes_deserialized)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,28 @@ module PaperTrail
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with object_changes_adapter" do
|
||||||
|
let(:adapter) { instance_spy("CustomObjectChangesAdapter") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:diff).with(
|
||||||
|
hash_including("name" => [nil, "Dashboard"])
|
||||||
|
).and_return([["name", nil, "Dashboard"]])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
PaperTrail.config.object_changes_adapter = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a version with custom changes" do
|
||||||
|
expect(widget.versions.last.object_changes).to eq("---\n- - name\n - \n - Dashboard\n")
|
||||||
|
expect(adapter).to have_received(:diff)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "serializer is JSON" do
|
context "serializer is JSON" do
|
||||||
before do
|
before do
|
||||||
PaperTrail.serializer = PaperTrail::Serializers::JSON
|
PaperTrail.serializer = PaperTrail::Serializers::JSON
|
||||||
|
@ -202,6 +224,25 @@ module PaperTrail
|
||||||
}.to raise_error(ArgumentError)
|
}.to raise_error(ArgumentError)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with object_changes_adapter configured" do
|
||||||
|
after do
|
||||||
|
PaperTrail.config.object_changes_adapter = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "calls the adapter's where_object_changes method" do
|
||||||
|
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||||
|
bicycle = Bicycle.create!(name: "abc")
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:where_object_changes).with(Version, name: "abc")
|
||||||
|
).and_return(bicycle.versions[0..1])
|
||||||
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
|
expect(
|
||||||
|
bicycle.versions.where_object_changes(name: "abc")
|
||||||
|
).to match_array(bicycle.versions[0..1])
|
||||||
|
expect(adapter).to have_received(:where_object_changes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Only test json and jsonb columns. where_object_changes no longer
|
# Only test json and jsonb columns. where_object_changes no longer
|
||||||
# supports text columns.
|
# supports text columns.
|
||||||
if column_datatype_override
|
if column_datatype_override
|
||||||
|
|
|
@ -51,6 +51,28 @@ RSpec.describe(::PaperTrail, versioning: true) do
|
||||||
expect(changeset["updated_at"][0]).to be_nil
|
expect(changeset["updated_at"][0]).to be_nil
|
||||||
expect(changeset["updated_at"][1].to_i).to eq(@widget.updated_at.to_i)
|
expect(changeset["updated_at"][1].to_i).to eq(@widget.updated_at.to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "custom object_changes_adapter" do
|
||||||
|
let(:adapter) { instance_spy("CustomObjectChangesAdapter") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:load_changeset).with(@widget.versions.last).and_return(a: "b", c: "d")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
PaperTrail.config.object_changes_adapter = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "calls the adapter's load_changeset method" do
|
||||||
|
changeset = @widget.versions.last.changeset
|
||||||
|
expect(changeset[:a]).to eq("b")
|
||||||
|
expect(changeset[:c]).to eq("d")
|
||||||
|
expect(adapter).to have_received(:load_changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "and then updated without any changes" do
|
context "and then updated without any changes" do
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# This custom serializer excludes nil values
|
||||||
|
class CustomObjectChangesAdapter
|
||||||
|
def diff(changes)
|
||||||
|
changes
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_changeset(version)
|
||||||
|
version.changeset
|
||||||
|
end
|
||||||
|
|
||||||
|
def where_object_changes(klass, attributes)
|
||||||
|
klass.where(attributes)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue