Queries: object_changes: Simplify error instantiation
- Introduces a uniform error class, UnsupportedColumnType - Simplifies the built-in serializers (paper_trail/serializers) Since 9.2.0, when `object_changes_adapter` was introduced, if someone must use a text column, and still wants to use these queries, they must write an `object_changes_adapter`. AFAIK, no one has ever done this. The only public adapter I know of, paper_trail-hashdiff, only supports json/b columns. It's also theoretically possible that, after `where_object_changes` dropped support for text columns, someone wrote a custom serializer (see `PaperTrail.serializer=`). AFAIK, no one has done that either. Such a technique was never documented under [6.b. Custom Serializer](https://github.com/paper-trail-gem/paper_trail#6b-custom-serializer)
This commit is contained in:
parent
15a018f669
commit
4ef8a0bfed
100
README.md
100
README.md
|
@ -704,42 +704,20 @@ widget = widget.paper_trail.previous_version
|
|||
widget.paper_trail.live? # false
|
||||
```
|
||||
|
||||
And you can perform `WHERE` queries for object versions based on attributes:
|
||||
|
||||
```ruby
|
||||
# Find versions that meet these criteria.
|
||||
PaperTrail::Version.where_object(content: 'Hello', title: 'Article')
|
||||
|
||||
# Find versions before and after attribute `atr` had value `v`:
|
||||
PaperTrail::Version.where_object_changes(atr: 'v')
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
- `where_object_changes_from`
|
||||
- `where_object_changes_to`
|
||||
- `where_attribute_changes`
|
||||
|
||||
Using `where_object_changes*` or `where_attribute_changes` to read YAML or JSON
|
||||
from a text column was deprecated in 8.1.0, and will now raise an error. Use a
|
||||
`json` or `jsonb` column if possible. If you must use a `text` column, you'll
|
||||
have to write a custom `object_changes_adapter`.
|
||||
See also: Section 3.e. Queries
|
||||
|
||||
### 3.c. Diffing Versions
|
||||
|
||||
There are two scenarios: diffing adjacent versions and diffing non-adjacent
|
||||
versions.
|
||||
|
||||
The best way to diff adjacent versions is to get PaperTrail to do it for you.
|
||||
If you add an `object_changes` text column to your `versions` table, either at
|
||||
installation time with the `rails generate paper_trail:install --with-changes`
|
||||
option or manually, PaperTrail will store the `changes` diff (excluding any
|
||||
attributes PaperTrail is ignoring) in each `update` version. You can use the
|
||||
`version.changeset` method to retrieve it. For example:
|
||||
The best way to diff adjacent versions is to get PaperTrail to do it for you. If
|
||||
you add an `object_changes` column to your `versions` table, PaperTrail will
|
||||
store the `changes` diff in each version. Ignored attributes are omitted.
|
||||
|
||||
```ruby
|
||||
widget = Widget.create name: 'Bob'
|
||||
widget.versions.last.changeset
|
||||
widget.versions.last.changeset # reads object_changes column
|
||||
# {
|
||||
# "name"=>[nil, "Bob"],
|
||||
# "created_at"=>[nil, 2015-08-10 04:10:40 UTC],
|
||||
|
@ -760,11 +738,12 @@ widget.versions.last.changeset
|
|||
Prior to 10.0.0, the `object_changes` were only stored for create and update
|
||||
events. As of 10.0.0, they are stored for all three events.
|
||||
|
||||
Please be aware that PaperTrail doesn't use diffs internally. When I designed
|
||||
PaperTrail I wanted simplicity and robustness so I decided to make each version
|
||||
of an object self-contained. A version stores all of its object's data, not a
|
||||
diff from the previous version. This means you can delete any version without
|
||||
affecting any other.
|
||||
PaperTrail doesn't use diffs internally.
|
||||
|
||||
> When I designed PaperTrail I wanted simplicity and robustness so I decided to
|
||||
> make each version of an object self-contained. A version stores all of its
|
||||
> object's data, not a diff from the previous version. This means you can
|
||||
> delete any version without affecting any other. -Andy
|
||||
|
||||
To diff non-adjacent versions you'll have to write your own code. These
|
||||
libraries may help:
|
||||
|
@ -800,6 +779,30 @@ sql> delete from versions where created_at < '2010-06-01';
|
|||
PaperTrail::Version.where('created_at < ?', 1.day.ago).delete_all
|
||||
```
|
||||
|
||||
### 3.e. Queries
|
||||
|
||||
You can query records in the `versions` table based on their `object` or
|
||||
`object_changes` columns.
|
||||
|
||||
```ruby
|
||||
# Find versions that meet these criteria.
|
||||
PaperTrail::Version.where_object(content: 'Hello', title: 'Article')
|
||||
|
||||
# Find versions before and after attribute `atr` had value `v`:
|
||||
PaperTrail::Version.where_object_changes(atr: 'v')
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
- `where_object_changes_from`
|
||||
- `where_object_changes_to`
|
||||
- `where_attribute_changes`
|
||||
|
||||
Only `where_object` supports text columns. Your `object_changes` column should
|
||||
be a `json` or `jsonb` column if possible. If you must use a `text` column,
|
||||
you'll have to write a [custom
|
||||
`object_changes_adapter`](#6c-custom-object-changes).
|
||||
|
||||
## 4. Saving More Information About Versions
|
||||
|
||||
### 4.a. Finding Out Who Was Responsible For A Change
|
||||
|
@ -1097,10 +1100,12 @@ Be advised that redefining an association is an undocumented feature of Rails.
|
|||
### 5.c. Generators
|
||||
|
||||
PaperTrail has one generator, `paper_trail:install`. It writes, but does not
|
||||
run, a migration file.
|
||||
The migration adds (at least) the `versions` table. The
|
||||
most up-to-date documentation for this generator can be found by running `rails
|
||||
generate paper_trail:install --help`, but a copy is included here for
|
||||
run, a migration file. The migration creates the `versions` table.
|
||||
|
||||
#### Reference
|
||||
|
||||
The most up-to-date documentation for this generator can be found by running
|
||||
`rails generate paper_trail:install --help`, but a copy is included here for
|
||||
convenience.
|
||||
|
||||
```
|
||||
|
@ -1365,17 +1370,24 @@ reading `::PaperTrail::Events::Base#recordable_object_changes`.
|
|||
|
||||
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
|
||||
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).
|
||||
5. where_object_changes_to: Returns the records resulting from the given hash of attributes where the attributes changed *to* the provided value(s).
|
||||
6. where_attribute_changes: Returns the records where the attribute changed to or from any value.
|
||||
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).
|
||||
6. where_attribute_changes: Returns the records where the attribute changed to
|
||||
or from any value.
|
||||
|
||||
Depending on what your adapter does, you may have to implement all three.
|
||||
Depending on your needs, you may choose to implement only a subset of these
|
||||
methods.
|
||||
|
||||
For an example of a complete and useful adapter, see
|
||||
[paper_trail-hashdiff](https://github.com/hashwin/paper_trail-hashdiff)
|
||||
#### Known Adapters
|
||||
|
||||
- [paper_trail-hashdiff](https://github.com/hashwin/paper_trail-hashdiff)
|
||||
|
||||
### 6.d. Excluding the Object Column
|
||||
|
||||
|
|
|
@ -10,4 +10,24 @@ module PaperTrail
|
|||
# @api public
|
||||
class InvalidOption < Error
|
||||
end
|
||||
|
||||
# The application's database schema is not supported.
|
||||
# @api public
|
||||
class UnsupportedSchema < Error
|
||||
end
|
||||
|
||||
# The application's database column type is not supported.
|
||||
# @api public
|
||||
class UnsupportedColumnType < UnsupportedSchema
|
||||
def initialize(method:, expected:, actual:)
|
||||
super(
|
||||
format(
|
||||
"%s expected %s column, got %s",
|
||||
method,
|
||||
expected,
|
||||
actual
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,12 +23,16 @@ module PaperTrail
|
|||
@version_model_class, @attribute
|
||||
)
|
||||
end
|
||||
|
||||
case @version_model_class.columns_hash["object_changes"].type
|
||||
column_type = @version_model_class.columns_hash["object_changes"].type
|
||||
case column_type
|
||||
when :jsonb, :json
|
||||
json
|
||||
else
|
||||
text
|
||||
raise UnsupportedColumnType.new(
|
||||
method: "where_attribute_changes",
|
||||
expected: "json or jsonb",
|
||||
actual: column_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,15 +44,6 @@ module PaperTrail
|
|||
|
||||
@version_model_class.where(sql, @attribute)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def text
|
||||
arel_field = @version_model_class.arel_table[:object_changes]
|
||||
|
||||
@version_model_class.where(
|
||||
::PaperTrail.serializer.where_attribute_changes(arel_field, @attribute)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,13 +28,18 @@ module PaperTrail
|
|||
@version_model_class, @attributes
|
||||
)
|
||||
end
|
||||
case @version_model_class.columns_hash["object_changes"].type
|
||||
column_type = @version_model_class.columns_hash["object_changes"].type
|
||||
case column_type
|
||||
when :jsonb
|
||||
jsonb
|
||||
when :json
|
||||
json
|
||||
else
|
||||
text
|
||||
raise UnsupportedColumnType.new(
|
||||
method: "where_object_changes",
|
||||
expected: "json or jsonb",
|
||||
actual: column_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -59,16 +64,6 @@ module PaperTrail
|
|||
@attributes.each { |field, value| @attributes[field] = [value] }
|
||||
@version_model_class.where("object_changes @> ?", @attributes.to_json)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def text
|
||||
arel_field = @version_model_class.arel_table[:object_changes]
|
||||
where_conditions = @attributes.map { |field, value|
|
||||
::PaperTrail.serializer.where_object_changes_condition(arel_field, field, value)
|
||||
}
|
||||
where_conditions = where_conditions.reduce { |a, e| a.and(e) }
|
||||
@version_model_class.where(where_conditions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,12 +23,16 @@ module PaperTrail
|
|||
@version_model_class, @attributes
|
||||
)
|
||||
end
|
||||
|
||||
case @version_model_class.columns_hash["object_changes"].type
|
||||
column_type = @version_model_class.columns_hash["object_changes"].type
|
||||
case column_type
|
||||
when :jsonb, :json
|
||||
json
|
||||
else
|
||||
text
|
||||
raise UnsupportedColumnType.new(
|
||||
method: "where_object_changes_from",
|
||||
expected: "json or jsonb",
|
||||
actual: column_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,18 +51,6 @@ module PaperTrail
|
|||
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
|
||||
|
|
|
@ -23,12 +23,16 @@ module PaperTrail
|
|||
@version_model_class, @attributes
|
||||
)
|
||||
end
|
||||
|
||||
case @version_model_class.columns_hash["object_changes"].type
|
||||
column_type = @version_model_class.columns_hash["object_changes"].type
|
||||
case column_type
|
||||
when :jsonb, :json
|
||||
json
|
||||
else
|
||||
text
|
||||
raise UnsupportedColumnType.new(
|
||||
method: "where_object_changes_to",
|
||||
expected: "json or jsonb",
|
||||
actual: column_type
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,18 +51,6 @@ module PaperTrail
|
|||
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
|
||||
|
|
|
@ -14,14 +14,6 @@ module PaperTrail
|
|||
ActiveSupport::JSON.encode object
|
||||
end
|
||||
|
||||
# Raises an exception as this operation is not allowed from text columns.
|
||||
def where_attribute_changes(*)
|
||||
raise Error, <<-STR.squish.freeze
|
||||
where_attribute_changes does not support reading JSON from a text
|
||||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
|
||||
# Returns a SQL LIKE condition to be used to match the given field and
|
||||
# value in the serialized object.
|
||||
def where_object_condition(arel_field, field, value)
|
||||
|
@ -39,32 +31,6 @@ module PaperTrail
|
|||
arel_field.matches("%\"#{field}\":#{json_value}%")
|
||||
end
|
||||
end
|
||||
|
||||
def where_object_changes_condition(*)
|
||||
raise Error, <<-STR.squish.freeze
|
||||
where_object_changes no longer supports reading JSON from a text
|
||||
column. The old implementation was inaccurate, returning more records
|
||||
than you wanted. This feature was deprecated in 7.1.0 and removed in
|
||||
8.0.0. The json and jsonb datatypes are still supported. See the
|
||||
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 Error, <<-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
|
||||
|
||||
# Raises an exception as this operation is not allowed from text columns.
|
||||
def where_object_changes_to_condition(*)
|
||||
raise Error, <<-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
|
||||
|
|
|
@ -21,47 +21,11 @@ module PaperTrail
|
|||
::YAML.dump object
|
||||
end
|
||||
|
||||
# Raises an exception as this operation is not allowed from text columns.
|
||||
def where_attribute_changes(*)
|
||||
raise Error, <<-STR.squish.freeze
|
||||
where_attribute_changes does not support reading YAML from a text
|
||||
column. The json and jsonb datatypes are supported.
|
||||
STR
|
||||
end
|
||||
|
||||
# Returns a SQL LIKE condition to be used to match the given field and
|
||||
# value in the serialized object.
|
||||
def where_object_condition(arel_field, field, value)
|
||||
arel_field.matches("%\n#{field}: #{value}\n%")
|
||||
end
|
||||
|
||||
# Returns a SQL LIKE condition to be used to match the given field and
|
||||
# value in the serialized `object_changes`.
|
||||
def where_object_changes_condition(*)
|
||||
raise Error, <<-STR.squish.freeze
|
||||
where_object_changes no longer supports reading YAML from a text
|
||||
column. The old implementation was inaccurate, returning more records
|
||||
than you wanted. This feature was deprecated in 8.1.0 and removed in
|
||||
9.0.0. The json and jsonb datatypes are still supported. See
|
||||
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 Error, <<-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
|
||||
|
||||
# Raises an exception as this operation is not allowed with YAML.
|
||||
def where_object_changes_to_condition(*)
|
||||
raise Error, <<-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,7 +4,7 @@ require "spec_helper"
|
|||
|
||||
module PaperTrail
|
||||
::RSpec.describe Version, type: :model do
|
||||
describe "object_changes column", versioning: true do
|
||||
describe "#object_changes", versioning: true do
|
||||
let(:widget) { Widget.create!(name: "Dashboard") }
|
||||
let(:value) { widget.versions.last.object_changes }
|
||||
|
||||
|
@ -190,9 +190,12 @@ module PaperTrail
|
|||
bicycle.versions.where_attribute_changes(:name)
|
||||
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
||||
else
|
||||
expect do
|
||||
expect {
|
||||
bicycle.versions.where_attribute_changes(:name)
|
||||
end.to raise_error(/does not support reading YAML/)
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_attribute_changes expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -218,7 +221,10 @@ module PaperTrail
|
|||
it "raises error" do
|
||||
expect {
|
||||
widget.versions.where_attribute_changes(:name).to_a
|
||||
}.to(raise_error(/does not support reading YAML from a text column/))
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_attribute_changes expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -311,9 +317,12 @@ module PaperTrail
|
|||
bicycle.versions.where_object_changes(name: "abc")
|
||||
).to match_array(bicycle.versions[0..1])
|
||||
else
|
||||
expect do
|
||||
expect {
|
||||
bicycle.versions.where_object_changes(name: "abc")
|
||||
end.to raise_error(/no longer supports reading YAML/)
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_object_changes expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -342,7 +351,10 @@ module PaperTrail
|
|||
it "raises error" do
|
||||
expect {
|
||||
widget.versions.where_object_changes(name: "foo").to_a
|
||||
}.to(raise_error(/no longer supports reading YAML from a text column/))
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_object_changes expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -389,9 +401,12 @@ module PaperTrail
|
|||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
else
|
||||
expect do
|
||||
expect {
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
end.to raise_error(/does not support reading YAML/)
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_object_changes_from expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -421,7 +436,10 @@ module PaperTrail
|
|||
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/))
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_object_changes_from expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -468,9 +486,12 @@ module PaperTrail
|
|||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
else
|
||||
expect do
|
||||
expect {
|
||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
end.to raise_error(/does not support reading YAML/)
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_object_changes_to expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -503,7 +524,10 @@ module PaperTrail
|
|||
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/))
|
||||
}.to raise_error(
|
||||
UnsupportedColumnType,
|
||||
"where_object_changes_to expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,14 +59,6 @@ module PaperTrail
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".where_object_changes_condition" do
|
||||
it "raises error" do
|
||||
expect {
|
||||
described_class.where_object_changes_condition
|
||||
}.to raise_error(/no longer supports/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue