mirror of
https://github.com/paper-trail-gem/paper_trail.git
synced 2022-11-09 11:33:19 -05:00
Query Versions Where Object Changed From Attributes (#1286)
* Add rails-controller-testing In order to get all of the tests to pass, this adds in and configures rails-controller-testing so that `assigns`is available in controller and integration tests. * Query Versions Where Object Changed From Attributes This proposes an extension to the public API to provide more targeted querying of object changes. `where_object_changes` will look for either side of the change of the attributes provided - either versions where the attribute changed __from__ the provided value, or changed __to__ the provided value. The proposed `where_object_changes_from` addition focuses only on one side of that equation. If you want to find versions where the attribute(s) explicitly changed *from* some known value, this will only show those changes, as opposed to both *from* and *to*. * Custom Serializer where_object_changes_from implementation This mirrors the implementation of WhereObjectChanges#text in WhereObjectChangesFrom#text in the case of a custom serializer. The provided YAML serializer does not support this method. * Document New API Method for Custom Adapter This provides awareness of the new method that custom object changes adapters may need to implement to support this change. * remove rails-controller-testing
This commit is contained in:
parent
c3c58b739a
commit
0007864e2e
8 changed files with 183 additions and 1 deletions
|
@ -15,7 +15,8 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
|||
|
||||
### Added
|
||||
|
||||
- None
|
||||
- `where_object_changes_from` queries for versions where the object's attributes
|
||||
changed from one set of known values to any other set of values.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -1338,6 +1338,7 @@ An adapter can implement any or all of 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.
|
||||
4. where_object_changes_from: Returns the records resulting from the given hash of attributes where the attributes changed *from* the provided value(s).
|
||||
|
||||
Depending on what your adapter does, you may have to implement all three.
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PaperTrail
|
||||
module Queries
|
||||
module Versions
|
||||
# For public API documentation, see `where_object_changes_from` in
|
||||
# `paper_trail/version_concern.rb`.
|
||||
# @api private
|
||||
class WhereObjectChangesFrom
|
||||
# - version_model_class - The class that VersionConcern was mixed into.
|
||||
# - attributes - A `Hash` of attributes and values. See the public API
|
||||
# documentation for details.
|
||||
# @api private
|
||||
def initialize(version_model_class, attributes)
|
||||
@version_model_class = version_model_class
|
||||
@attributes = attributes
|
||||
end
|
||||
|
||||
# @api private
|
||||
def execute
|
||||
if PaperTrail.config.object_changes_adapter&.respond_to?(:where_object_changes_from)
|
||||
return PaperTrail.config.object_changes_adapter.where_object_changes_from(
|
||||
@version_model_class, @attributes
|
||||
)
|
||||
end
|
||||
|
||||
case @version_model_class.columns_hash["object_changes"].type
|
||||
when :jsonb, :json
|
||||
json
|
||||
else
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @api private
|
||||
def json
|
||||
predicates = []
|
||||
values = []
|
||||
@attributes.each do |field, value|
|
||||
predicates.push(
|
||||
"(object_changes->>? ILIKE ?)"
|
||||
)
|
||||
values.concat([field, "[#{value.to_json},%"])
|
||||
end
|
||||
sql = predicates.join(" and ")
|
||||
@version_model_class.where(sql, *values)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def text
|
||||
arel_field = @version_model_class.arel_table[:object_changes]
|
||||
|
||||
where_conditions = @attributes.map do |field, value|
|
||||
::PaperTrail.serializer.where_object_changes_from_condition(arel_field, field, value)
|
||||
end
|
||||
|
||||
where_conditions = where_conditions.reduce { |a, e| a.and(e) }
|
||||
@version_model_class.where(where_conditions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,6 +41,14 @@ module PaperTrail
|
|||
discussion at https://github.com/paper-trail-gem/paper_trail/issues/803
|
||||
STR
|
||||
end
|
||||
|
||||
# Raises an exception as this operation is not allowed from text columns.
|
||||
def where_object_changes_from_condition(*)
|
||||
raise <<-STR.squish.freeze
|
||||
where_object_changes_from does not support reading JSON from a text
|
||||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,14 @@ module PaperTrail
|
|||
discussion at https://github.com/paper-trail-gem/paper_trail/pull/997
|
||||
STR
|
||||
end
|
||||
|
||||
# Raises an exception as this operation is not allowed with YAML.
|
||||
def where_object_changes_from_condition(*)
|
||||
raise <<-STR.squish.freeze
|
||||
where_object_changes_from does not support reading YAML from a text
|
||||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
require "paper_trail/attribute_serializers/object_changes_attribute"
|
||||
require "paper_trail/queries/versions/where_object"
|
||||
require "paper_trail/queries/versions/where_object_changes"
|
||||
require "paper_trail/queries/versions/where_object_changes_from"
|
||||
|
||||
module PaperTrail
|
||||
# Originally, PaperTrail did not provide this module, and all of this
|
||||
|
@ -120,6 +121,21 @@ module PaperTrail
|
|||
Queries::Versions::WhereObjectChanges.new(self, args).execute
|
||||
end
|
||||
|
||||
# Given a hash of attributes like `name: 'Joan'`, query the
|
||||
# `versions.objects_changes` column for changes where the version changed
|
||||
# from the hash of attributes to other values.
|
||||
#
|
||||
# This is useful for finding versions where the attribute started with a
|
||||
# known value and changed to something else. This is in comparison to
|
||||
# `where_object_changes` which will find both the changes before and
|
||||
# after.
|
||||
#
|
||||
# @api public
|
||||
def where_object_changes_from(args = {})
|
||||
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
||||
Queries::Versions::WhereObjectChangesFrom.new(self, args).execute
|
||||
end
|
||||
|
||||
def primary_key_is_int?
|
||||
@primary_key_is_int ||= columns_hash[primary_key].type == :integer
|
||||
rescue StandardError # TODO: Rescue something more specific
|
||||
|
|
|
@ -273,6 +273,85 @@ module PaperTrail
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#where_object_changes_from", versioning: true do
|
||||
it "requires its argument to be a Hash" do
|
||||
expect {
|
||||
PaperTrail::Version.where_object_changes_from(:foo)
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
PaperTrail::Version.where_object_changes_from([])
|
||||
}.to raise_error(ArgumentError)
|
||||
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_from method" do
|
||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||
bicycle = Bicycle.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
allow(adapter).to(
|
||||
receive(:where_object_changes_from).with(Version, name: "abc")
|
||||
).and_return([bicycle.versions[1]])
|
||||
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
expect(adapter).to have_received(:where_object_changes_from)
|
||||
end
|
||||
|
||||
it "defaults to the original behavior" do
|
||||
adapter = Class.new.new
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
bicycle = Bicycle.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
if column_datatype_override
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
else
|
||||
expect do
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
end.to raise_error(/does not support reading YAML/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Only test json and jsonb columns. where_object_changes_from does
|
||||
# not support text columns.
|
||||
if column_datatype_override
|
||||
it "locates versions according to their object_changes contents" do
|
||||
widget.update!(name: name, an_integer: 0)
|
||||
widget.update!(name: "foobar", an_integer: 100)
|
||||
widget.update!(name: FFaker::Name.last_name, an_integer: int)
|
||||
|
||||
expect(
|
||||
widget.versions.where_object_changes_from(name: name)
|
||||
).to eq([widget.versions[1]])
|
||||
expect(
|
||||
widget.versions.where_object_changes_from(an_integer: 100)
|
||||
).to eq([widget.versions[2]])
|
||||
expect(
|
||||
widget.versions.where_object_changes_from(an_integer: int)
|
||||
).to eq([])
|
||||
expect(
|
||||
widget.versions.where_object_changes_from(an_integer: 100, name: "foobar")
|
||||
).to eq([widget.versions[2]])
|
||||
end
|
||||
else
|
||||
it "raises error" do
|
||||
expect {
|
||||
widget.versions.where_object_changes_from(name: "foo").to_a
|
||||
}.to(raise_error(/does not support reading YAML from a text column/))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,4 +13,8 @@ class CustomObjectChangesAdapter
|
|||
def where_object_changes(klass, attributes)
|
||||
klass.where(attributes)
|
||||
end
|
||||
|
||||
def where_object_changes_from(klass, attributes)
|
||||
klass.where(attributes)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue