Query Versions Where Object Changed To Attributes
This extends 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 `where_object_changes_to` addition focuses only on one side of that equation. If you want to find versions where the attribute(s) explicitly changed *to* some known value, this will only show those changes, as opposed to both *from* and *to*.
This commit is contained in:
parent
584e2f732e
commit
06a7c12534
|
@ -30,6 +30,8 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
|||
|
||||
### Added
|
||||
|
||||
- `where_object_changes_to` queries for versions where the object's attributes
|
||||
changed to one set of known values from any other set of values.
|
||||
- `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.
|
||||
|
||||
|
|
|
@ -1367,6 +1367,7 @@ An adapter can implement any or all of the following methods:
|
|||
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).
|
||||
5. where_object_changes_to: Returns the records resulting from the given hash of attributes where the attributes changed *to* 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_to` in
|
||||
# `paper_trail/version_concern.rb`.
|
||||
# @api private
|
||||
class WhereObjectChangesTo
|
||||
# - 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_to)
|
||||
return PaperTrail.config.object_changes_adapter.where_object_changes_to(
|
||||
@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_to_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
|
|
@ -49,6 +49,14 @@ module PaperTrail
|
|||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
|
||||
# Raises an exception as this operation is not allowed from text columns.
|
||||
def where_object_changes_to_condition(*)
|
||||
raise <<-STR.squish.freeze
|
||||
where_object_changes_to does not support reading JSON from a text
|
||||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,14 @@ module PaperTrail
|
|||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
|
||||
# Raises an exception as this operation is not allowed with YAML.
|
||||
def where_object_changes_to_condition(*)
|
||||
raise <<-STR.squish.freeze
|
||||
where_object_changes_to does not support reading YAML from a text
|
||||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,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"
|
||||
require "paper_trail/queries/versions/where_object_changes_to"
|
||||
|
||||
module PaperTrail
|
||||
# Originally, PaperTrail did not provide this module, and all of this
|
||||
|
@ -131,6 +132,21 @@ module PaperTrail
|
|||
Queries::Versions::WhereObjectChangesFrom.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
|
||||
# to the hash of attributes from other values.
|
||||
#
|
||||
# This is useful for finding versions where the attribute started with an
|
||||
# unknown value and changed to a known value. This is in comparison to
|
||||
# `where_object_changes` which will find both the changes before and
|
||||
# after.
|
||||
#
|
||||
# @api public
|
||||
def where_object_changes_to(args = {})
|
||||
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
||||
Queries::Versions::WhereObjectChangesTo.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
|
||||
|
|
|
@ -350,6 +350,88 @@ module PaperTrail
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#where_object_changes_to", versioning: true do
|
||||
it "requires its argument to be a Hash" do
|
||||
expect {
|
||||
PaperTrail::Version.where_object_changes_to(:foo)
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
PaperTrail::Version.where_object_changes_to([])
|
||||
}.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_to method" do
|
||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||
bicycle = Bicycle.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
allow(adapter).to(
|
||||
receive(:where_object_changes_to).with(Version, name: "xyz")
|
||||
).and_return([bicycle.versions[1]])
|
||||
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
expect(adapter).to have_received(:where_object_changes_to)
|
||||
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_to(name: "xyz")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
else
|
||||
expect do
|
||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
end.to raise_error(/does not support reading YAML/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Only test json and jsonb columns. where_object_changes_to 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_to(name: name)
|
||||
).to eq([widget.versions[0]])
|
||||
expect(
|
||||
widget.versions.where_object_changes_to(an_integer: 100)
|
||||
).to eq([widget.versions[1]])
|
||||
expect(
|
||||
widget.versions.where_object_changes_to(an_integer: int)
|
||||
).to eq([widget.versions[2]])
|
||||
expect(
|
||||
widget.versions.where_object_changes_to(an_integer: 100, name: "foobar")
|
||||
).to eq([widget.versions[1]])
|
||||
expect(
|
||||
widget.versions.where_object_changes_to(an_integer: -1)
|
||||
).to eq([])
|
||||
end
|
||||
else
|
||||
it "raises error" do
|
||||
expect {
|
||||
widget.versions.where_object_changes_to(name: "foo").to_a
|
||||
}.to(raise_error(/does not support reading YAML from a text column/))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,4 +17,8 @@ class CustomObjectChangesAdapter
|
|||
def where_object_changes_from(klass, attributes)
|
||||
klass.where(attributes)
|
||||
end
|
||||
|
||||
def where_object_changes_to(klass, attributes)
|
||||
klass.where(attributes)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue