Merge pull request #1367 from paper-trail-gem/release-12.2.0
Release 12.2.0
This commit is contained in:
commit
f21e9a4368
|
@ -32,11 +32,11 @@ require "bundler/inline"
|
||||||
|
|
||||||
# STEP ONE: What versions are you using?
|
# STEP ONE: What versions are you using?
|
||||||
gemfile(true) do
|
gemfile(true) do
|
||||||
ruby "2.5.1"
|
ruby "3.0.2"
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
gem "activerecord", "5.2.0"
|
gem "activerecord", "6.1.4.1"
|
||||||
gem "minitest", "5.11.3"
|
gem "minitest", "5.11.3"
|
||||||
gem "paper_trail", "9.2.0", require: false
|
gem "paper_trail", "12.1.0", require: false
|
||||||
gem "sqlite3", "1.3.13"
|
gem "sqlite3", "1.3.13"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
||||||
ruby-version: '2.5'
|
ruby-version: '2.6'
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: |
|
run: |
|
||||||
gem install bundler
|
gem install bundler
|
||||||
|
@ -63,13 +63,15 @@ jobs:
|
||||||
# in case it still produces any deprecation warnings.
|
# in case it still produces any deprecation warnings.
|
||||||
#
|
#
|
||||||
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
||||||
ruby: [ '2.5', '2.7', '3.0' ]
|
ruby: [ '2.6', '2.7', '3.0', '3.1' ]
|
||||||
|
|
||||||
exclude:
|
exclude:
|
||||||
# rails 5.2 requires ruby < 3.0
|
# rails 5.2 requires ruby < 3.0
|
||||||
# https://github.com/rails/rails/issues/40938
|
# https://github.com/rails/rails/issues/40938
|
||||||
- ruby: '3.0'
|
- ruby: '3.0'
|
||||||
gemfile: 'rails_5.2'
|
gemfile: 'rails_5.2'
|
||||||
|
- ruby: '3.1'
|
||||||
|
gemfile: 'rails_5.2'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source
|
- name: Checkout source
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
42
.rubocop.yml
42
.rubocop.yml
|
@ -14,25 +14,24 @@ inherit_from: .rubocop_todo.yml
|
||||||
# - Only include permanent config; temporary goes in .rubocop_todo.yml
|
# - Only include permanent config; temporary goes in .rubocop_todo.yml
|
||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
|
# Generated files, like schema.rb, are out of our control.
|
||||||
Exclude:
|
Exclude:
|
||||||
- gemfiles/vendor/bundle/**/* # This dir only shows up on travis ¯\_(ツ)_/¯
|
- gemfiles/*
|
||||||
- spec/dummy_app/db/schema.rb # Generated, out of our control
|
- spec/dummy_app/db/schema.rb
|
||||||
|
|
||||||
# Enable pending cops so we can adopt the code before they are switched on.
|
# Enable pending cops so we can adopt the code before they are switched on.
|
||||||
NewCops: enable
|
NewCops: enable
|
||||||
|
|
||||||
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
||||||
TargetRubyVersion: 2.5
|
TargetRubyVersion: 2.6
|
||||||
|
|
||||||
Bundler/OrderedGems:
|
|
||||||
Exclude:
|
|
||||||
- gemfiles/* # generated by Appraisal
|
|
||||||
|
|
||||||
Layout/ArgumentAlignment:
|
Layout/ArgumentAlignment:
|
||||||
EnforcedStyle: with_fixed_indentation
|
EnforcedStyle: with_fixed_indentation
|
||||||
|
|
||||||
|
# This cop has a bug in 1.22.2 (https://github.com/rubocop/rubocop/issues/10210)
|
||||||
|
# When the bug is fixed, we'll return to using `EnforcedStyle: trailing`.
|
||||||
Layout/DotPosition:
|
Layout/DotPosition:
|
||||||
EnforcedStyle: trailing
|
Enabled: false
|
||||||
|
|
||||||
# Avoid blank lines inside methods. They are a sign that the method is too big.
|
# Avoid blank lines inside methods. They are a sign that the method is too big.
|
||||||
Layout/EmptyLineAfterGuardClause:
|
Layout/EmptyLineAfterGuardClause:
|
||||||
|
@ -57,20 +56,11 @@ Layout/MultilineOperationIndentation:
|
||||||
Layout/ParameterAlignment:
|
Layout/ParameterAlignment:
|
||||||
EnforcedStyle: with_fixed_indentation
|
EnforcedStyle: with_fixed_indentation
|
||||||
|
|
||||||
Layout/SpaceAroundMethodCallOperator:
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
# Use exactly one space on each side of an operator. Do not align operators
|
# Use exactly one space on each side of an operator. Do not align operators
|
||||||
# because it makes the code harder to edit, and makes lines unnecessarily long.
|
# because it makes the code harder to edit, and makes lines unnecessarily long.
|
||||||
Layout/SpaceAroundOperators:
|
Layout/SpaceAroundOperators:
|
||||||
AllowForAlignment: false
|
AllowForAlignment: false
|
||||||
|
|
||||||
Lint/RaiseException:
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
Lint/StructNewOverride:
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
# Migrations often contain long up/down methods, and extracting smaller methods
|
# Migrations often contain long up/down methods, and extracting smaller methods
|
||||||
# from these is of questionable value.
|
# from these is of questionable value.
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
|
@ -100,12 +90,9 @@ Naming/FileName:
|
||||||
- Appraisals
|
- Appraisals
|
||||||
|
|
||||||
# Heredocs are usually assigned to a variable or constant, which already has a
|
# Heredocs are usually assigned to a variable or constant, which already has a
|
||||||
# name, so naming the heredoc doesn't add much value. Feel free to name
|
# name, so naming the delimiter doesn't add much value unless doing so improves
|
||||||
# heredocs that are used as anonymous values (not a variable, constant, or
|
# syntax highlighting. For example, all heredocs containing SQL should be named
|
||||||
# named parameter).
|
# SQL, to support editor syntax highlighting.
|
||||||
#
|
|
||||||
# All heredocs containing SQL should be named SQL, to support editor syntax
|
|
||||||
# highlighting.
|
|
||||||
Naming/HeredocDelimiterNaming:
|
Naming/HeredocDelimiterNaming:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
@ -136,11 +123,6 @@ Rails/SkipsModelValidations:
|
||||||
RSpec/DescribeClass:
|
RSpec/DescribeClass:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# This cop has a bug in 1.35.0
|
|
||||||
# https://github.com/rubocop-hq/rubocop-rspec/issues/799
|
|
||||||
RSpec/DescribedClass:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Yes, ideally examples would be short. Is it possible to pick a limit and say,
|
# Yes, ideally examples would be short. Is it possible to pick a limit and say,
|
||||||
# "no example will ever be longer than this"? Hard to say. Sometimes they're
|
# "no example will ever be longer than this"? Hard to say. Sometimes they're
|
||||||
# quite long.
|
# quite long.
|
||||||
|
@ -164,10 +146,6 @@ Style/BlockDelimiters:
|
||||||
Style/DoubleNegation:
|
Style/DoubleNegation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# This cop is unimportant in this repo.
|
|
||||||
Style/ExponentialNotation:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Avoid annotated tokens except in desperately complicated format strings.
|
# Avoid annotated tokens except in desperately complicated format strings.
|
||||||
# In 99% of format strings they actually make it less readable.
|
# In 99% of format strings they actually make it less readable.
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
|
|
|
@ -6,21 +6,6 @@
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
# versions of RuboCop, may require this file to be generated again.
|
# versions of RuboCop, may require this file to be generated again.
|
||||||
|
|
||||||
# Offense count: 5
|
|
||||||
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
|
|
||||||
Metrics/AbcSize:
|
|
||||||
Max: 17.5 # Goal: 17, the default
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Configuration parameters: IgnoredMethods.
|
|
||||||
Metrics/CyclomaticComplexity:
|
|
||||||
Max: 8 # Goal: 7, the default
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Configuration parameters: IgnoredMethods.
|
|
||||||
Metrics/PerceivedComplexity:
|
|
||||||
Max: 9 # Goal: 8, the default
|
|
||||||
|
|
||||||
# Offense count: 56
|
# Offense count: 56
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Rails/ApplicationRecord:
|
Rails/ApplicationRecord:
|
||||||
|
|
|
@ -24,3 +24,8 @@ appraise "rails-6.1" do
|
||||||
gem "rails", "~> 6.1.0"
|
gem "rails", "~> 6.1.0"
|
||||||
gem "rails-controller-testing", "~> 1.0.5"
|
gem "rails-controller-testing", "~> 1.0.5"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
appraise "rails-7.0" do
|
||||||
|
gem "rails", "~> 7.0.0"
|
||||||
|
gem "rails-controller-testing", "~> 1.0.5"
|
||||||
|
end
|
||||||
|
|
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -17,6 +17,29 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
||||||
|
|
||||||
- None
|
- None
|
||||||
|
|
||||||
|
## 12.2.0 (2022-01-21)
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- [#1365](https://github.com/paper-trail-gem/paper_trail/pull/1365) -
|
||||||
|
Support Rails 7.0
|
||||||
|
- [#1349](https://github.com/paper-trail-gem/paper_trail/pull/1349) -
|
||||||
|
`if:` and `unless:` work with `touch` events now.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- [#1338](https://github.com/paper-trail-gem/paper_trail/pull/1338) -
|
||||||
|
Support Psych version 4
|
||||||
|
- ruby >= 2.6 (was >= 2.5). Ruby 2.5 reached EoL on 2021-03-31.
|
||||||
|
|
||||||
## 12.1.0 (2021-08-30)
|
## 12.1.0 (2021-08-30)
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
19
README.md
19
README.md
|
@ -15,7 +15,7 @@ This is the _user guide_. See also, the
|
||||||
|
|
||||||
Choose version:
|
Choose version:
|
||||||
[Unreleased](https://github.com/paper-trail-gem/paper_trail/blob/master/README.md),
|
[Unreleased](https://github.com/paper-trail-gem/paper_trail/blob/master/README.md),
|
||||||
[12.1](https://github.com/paper-trail-gem/paper_trail/blob/v12.1.0/README.md),
|
[12.2](https://github.com/paper-trail-gem/paper_trail/blob/v12.2.0/README.md),
|
||||||
[11.1](https://github.com/paper-trail-gem/paper_trail/blob/v11.1.0/README.md),
|
[11.1](https://github.com/paper-trail-gem/paper_trail/blob/v11.1.0/README.md),
|
||||||
[10.3](https://github.com/paper-trail-gem/paper_trail/blob/v10.3.1/README.md),
|
[10.3](https://github.com/paper-trail-gem/paper_trail/blob/v10.3.1/README.md),
|
||||||
[9.2](https://github.com/paper-trail-gem/paper_trail/blob/v9.2.0/README.md),
|
[9.2](https://github.com/paper-trail-gem/paper_trail/blob/v9.2.0/README.md),
|
||||||
|
@ -1208,17 +1208,20 @@ class PostVersion < PaperTrail::Version
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
If you only use custom version classes and don't have a `versions` table, you
|
If you only use custom version classes and don't have a `versions` table, you must
|
||||||
must let ActiveRecord know that the `PaperTrail::Version` class is an
|
let ActiveRecord know that your base version class (eg. `ApplicationVersion` below)
|
||||||
`abstract_class`.
|
class is an `abstract_class`.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# app/models/paper_trail/version.rb
|
# app/models/application_version.rb
|
||||||
module PaperTrail
|
class ApplicationVersion < ActiveRecord::Base
|
||||||
class Version < ActiveRecord::Base
|
|
||||||
include PaperTrail::VersionConcern
|
include PaperTrail::VersionConcern
|
||||||
self.abstract_class = true
|
self.abstract_class = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class PostVersion < ApplicationVersion
|
||||||
|
self.table_name = :post_versions
|
||||||
|
self.sequence_name = :post_versions_id_seq
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
13
Rakefile
13
Rakefile
|
@ -38,11 +38,8 @@ task :clean do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc <<~EOS
|
desc "Create the database."
|
||||||
Write a database.yml for the specified RDBMS, and create database. Does not
|
task :create_db do
|
||||||
migrate. Migration happens later in spec_helper.
|
|
||||||
EOS
|
|
||||||
task prepare: %i[clean install_database_yml] do
|
|
||||||
puts format("creating %s database", ENV["DB"])
|
puts format("creating %s database", ENV["DB"])
|
||||||
case ENV["DB"]
|
case ENV["DB"]
|
||||||
when "mysql"
|
when "mysql"
|
||||||
|
@ -59,6 +56,12 @@ task prepare: %i[clean install_database_yml] do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc <<~EOS
|
||||||
|
Write a database.yml for the specified RDBMS, and create database. Does not
|
||||||
|
migrate. Migration happens later in spec_helper.
|
||||||
|
EOS
|
||||||
|
task prepare: %i[clean install_database_yml create_db]
|
||||||
|
|
||||||
require "rspec/core/rake_task"
|
require "rspec/core/rake_task"
|
||||||
desc "Run tests on PaperTrail with RSpec"
|
desc "Run tests on PaperTrail with RSpec"
|
||||||
task(:spec).clear
|
task(:spec).clear
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# This file was generated by Appraisal
|
||||||
|
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "rails", "~> 7.0.0"
|
||||||
|
gem "rails-controller-testing", "~> 1.0.5"
|
||||||
|
|
||||||
|
gemspec path: "../"
|
|
@ -29,6 +29,8 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
|
||||||
# version of ActiveRecord with support for fractional seconds in MySQL.
|
# version of ActiveRecord with support for fractional seconds in MySQL.
|
||||||
# (https://github.com/rails/rails/pull/14359)
|
# (https://github.com/rails/rails/pull/14359)
|
||||||
#
|
#
|
||||||
|
# MySQL users should use the following line for `created_at`
|
||||||
|
# t.datetime :created_at, limit: 6
|
||||||
t.datetime :created_at
|
t.datetime :created_at
|
||||||
end
|
end
|
||||||
add_index :versions, %i(item_type item_id)
|
add_index :versions, %i(item_type item_id)
|
||||||
|
|
|
@ -26,6 +26,8 @@ module PaperTrail
|
||||||
named created_at.
|
named created_at.
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
|
RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
|
||||||
|
|
||||||
extend PaperTrail::Cleaner
|
extend PaperTrail::Cleaner
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
|
@ -32,6 +32,12 @@ module PaperTrail
|
||||||
if defined_enums[attr] && val.is_a?(::String)
|
if defined_enums[attr] && val.is_a?(::String)
|
||||||
# Because PT 4 used to save the string version of enums to `object_changes`
|
# Because PT 4 used to save the string version of enums to `object_changes`
|
||||||
val
|
val
|
||||||
|
elsif PaperTrail::RAILS_GTE_7_0 && val.is_a?(ActiveRecord::Type::Time::Value)
|
||||||
|
# Because Rails 7 time attribute throws a delegation error when you deserialize
|
||||||
|
# it with the factory.
|
||||||
|
# See ActiveRecord::Type::Time::Value crashes when loaded from YAML on rails 7.0
|
||||||
|
# https://github.com/rails/rails/issues/43966
|
||||||
|
val.instance_variable_get(:@time)
|
||||||
else
|
else
|
||||||
AttributeSerializerFactory.for(@klass, attr).deserialize(val)
|
AttributeSerializerFactory.for(@klass, attr).deserialize(val)
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ module PaperTrail
|
||||||
#
|
#
|
||||||
# It is not safe to assume that a new version of rails will be compatible with
|
# It is not safe to assume that a new version of rails will be compatible with
|
||||||
# PaperTrail. PT is only compatible with the versions of rails that it is
|
# PaperTrail. PT is only compatible with the versions of rails that it is
|
||||||
# tested against. See `.travis.yml`.
|
# tested against. See `.github/workflows/test.yml`.
|
||||||
#
|
#
|
||||||
# However, as of
|
# However, as of
|
||||||
# [#1213](https://github.com/paper-trail-gem/paper_trail/pull/1213) our
|
# [#1213](https://github.com/paper-trail-gem/paper_trail/pull/1213) our
|
||||||
|
@ -18,7 +18,7 @@ module PaperTrail
|
||||||
# versions.
|
# versions.
|
||||||
module Compatibility
|
module Compatibility
|
||||||
ACTIVERECORD_GTE = ">= 5.2" # enforced in gemspec
|
ACTIVERECORD_GTE = ">= 5.2" # enforced in gemspec
|
||||||
ACTIVERECORD_LT = "< 7.0" # not enforced in gemspec
|
ACTIVERECORD_LT = "< 7.1" # not enforced in gemspec
|
||||||
|
|
||||||
E_INCOMPATIBLE_AR = <<-EOS
|
E_INCOMPATIBLE_AR = <<-EOS
|
||||||
PaperTrail %s is not compatible with ActiveRecord %s. We allow PT
|
PaperTrail %s is not compatible with ActiveRecord %s. We allow PT
|
||||||
|
|
|
@ -116,6 +116,20 @@ module PaperTrail
|
||||||
@changes_in_latest_version ||= load_changes_in_latest_version
|
@changes_in_latest_version ||= load_changes_in_latest_version
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
def evaluate_only
|
||||||
|
only = @record.paper_trail_options[:only].dup
|
||||||
|
# Remove Hash arguments and then evaluate whether the attributes (the
|
||||||
|
# keys of the hash) should also get pushed into the collection.
|
||||||
|
only.delete_if do |obj|
|
||||||
|
obj.is_a?(Hash) &&
|
||||||
|
obj.each { |attr, condition|
|
||||||
|
only << attr if condition.respond_to?(:call) && condition.call(@record)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
only
|
||||||
|
end
|
||||||
|
|
||||||
# An attributed is "ignored" if it is listed in the `:ignore` option
|
# An attributed is "ignored" if it is listed in the `:ignore` option
|
||||||
# and/or the `:skip` option. Returns true if an ignored attribute has
|
# and/or the `:skip` option. Returns true if an ignored attribute has
|
||||||
# changed.
|
# changed.
|
||||||
|
@ -182,20 +196,28 @@ module PaperTrail
|
||||||
if value.respond_to?(:call)
|
if value.respond_to?(:call)
|
||||||
value.call(@record)
|
value.call(@record)
|
||||||
elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
|
elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
|
||||||
# If it is an attribute that is changing in an existing object,
|
metadatum_from_model_method(event, value)
|
||||||
# be sure to grab the current version.
|
|
||||||
if event != "create" &&
|
|
||||||
@record.has_attribute?(value) &&
|
|
||||||
attribute_changed_in_latest_version?(value)
|
|
||||||
attribute_in_previous_version(value, false)
|
|
||||||
else
|
|
||||||
@record.send(value)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The model method can either be an attribute or a non-attribute method.
|
||||||
|
#
|
||||||
|
# If it is an attribute that is changing in an existing object,
|
||||||
|
# be sure to grab the correct version.
|
||||||
|
#
|
||||||
|
# @api private
|
||||||
|
def metadatum_from_model_method(event, method)
|
||||||
|
if event != "create" &&
|
||||||
|
@record.has_attribute?(method) &&
|
||||||
|
attribute_changed_in_latest_version?(method)
|
||||||
|
attribute_in_previous_version(method, false)
|
||||||
|
else
|
||||||
|
@record.send(method)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def notable_changes
|
def notable_changes
|
||||||
changes_in_latest_version.delete_if { |k, _v|
|
changes_in_latest_version.delete_if { |k, _v|
|
||||||
|
@ -207,16 +229,9 @@ module PaperTrail
|
||||||
def notably_changed
|
def notably_changed
|
||||||
# Memoized to reduce memory usage
|
# Memoized to reduce memory usage
|
||||||
@notably_changed ||= begin
|
@notably_changed ||= begin
|
||||||
only = @record.paper_trail_options[:only].dup
|
only = evaluate_only
|
||||||
# Remove Hash arguments and then evaluate whether the attributes (the
|
cani = changed_and_not_ignored
|
||||||
# keys of the hash) should also get pushed into the collection.
|
only.empty? ? cani : (cani & only)
|
||||||
only.delete_if do |obj|
|
|
||||||
obj.is_a?(Hash) &&
|
|
||||||
obj.each { |attr, condition|
|
|
||||||
only << attr if condition.respond_to?(:call) && condition.call(@record)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,16 +35,21 @@ module PaperTrail
|
||||||
if record_object?
|
if record_object?
|
||||||
data[:object] = recordable_object(@is_touch)
|
data[:object] = recordable_object(@is_touch)
|
||||||
end
|
end
|
||||||
if record_object_changes?
|
merge_object_changes_into(data)
|
||||||
changes = @force_changes.nil? ? notable_changes : @force_changes
|
|
||||||
data[:object_changes] = prepare_object_changes(changes)
|
|
||||||
end
|
|
||||||
merge_item_subtype_into(data)
|
merge_item_subtype_into(data)
|
||||||
merge_metadata_into(data)
|
merge_metadata_into(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
def merge_object_changes_into(data)
|
||||||
|
if record_object_changes?
|
||||||
|
changes = @force_changes.nil? ? notable_changes : @force_changes
|
||||||
|
data[:object_changes] = prepare_object_changes(changes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# `touch` cannot record `object_changes` because rails' `touch` does not
|
# `touch` cannot record `object_changes` because rails' `touch` does not
|
||||||
# perform dirty-tracking. Specifically, methods from `Dirty`, like
|
# perform dirty-tracking. Specifically, methods from `Dirty`, like
|
||||||
# `saved_changes`, return the same values before and after `touch`.
|
# `saved_changes`, return the same values before and after `touch`.
|
||||||
|
|
|
@ -40,8 +40,7 @@ module PaperTrail
|
||||||
@model_class.after_create { |r|
|
@model_class.after_create { |r|
|
||||||
r.paper_trail.record_create if r.paper_trail.save_version?
|
r.paper_trail.record_create if r.paper_trail.save_version?
|
||||||
}
|
}
|
||||||
return if @model_class.paper_trail_options[:on].include?(:create)
|
append_option_uniquely(:on, :create)
|
||||||
@model_class.paper_trail_options[:on] << :create
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a callback that records a version before or after a "destroy" event.
|
# Adds a callback that records a version before or after a "destroy" event.
|
||||||
|
@ -49,7 +48,6 @@ module PaperTrail
|
||||||
# @api public
|
# @api public
|
||||||
def on_destroy(recording_order = "before")
|
def on_destroy(recording_order = "before")
|
||||||
assert_valid_recording_order_for_on_destroy(recording_order)
|
assert_valid_recording_order_for_on_destroy(recording_order)
|
||||||
|
|
||||||
@model_class.send(
|
@model_class.send(
|
||||||
"#{recording_order}_destroy",
|
"#{recording_order}_destroy",
|
||||||
lambda do |r|
|
lambda do |r|
|
||||||
|
@ -57,9 +55,7 @@ module PaperTrail
|
||||||
r.paper_trail.record_destroy(recording_order)
|
r.paper_trail.record_destroy(recording_order)
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
append_option_uniquely(:on, :destroy)
|
||||||
return if @model_class.paper_trail_options[:on].include?(:destroy)
|
|
||||||
@model_class.paper_trail_options[:on] << :destroy
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a callback that records a version after an "update" event.
|
# Adds a callback that records a version after an "update" event.
|
||||||
|
@ -81,8 +77,7 @@ module PaperTrail
|
||||||
@model_class.after_update { |r|
|
@model_class.after_update { |r|
|
||||||
r.paper_trail.clear_version_instance
|
r.paper_trail.clear_version_instance
|
||||||
}
|
}
|
||||||
return if @model_class.paper_trail_options[:on].include?(:update)
|
append_option_uniquely(:on, :update)
|
||||||
@model_class.paper_trail_options[:on] << :update
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a callback that records a version after a "touch" event.
|
# Adds a callback that records a version after a "touch" event.
|
||||||
|
@ -96,11 +91,13 @@ module PaperTrail
|
||||||
# @api public
|
# @api public
|
||||||
def on_touch
|
def on_touch
|
||||||
@model_class.after_touch { |r|
|
@model_class.after_touch { |r|
|
||||||
|
if r.paper_trail.save_version?
|
||||||
r.paper_trail.record_update(
|
r.paper_trail.record_update(
|
||||||
force: RAILS_LT_6_0,
|
force: RAILS_LT_6_0,
|
||||||
in_after_callback: true,
|
in_after_callback: true,
|
||||||
is_touch: true
|
is_touch: true
|
||||||
)
|
)
|
||||||
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -127,6 +124,13 @@ module PaperTrail
|
||||||
RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
|
RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
|
||||||
private_constant :RAILS_LT_6_0
|
private_constant :RAILS_LT_6_0
|
||||||
|
|
||||||
|
# @api private
|
||||||
|
def append_option_uniquely(option, value)
|
||||||
|
collection = @model_class.paper_trail_options.fetch(option)
|
||||||
|
return if collection.include?(value)
|
||||||
|
collection << value
|
||||||
|
end
|
||||||
|
|
||||||
# Raises an error if the provided class is an `abstract_class`.
|
# Raises an error if the provided class is an `abstract_class`.
|
||||||
# @api private
|
# @api private
|
||||||
def assert_concrete_activerecord_class(class_name)
|
def assert_concrete_activerecord_class(class_name)
|
||||||
|
@ -205,6 +209,14 @@ module PaperTrail
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Process an `ignore`, `skip`, or `only` option.
|
||||||
|
def event_attribute_option(option_name)
|
||||||
|
[@model_class.paper_trail_options[option_name]].
|
||||||
|
flatten.
|
||||||
|
compact.
|
||||||
|
map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
||||||
|
end
|
||||||
|
|
||||||
def get_versions_scope(options)
|
def get_versions_scope(options)
|
||||||
options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
|
options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
|
||||||
end
|
end
|
||||||
|
@ -239,12 +251,8 @@ module PaperTrail
|
||||||
@model_class.paper_trail_options = options.dup
|
@model_class.paper_trail_options = options.dup
|
||||||
|
|
||||||
%i[ignore skip only].each do |k|
|
%i[ignore skip only].each do |k|
|
||||||
@model_class.paper_trail_options[k] = [@model_class.paper_trail_options[k]].
|
@model_class.paper_trail_options[k] = event_attribute_option(k)
|
||||||
flatten.
|
|
||||||
compact.
|
|
||||||
map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@model_class.paper_trail_options[:meta] ||= {}
|
@model_class.paper_trail_options[:meta] ||= {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ module PaperTrail
|
||||||
extend self # makes all instance methods become module methods as well
|
extend self # makes all instance methods become module methods as well
|
||||||
|
|
||||||
def load(string)
|
def load(string)
|
||||||
::YAML.load string
|
::YAML.respond_to?(:unsafe_load) ? ::YAML.unsafe_load(string) : ::YAML.load(string)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
||||||
|
|
|
@ -16,7 +16,7 @@ module PaperTrail
|
||||||
extend ::ActiveSupport::Concern
|
extend ::ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
belongs_to :item, polymorphic: true, optional: true
|
belongs_to :item, polymorphic: true, optional: true, inverse_of: false
|
||||||
validates_presence_of :event
|
validates_presence_of :event
|
||||||
after_create :enforce_version_limit!
|
after_create :enforce_version_limit!
|
||||||
end
|
end
|
||||||
|
@ -376,10 +376,11 @@ module PaperTrail
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
def version_limit
|
def version_limit
|
||||||
if limit_option?(item.class)
|
klass = item.class
|
||||||
item.class.paper_trail_options[:limit]
|
if limit_option?(klass)
|
||||||
elsif base_class_limit_option?(item.class)
|
klass.paper_trail_options[:limit]
|
||||||
item.class.base_class.paper_trail_options[:limit]
|
elsif base_class_limit_option?(klass)
|
||||||
|
klass.base_class.paper_trail_options[:limit]
|
||||||
else
|
else
|
||||||
PaperTrail.config.version_limit
|
PaperTrail.config.version_limit
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ module PaperTrail
|
||||||
# People are encouraged to use `PaperTrail.gem_version` instead.
|
# People are encouraged to use `PaperTrail.gem_version` instead.
|
||||||
module VERSION
|
module VERSION
|
||||||
MAJOR = 12
|
MAJOR = 12
|
||||||
MINOR = 1
|
MINOR = 2
|
||||||
TINY = 0
|
TINY = 0
|
||||||
|
|
||||||
# Set PRE to nil unless it's a pre-release (beta, rc, etc.)
|
# Set PRE to nil unless it's a pre-release (beta, rc, etc.)
|
||||||
|
|
|
@ -43,9 +43,7 @@ has been destroyed.
|
||||||
# about 3 years, per https://www.ruby-lang.org/en/downloads/branches/
|
# about 3 years, per https://www.ruby-lang.org/en/downloads/branches/
|
||||||
#
|
#
|
||||||
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
# See "Lowest supported ruby version" in CONTRIBUTING.md
|
||||||
#
|
s.required_ruby_version = ">= 2.6.0"
|
||||||
# Ruby 2.5 reaches EoL on 2021-03-31.
|
|
||||||
s.required_ruby_version = ">= 2.5.0"
|
|
||||||
|
|
||||||
# We no longer specify a maximum activerecord version.
|
# We no longer specify a maximum activerecord version.
|
||||||
# See discussion in paper_trail/compatibility.rb
|
# See discussion in paper_trail/compatibility.rb
|
||||||
|
@ -53,8 +51,8 @@ has been destroyed.
|
||||||
s.add_dependency "request_store", "~> 1.1"
|
s.add_dependency "request_store", "~> 1.1"
|
||||||
|
|
||||||
s.add_development_dependency "appraisal", "~> 2.4.1"
|
s.add_development_dependency "appraisal", "~> 2.4.1"
|
||||||
s.add_development_dependency "byebug", "~> 11.0"
|
s.add_development_dependency "byebug", "~> 11.1"
|
||||||
s.add_development_dependency "ffaker", "~> 2.19.0"
|
s.add_development_dependency "ffaker", "~> 2.20"
|
||||||
s.add_development_dependency "generator_spec", "~> 0.9.4"
|
s.add_development_dependency "generator_spec", "~> 0.9.4"
|
||||||
s.add_development_dependency "memory_profiler", "~> 1.0.0"
|
s.add_development_dependency "memory_profiler", "~> 1.0.0"
|
||||||
|
|
||||||
|
@ -65,13 +63,13 @@ has been destroyed.
|
||||||
|
|
||||||
s.add_development_dependency "rake", "~> 13.0"
|
s.add_development_dependency "rake", "~> 13.0"
|
||||||
s.add_development_dependency "rspec-rails", "~> 5.0.2"
|
s.add_development_dependency "rspec-rails", "~> 5.0.2"
|
||||||
s.add_development_dependency "rubocop", "~> 1.20.0"
|
s.add_development_dependency "rubocop", "~> 1.22.2"
|
||||||
s.add_development_dependency "rubocop-packaging", "~> 0.5.1"
|
s.add_development_dependency "rubocop-packaging", "~> 0.5.1"
|
||||||
s.add_development_dependency "rubocop-performance", "~> 1.11.5"
|
s.add_development_dependency "rubocop-performance", "~> 1.11.5"
|
||||||
s.add_development_dependency "rubocop-rails", "~> 2.11.3"
|
s.add_development_dependency "rubocop-rails", "~> 2.12.4"
|
||||||
s.add_development_dependency "rubocop-rake", "~> 0.6.0"
|
s.add_development_dependency "rubocop-rake", "~> 0.6.0"
|
||||||
s.add_development_dependency "rubocop-rspec", "~> 2.4.0"
|
s.add_development_dependency "rubocop-rspec", "~> 2.5.0"
|
||||||
s.add_development_dependency "simplecov", ">= 0.21", "< 0.22"
|
s.add_development_dependency "simplecov", "~> 0.21.2"
|
||||||
|
|
||||||
# ## Database Adapters
|
# ## Database Adapters
|
||||||
#
|
#
|
||||||
|
@ -83,7 +81,7 @@ has been destroyed.
|
||||||
# Currently, all versions of rails we test against are consistent. In the past,
|
# Currently, all versions of rails we test against are consistent. In the past,
|
||||||
# when we tested against rails 4.2, we had to specify database adapters in
|
# when we tested against rails 4.2, we had to specify database adapters in
|
||||||
# `Appraisals`.
|
# `Appraisals`.
|
||||||
s.add_development_dependency "mysql2", "~> 0.5"
|
s.add_development_dependency "mysql2", "~> 0.5.3"
|
||||||
s.add_development_dependency "pg", ">= 0.18", "< 2.0"
|
s.add_development_dependency "pg", "~> 1.2"
|
||||||
s.add_development_dependency "sqlite3", "~> 1.4"
|
s.add_development_dependency "sqlite3", "~> 1.4"
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,4 +2,6 @@
|
||||||
|
|
||||||
class Car < Vehicle
|
class Car < Vehicle
|
||||||
has_paper_trail
|
has_paper_trail
|
||||||
|
attribute :color, type: ActiveModel::Type::String
|
||||||
|
attr_accessor :top_speed
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# See also `Vegetable` which uses `JsonbVersion`.
|
||||||
class Fruit < ActiveRecord::Base
|
class Fruit < ActiveRecord::Base
|
||||||
if ENV["DB"] == "postgres" || JsonVersion.table_exists?
|
if ENV["DB"] == "postgres"
|
||||||
has_paper_trail versions: { class_name: "JsonVersion" }
|
has_paper_trail versions: { class_name: "JsonVersion" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,12 +2,8 @@
|
||||||
|
|
||||||
# Demonstrates the `if` and `unless` configuration options.
|
# Demonstrates the `if` and `unless` configuration options.
|
||||||
class Translation < ActiveRecord::Base
|
class Translation < ActiveRecord::Base
|
||||||
# Has a `type` column, but it's not used for STI.
|
|
||||||
# TODO: rename column
|
|
||||||
self.inheritance_column = nil
|
|
||||||
|
|
||||||
has_paper_trail(
|
has_paper_trail(
|
||||||
if: proc { |t| t.language_code == "US" },
|
if: proc { |t| t.language_code == "US" },
|
||||||
unless: proc { |t| t.type == "DRAFT" }
|
unless: proc { |t| t.draft_status == "DRAFT" }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# See also `Fruit` which uses `JsonVersion`.
|
||||||
|
class Vegetable < ActiveRecord::Base
|
||||||
|
has_paper_trail versions: {
|
||||||
|
class_name: ENV["DB"] == "postgres" ? "JsonbVersion" : "PaperTrail::Version"
|
||||||
|
}, on: %i[create update]
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class JsonbVersion < ActiveRecord::Base
|
||||||
|
include PaperTrail::VersionConcern
|
||||||
|
|
||||||
|
self.table_name = "jsonb_versions"
|
||||||
|
end
|
|
@ -128,16 +128,19 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
|
||||||
add_index :no_object_versions, %i[item_type item_id]
|
add_index :no_object_versions, %i[item_type item_id]
|
||||||
|
|
||||||
if ENV["DB"] == "postgres"
|
if ENV["DB"] == "postgres"
|
||||||
create_table :json_versions, force: true do |t|
|
%w[json jsonb].each do |j|
|
||||||
|
table_name = j + "_versions"
|
||||||
|
create_table table_name, force: true do |t|
|
||||||
t.string :item_type, null: false
|
t.string :item_type, null: false
|
||||||
t.integer :item_id, null: false
|
t.bigint :item_id, null: false
|
||||||
t.string :event, null: false
|
t.string :event, null: false
|
||||||
t.string :whodunnit
|
t.string :whodunnit
|
||||||
t.json :object
|
t.public_send j, :object
|
||||||
t.json :object_changes
|
t.public_send j, :object_changes
|
||||||
t.datetime :created_at, limit: 6
|
t.datetime :created_at, limit: 6
|
||||||
end
|
end
|
||||||
add_index :json_versions, %i[item_type item_id]
|
add_index table_name, %i[item_type item_id]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :not_on_updates, force: true do |t|
|
create_table :not_on_updates, force: true do |t|
|
||||||
|
@ -249,10 +252,10 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :translations, force: true do |t|
|
create_table :translations, force: true do |t|
|
||||||
t.string :headline
|
|
||||||
t.string :content
|
t.string :content
|
||||||
|
t.string :draft_status
|
||||||
|
t.string :headline
|
||||||
t.string :language_code
|
t.string :language_code
|
||||||
t.string :type
|
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :gadgets, force: true do |t|
|
create_table :gadgets, force: true do |t|
|
||||||
|
@ -277,8 +280,9 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :fruits, force: true do |t|
|
create_table :fruits, force: true do |t|
|
||||||
t.string :name
|
|
||||||
t.string :color
|
t.string :color
|
||||||
|
t.integer :mass
|
||||||
|
t.string :name
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :boolits, force: true do |t|
|
create_table :boolits, force: true do |t|
|
||||||
|
@ -358,6 +362,12 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
|
||||||
t.integer :parent_id
|
t.integer :parent_id
|
||||||
t.integer :partner_id
|
t.integer :partner_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table :vegetables, force: true do |t|
|
||||||
|
t.string :color
|
||||||
|
t.integer :mass
|
||||||
|
t.string :name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
|
|
|
@ -4,8 +4,8 @@ require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe Animal, type: :model, versioning: true do
|
RSpec.describe Animal, type: :model, versioning: true do
|
||||||
it "baseline test setup" do
|
it "baseline test setup" do
|
||||||
expect(Animal.new).to be_versioned
|
expect(described_class.new).to be_versioned
|
||||||
expect(Animal.inheritance_column).to eq("species")
|
expect(described_class.inheritance_column).to eq("species")
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#descends_from_active_record?" do
|
describe "#descends_from_active_record?" do
|
||||||
|
@ -15,7 +15,7 @@ RSpec.describe Animal, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "works with custom STI inheritance column" do
|
it "works with custom STI inheritance column" do
|
||||||
animal = Animal.create(name: "Animal")
|
animal = described_class.create(name: "Animal")
|
||||||
animal.update(name: "Animal from the Muppets")
|
animal.update(name: "Animal from the Muppets")
|
||||||
animal.update(name: "Animal Muppet")
|
animal.update(name: "Animal Muppet")
|
||||||
animal.destroy
|
animal.destroy
|
||||||
|
@ -46,7 +46,7 @@ RSpec.describe Animal, type: :model, versioning: true do
|
||||||
it "allows the inheritance_column (species) to be updated" do
|
it "allows the inheritance_column (species) to be updated" do
|
||||||
cat = Cat.create!(name: "Leo")
|
cat = Cat.create!(name: "Leo")
|
||||||
cat.update(name: "Spike", species: "Dog")
|
cat.update(name: "Spike", species: "Dog")
|
||||||
dog = Animal.find(cat.id)
|
dog = described_class.find(cat.id)
|
||||||
expect(dog).to be_instance_of(Dog)
|
expect(dog).to be_instance_of(Dog)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ RSpec.describe Article, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with an item" do
|
context "with an item" do
|
||||||
let(:article) { Article.new(title: initial_title) }
|
let(:article) { described_class.new(title: initial_title) }
|
||||||
let(:initial_title) { "Foobar" }
|
let(:initial_title) { "Foobar" }
|
||||||
|
|
||||||
context "when it is created" do
|
context "when it is created" do
|
||||||
|
|
|
@ -5,7 +5,7 @@ require "spec_helper"
|
||||||
RSpec.describe Book, versioning: true do
|
RSpec.describe Book, versioning: true do
|
||||||
context "with :has_many :through" do
|
context "with :has_many :through" do
|
||||||
it "store version on source <<" do
|
it "store version on source <<" do
|
||||||
book = Book.create(title: "War and Peace")
|
book = described_class.create(title: "War and Peace")
|
||||||
dostoyevsky = Person.create(name: "Dostoyevsky")
|
dostoyevsky = Person.create(name: "Dostoyevsky")
|
||||||
Person.create(name: "Solzhenitsyn")
|
Person.create(name: "Solzhenitsyn")
|
||||||
count = PaperTrail::Version.count
|
count = PaperTrail::Version.count
|
||||||
|
@ -15,7 +15,7 @@ RSpec.describe Book, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "store version on source create" do
|
it "store version on source create" do
|
||||||
book = Book.create(title: "War and Peace")
|
book = described_class.create(title: "War and Peace")
|
||||||
Person.create(name: "Dostoyevsky")
|
Person.create(name: "Dostoyevsky")
|
||||||
Person.create(name: "Solzhenitsyn")
|
Person.create(name: "Solzhenitsyn")
|
||||||
count = PaperTrail::Version.count
|
count = PaperTrail::Version.count
|
||||||
|
@ -27,7 +27,7 @@ RSpec.describe Book, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "store version on join destroy" do
|
it "store version on join destroy" do
|
||||||
book = Book.create(title: "War and Peace")
|
book = described_class.create(title: "War and Peace")
|
||||||
dostoyevsky = Person.create(name: "Dostoyevsky")
|
dostoyevsky = Person.create(name: "Dostoyevsky")
|
||||||
Person.create(name: "Solzhenitsyn")
|
Person.create(name: "Solzhenitsyn")
|
||||||
(book.authors << dostoyevsky)
|
(book.authors << dostoyevsky)
|
||||||
|
@ -39,7 +39,7 @@ RSpec.describe Book, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "store version on join clear" do
|
it "store version on join clear" do
|
||||||
book = Book.create(title: "War and Peace")
|
book = described_class.create(title: "War and Peace")
|
||||||
dostoyevsky = Person.create(name: "Dostoyevsky")
|
dostoyevsky = Person.create(name: "Dostoyevsky")
|
||||||
Person.create(name: "Solzhenitsyn")
|
Person.create(name: "Solzhenitsyn")
|
||||||
book.authors << dostoyevsky
|
book.authors << dostoyevsky
|
||||||
|
@ -53,7 +53,7 @@ RSpec.describe Book, versioning: true do
|
||||||
|
|
||||||
context "when a persisted record is updated then destroyed" do
|
context "when a persisted record is updated then destroyed" do
|
||||||
it "has changes" do
|
it "has changes" do
|
||||||
book = Book.create! title: "A"
|
book = described_class.create! title: "A"
|
||||||
changes = YAML.load book.versions.last.attributes["object_changes"]
|
changes = YAML.load book.versions.last.attributes["object_changes"]
|
||||||
expect(changes).to eq("id" => [nil, book.id], "title" => [nil, "A"])
|
expect(changes).to eq("id" => [nil, book.id], "title" => [nil, "A"])
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ require "spec_helper"
|
||||||
require "support/custom_json_serializer"
|
require "support/custom_json_serializer"
|
||||||
|
|
||||||
RSpec.describe Boolit, type: :model, versioning: true do
|
RSpec.describe Boolit, type: :model, versioning: true do
|
||||||
let(:boolit) { Boolit.create! }
|
let(:boolit) { described_class.create! }
|
||||||
|
|
||||||
before { boolit.update!(name: FFaker::Name.name) }
|
before { boolit.update!(name: FFaker::Name.name) }
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ RSpec.describe Boolit, type: :model, versioning: true do
|
||||||
before { boolit.update!(scoped: false) }
|
before { boolit.update!(scoped: false) }
|
||||||
|
|
||||||
it "is NOT scoped" do
|
it "is NOT scoped" do
|
||||||
expect(Boolit.first).to be_nil
|
expect(described_class.first).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "still can be reified and persisted" do
|
it "still can be reified and persisted" do
|
||||||
|
|
|
@ -7,9 +7,28 @@ RSpec.describe Car, type: :model do
|
||||||
|
|
||||||
describe "changeset", versioning: true do
|
describe "changeset", versioning: true do
|
||||||
it "has the expected keys (see issue 738)" do
|
it "has the expected keys (see issue 738)" do
|
||||||
car = Car.create!(name: "Alice")
|
car = described_class.create!(name: "Alice")
|
||||||
car.update(name: "Bob")
|
car.update(name: "Bob")
|
||||||
assert_includes car.versions.last.changeset.keys, "name"
|
assert_includes car.versions.last.changeset.keys, "name"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "attributes and accessors", versioning: true do
|
||||||
|
it "reifies attributes that are not AR attributes" do
|
||||||
|
car = described_class.create name: "Pinto", color: "green"
|
||||||
|
car.update color: "yellow"
|
||||||
|
car.update color: "brown"
|
||||||
|
expect(car.versions.second.reify.color).to eq("yellow")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reifies attributes that once were attributes but now just attr_accessor" do
|
||||||
|
car = described_class.create name: "Pinto", color: "green"
|
||||||
|
car.update color: "yellow"
|
||||||
|
changes = PaperTrail::Serializers::YAML.load(car.versions.last.attributes["object"])
|
||||||
|
changes[:top_speed] = 80
|
||||||
|
car.versions.first.update object: changes.to_yaml
|
||||||
|
car.reload
|
||||||
|
expect(car.versions.first.reify.top_speed).to eq(80)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,9 +12,9 @@ RSpec.describe CustomPrimaryKeyRecord, type: :model do
|
||||||
version = custom_primary_key_record.versions.last
|
version = custom_primary_key_record.versions.last
|
||||||
expect(version).to be_a(CustomPrimaryKeyRecordVersion)
|
expect(version).to be_a(CustomPrimaryKeyRecordVersion)
|
||||||
version_from_db = CustomPrimaryKeyRecordVersion.last
|
version_from_db = CustomPrimaryKeyRecordVersion.last
|
||||||
expect(version_from_db.reify).to be_a(CustomPrimaryKeyRecord)
|
expect(version_from_db.reify).to be_a(described_class)
|
||||||
custom_primary_key_record.destroy
|
custom_primary_key_record.destroy
|
||||||
expect(CustomPrimaryKeyRecordVersion.last.reify).to be_a(CustomPrimaryKeyRecord)
|
expect(CustomPrimaryKeyRecordVersion.last.reify).to be_a(described_class)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ require "spec_helper"
|
||||||
RSpec.describe Document, type: :model, versioning: true do
|
RSpec.describe Document, type: :model, versioning: true do
|
||||||
describe "have_a_version_with matcher" do
|
describe "have_a_version_with matcher" do
|
||||||
it "works with custom versions association" do
|
it "works with custom versions association" do
|
||||||
document = Document.create!(name: "Foo")
|
document = described_class.create!(name: "Foo")
|
||||||
document.update!(name: "Bar")
|
document.update!(name: "Bar")
|
||||||
expect(document).to have_a_version_with(name: "Foo")
|
expect(document).to have_a_version_with(name: "Foo")
|
||||||
end
|
end
|
||||||
|
@ -13,7 +13,7 @@ RSpec.describe Document, type: :model, versioning: true do
|
||||||
|
|
||||||
describe "#paper_trail.next_version" do
|
describe "#paper_trail.next_version" do
|
||||||
it "returns the expected document" do
|
it "returns the expected document" do
|
||||||
doc = Document.create
|
doc = described_class.create
|
||||||
doc.update(name: "Doc 1")
|
doc.update(name: "Doc 1")
|
||||||
reified = doc.paper_trail_versions.last.reify
|
reified = doc.paper_trail_versions.last.reify
|
||||||
expect(doc.name).to(eq(reified.paper_trail.next_version.name))
|
expect(doc.name).to(eq(reified.paper_trail.next_version.name))
|
||||||
|
@ -22,7 +22,7 @@ RSpec.describe Document, type: :model, versioning: true do
|
||||||
|
|
||||||
describe "#paper_trail.previous_version" do
|
describe "#paper_trail.previous_version" do
|
||||||
it "returns the expected document" do
|
it "returns the expected document" do
|
||||||
doc = Document.create
|
doc = described_class.create
|
||||||
doc.update(name: "Doc 1")
|
doc.update(name: "Doc 1")
|
||||||
doc.update(name: "Doc 2")
|
doc.update(name: "Doc 2")
|
||||||
expect(doc.paper_trail_versions.length).to(eq(3))
|
expect(doc.paper_trail_versions.length).to(eq(3))
|
||||||
|
@ -32,7 +32,7 @@ RSpec.describe Document, type: :model, versioning: true do
|
||||||
|
|
||||||
describe "#paper_trail_versions" do
|
describe "#paper_trail_versions" do
|
||||||
it "returns the expected version records" do
|
it "returns the expected version records" do
|
||||||
doc = Document.create
|
doc = described_class.create
|
||||||
doc.update(name: "Doc 1")
|
doc.update(name: "Doc 1")
|
||||||
expect(doc.paper_trail_versions.length).to(eq(2))
|
expect(doc.paper_trail_versions.length).to(eq(2))
|
||||||
expect(doc.paper_trail_versions.map(&:event)).to(
|
expect(doc.paper_trail_versions.map(&:event)).to(
|
||||||
|
@ -43,7 +43,7 @@ RSpec.describe Document, type: :model, versioning: true do
|
||||||
|
|
||||||
describe "#versions" do
|
describe "#versions" do
|
||||||
it "does not respond to versions method" do
|
it "does not respond to versions method" do
|
||||||
doc = Document.create
|
doc = described_class.create
|
||||||
doc.update(name: "Doc 1")
|
doc.update(name: "Doc 1")
|
||||||
expect(doc).not_to respond_to(:versions)
|
expect(doc).not_to respond_to(:versions)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ require "support/performance_helpers"
|
||||||
|
|
||||||
RSpec.describe(FooWidget, versioning: true) do
|
RSpec.describe(FooWidget, versioning: true) do
|
||||||
context "with a subclass" do
|
context "with a subclass" do
|
||||||
let(:foo) { FooWidget.create }
|
let(:foo) { described_class.create }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
foo.update!(name: "Foo")
|
foo.update!(name: "Foo")
|
||||||
|
@ -26,7 +26,7 @@ RSpec.describe(FooWidget, versioning: true) do
|
||||||
before { foo.destroy }
|
before { foo.destroy }
|
||||||
|
|
||||||
it "reify with the correct type" do
|
it "reify with the correct type" do
|
||||||
assert_kind_of(FooWidget, foo.versions.last.reify)
|
assert_kind_of(described_class, foo.versions.last.reify)
|
||||||
expect(PaperTrail::Version.last.previous).to(eq(foo.versions[1]))
|
expect(PaperTrail::Version.last.previous).to(eq(foo.versions[1]))
|
||||||
expect(PaperTrail::Version.last.next).to(be_nil)
|
expect(PaperTrail::Version.last.next).to(be_nil)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,19 +2,54 @@
|
||||||
|
|
||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
|
|
||||||
if ENV["DB"] == "postgres" || JsonVersion.table_exists?
|
if ENV["DB"] == "postgres" && JsonVersion.table_exists?
|
||||||
RSpec.describe Fruit, type: :model, versioning: true do
|
RSpec.describe Fruit, type: :model, versioning: true do
|
||||||
describe "have_a_version_with_changes matcher" do
|
describe "have_a_version_with_changes matcher" do
|
||||||
it "works with Fruit because Fruit uses JsonVersion" do
|
it "works with Fruit because Fruit uses JsonVersion" do
|
||||||
# As of PT 9.0.0, with_version_changes only supports json(b) columns,
|
# As of PT 9.0.0, with_version_changes only supports json(b) columns,
|
||||||
# so that's why were testing the have_a_version_with_changes matcher
|
# so that's why were testing the have_a_version_with_changes matcher
|
||||||
# here.
|
# here.
|
||||||
banana = Fruit.create!(color: "Red", name: "Banana")
|
banana = described_class.create!(color: "Red", name: "Banana")
|
||||||
banana.update!(color: "Yellow")
|
banana.update!(color: "Yellow")
|
||||||
expect(banana).to have_a_version_with_changes(color: "Yellow")
|
expect(banana).to have_a_version_with_changes(color: "Yellow")
|
||||||
expect(banana).not_to have_a_version_with_changes(color: "Pink")
|
expect(banana).not_to have_a_version_with_changes(color: "Pink")
|
||||||
expect(banana).not_to have_a_version_with_changes(color: "Yellow", name: "Kiwi")
|
expect(banana).not_to have_a_version_with_changes(color: "Yellow", name: "Kiwi")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "queries of versions", versioning: true do
|
||||||
|
let!(:fruit) { described_class.create(name: "Apple", mass: 1, color: "green") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.create(name: "Pear")
|
||||||
|
fruit.update(name: "Fidget")
|
||||||
|
fruit.update(name: "Digit")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "return the fruit whose name has changed" do
|
||||||
|
result = JsonVersion.where_attribute_changes(:name).map(&:item)
|
||||||
|
expect(result).to include(fruit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the fruit whose name was Fidget" do
|
||||||
|
result = JsonVersion.where_object_changes_from({ name: "Fidget" }).map(&:item)
|
||||||
|
expect(result).to include(fruit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the fruit whose name became Digit" do
|
||||||
|
result = JsonVersion.where_object_changes_to({ name: "Digit" }).map(&:item)
|
||||||
|
expect(result).to include(fruit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the fruit where the object was named Fidget before it changed" do
|
||||||
|
result = JsonVersion.where_object({ name: "Fidget" }).map(&:item)
|
||||||
|
expect(result).to include(fruit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the fruit that changed to Fidget" do
|
||||||
|
result = JsonVersion.where_object_changes({ name: "Fidget" }).map(&:item)
|
||||||
|
expect(result).to include(fruit)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe Gadget, type: :model do
|
RSpec.describe Gadget, type: :model do
|
||||||
let(:gadget) { Gadget.create!(name: "Wrench", brand: "Acme") }
|
let(:gadget) { described_class.create!(name: "Wrench", brand: "Acme") }
|
||||||
|
|
||||||
it { is_expected.to be_versioned }
|
it { is_expected.to be_versioned }
|
||||||
|
|
||||||
|
@ -35,7 +35,11 @@ RSpec.describe Gadget, type: :model do
|
||||||
gadget.update_attribute(:updated_at, Time.current + 1)
|
gadget.update_attribute(:updated_at, Time.current + 1)
|
||||||
}.to(change { gadget.versions.size }.by(1))
|
}.to(change { gadget.versions.size }.by(1))
|
||||||
expect(
|
expect(
|
||||||
|
if ::YAML.respond_to?(:unsafe_load)
|
||||||
|
YAML.unsafe_load(gadget.versions.last.object_changes).keys
|
||||||
|
else
|
||||||
YAML.load(gadget.versions.last.object_changes).keys
|
YAML.load(gadget.versions.last.object_changes).keys
|
||||||
|
end
|
||||||
).to eq(["updated_at"])
|
).to eq(["updated_at"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,10 +4,10 @@ require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe JoinedVersion, type: :model, versioning: true do
|
RSpec.describe JoinedVersion, type: :model, versioning: true do
|
||||||
let(:widget) { Widget.create!(name: FFaker::Name.name) }
|
let(:widget) { Widget.create!(name: FFaker::Name.name) }
|
||||||
let(:version) { JoinedVersion.first }
|
let(:version) { described_class.first }
|
||||||
|
|
||||||
describe "default_scope" do
|
describe "default_scope" do
|
||||||
it { expect(JoinedVersion.default_scopes).not_to be_empty }
|
it { expect(described_class.default_scopes).not_to be_empty }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "VersionConcern::ClassMethods" do
|
describe "VersionConcern::ClassMethods" do
|
||||||
|
@ -15,19 +15,19 @@ RSpec.describe JoinedVersion, type: :model, versioning: true do
|
||||||
|
|
||||||
describe "#subsequent" do
|
describe "#subsequent" do
|
||||||
it "does not raise error when there is a default_scope that joins" do
|
it "does not raise error when there is a default_scope that joins" do
|
||||||
JoinedVersion.subsequent(version).first
|
described_class.subsequent(version).first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#preceding" do
|
describe "#preceding" do
|
||||||
it "does not raise error when there is a default scope that joins" do
|
it "does not raise error when there is a default scope that joins" do
|
||||||
JoinedVersion.preceding(version).first
|
described_class.preceding(version).first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#between" do
|
describe "#between" do
|
||||||
it "does not raise error when there is a default scope that joins" do
|
it "does not raise error when there is a default scope that joins" do
|
||||||
JoinedVersion.between(Time.current, 1.minute.from_now).first
|
described_class.between(Time.current, 1.minute.from_now).first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ require "spec_helper"
|
||||||
# The `json_versions` table tests postgres' `json` data type. So, that
|
# The `json_versions` table tests postgres' `json` data type. So, that
|
||||||
# table is only created when testing against postgres.
|
# table is only created when testing against postgres.
|
||||||
if JsonVersion.table_exists?
|
if JsonVersion.table_exists?
|
||||||
RSpec.describe JsonVersion, type: :model do
|
RSpec.describe JsonVersion, type: :model, versioning: true do
|
||||||
it "includes the VersionConcern module" do
|
it "includes the VersionConcern module" do
|
||||||
expect(described_class).to include(PaperTrail::VersionConcern)
|
expect(described_class).to include(PaperTrail::VersionConcern)
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,11 @@ RSpec.describe NoObject, versioning: true do
|
||||||
|
|
||||||
# New feature: destroy populates object_changes
|
# New feature: destroy populates object_changes
|
||||||
# https://github.com/paper-trail-gem/paper_trail/pull/1123
|
# https://github.com/paper-trail-gem/paper_trail/pull/1123
|
||||||
h = YAML.load a["object_changes"]
|
h = if ::YAML.respond_to?(:unsafe_load)
|
||||||
|
YAML.unsafe_load a["object_changes"]
|
||||||
|
else
|
||||||
|
YAML.load a["object_changes"]
|
||||||
|
end
|
||||||
expect(h["id"]).to eq([n.id, nil])
|
expect(h["id"]).to eq([n.id, nil])
|
||||||
expect(h["letter"]).to eq([n.letter, nil])
|
expect(h["letter"]).to eq([n.letter, nil])
|
||||||
expect(h["created_at"][0]).to be_present
|
expect(h["created_at"][0]).to be_present
|
||||||
|
@ -38,7 +42,7 @@ RSpec.describe NoObject, versioning: true do
|
||||||
|
|
||||||
describe "reify" do
|
describe "reify" do
|
||||||
it "raises error" do
|
it "raises error" do
|
||||||
n = NoObject.create!(letter: "A")
|
n = described_class.create!(letter: "A")
|
||||||
v = n.versions.last
|
v = n.versions.last
|
||||||
expect { v.reify }.to(
|
expect { v.reify }.to(
|
||||||
raise_error(
|
raise_error(
|
||||||
|
@ -51,7 +55,7 @@ RSpec.describe NoObject, versioning: true do
|
||||||
|
|
||||||
describe "where_object" do
|
describe "where_object" do
|
||||||
it "raises error" do
|
it "raises error" do
|
||||||
n = NoObject.create!(letter: "A")
|
n = described_class.create!(letter: "A")
|
||||||
expect {
|
expect {
|
||||||
n.versions.where_object(foo: "bar")
|
n.versions.where_object(foo: "bar")
|
||||||
}.to(
|
}.to(
|
||||||
|
|
|
@ -9,21 +9,21 @@ require "spec_helper"
|
||||||
RSpec.describe Person, type: :model, versioning: true do
|
RSpec.describe Person, type: :model, versioning: true do
|
||||||
describe "#time_zone" do
|
describe "#time_zone" do
|
||||||
it "returns an ActiveSupport::TimeZone" do
|
it "returns an ActiveSupport::TimeZone" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
expect(person.time_zone.class).to(eq(ActiveSupport::TimeZone))
|
expect(person.time_zone.class).to(eq(ActiveSupport::TimeZone))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the model is saved" do
|
context "when the model is saved" do
|
||||||
it "version.object_changes should store long serialization of TimeZone object" do
|
it "version.object_changes should store long serialization of TimeZone object" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
len = person.versions.last.object_changes.length
|
len = person.versions.last.object_changes.length
|
||||||
expect((len < 105)).to(be_truthy)
|
expect((len < 105)).to(be_truthy)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.object_changes attribute should have stored the value from serializer" do
|
it "version.object_changes attribute should have stored the value from serializer" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
as_stored_in_version = HashWithIndifferentAccess[
|
as_stored_in_version = HashWithIndifferentAccess[
|
||||||
YAML.load(person.versions.last.object_changes)
|
YAML.load(person.versions.last.object_changes)
|
||||||
|
@ -34,14 +34,14 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.changeset should convert attribute to original, unserialized value" do
|
it "version.changeset should convert attribute to original, unserialized value" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
unserialized_value = Person::TimeZoneSerializer.load(person.time_zone)
|
unserialized_value = Person::TimeZoneSerializer.load(person.time_zone)
|
||||||
expect(person.versions.last.changeset[:time_zone].last).to(eq(unserialized_value))
|
expect(person.versions.last.changeset[:time_zone].last).to(eq(unserialized_value))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "record.changes (before save) returns the original, unserialized values" do
|
it "record.changes (before save) returns the original, unserialized values" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
changes_before_save = person.changes.dup
|
changes_before_save = person.changes.dup
|
||||||
person.save!
|
person.save!
|
||||||
expect(
|
expect(
|
||||||
|
@ -50,7 +50,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.changeset should be the same as record.changes was before the save" do
|
it "version.changeset should be the same as record.changes was before the save" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
changes_before_save = person.changes.dup
|
changes_before_save = person.changes.dup
|
||||||
person.save!
|
person.save!
|
||||||
actual = person.versions.last.changeset.delete_if { |k, _v| (k.to_sym == :id) }
|
actual = person.versions.last.changeset.delete_if { |k, _v| (k.to_sym == :id) }
|
||||||
|
@ -61,7 +61,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when that attribute is updated" do
|
context "when that attribute is updated" do
|
||||||
it "object should not store long serialization of TimeZone object" do
|
it "object should not store long serialization of TimeZone object" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
person.save!
|
person.save!
|
||||||
|
@ -70,7 +70,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "object_changes should not store long serialization of TimeZone object" do
|
it "object_changes should not store long serialization of TimeZone object" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
person.save!
|
person.save!
|
||||||
|
@ -79,7 +79,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.object attribute should have stored value from serializer" do
|
it "version.object attribute should have stored value from serializer" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
attribute_value_before_change = person.time_zone
|
attribute_value_before_change = person.time_zone
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
|
@ -93,7 +93,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.object_changes attribute should have stored value from serializer" do
|
it "version.object_changes attribute should have stored value from serializer" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
person.save!
|
person.save!
|
||||||
|
@ -106,7 +106,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.reify should convert attribute to original, unserialized value" do
|
it "version.reify should convert attribute to original, unserialized value" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
attribute_value_before_change = person.time_zone
|
attribute_value_before_change = person.time_zone
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
|
@ -116,7 +116,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.changeset should convert attribute to original, unserialized value" do
|
it "version.changeset should convert attribute to original, unserialized value" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
person.save!
|
person.save!
|
||||||
|
@ -125,7 +125,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "record.changes (before save) returns the original, unserialized values" do
|
it "record.changes (before save) returns the original, unserialized values" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
changes_before_save = person.changes.dup
|
changes_before_save = person.changes.dup
|
||||||
|
@ -136,7 +136,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "version.changeset should be the same as record.changes was before the save" do
|
it "version.changeset should be the same as record.changes was before the save" do
|
||||||
person = Person.new(time_zone: "Samoa")
|
person = described_class.new(time_zone: "Samoa")
|
||||||
person.save!
|
person.save!
|
||||||
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
|
||||||
changes_before_save = person.changes.dup
|
changes_before_save = person.changes.dup
|
||||||
|
@ -151,7 +151,7 @@ RSpec.describe Person, type: :model, versioning: true do
|
||||||
|
|
||||||
describe "#cars and bicycles" do
|
describe "#cars and bicycles" do
|
||||||
it "can be reified" do
|
it "can be reified" do
|
||||||
person = Person.create(name: "Frank")
|
person = described_class.create(name: "Frank")
|
||||||
car = Car.create(name: "BMW 325")
|
car = Car.create(name: "BMW 325")
|
||||||
bicycle = Bicycle.create(name: "BMX 1.0")
|
bicycle = Bicycle.create(name: "BMX 1.0")
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ require "rails/generators"
|
||||||
|
|
||||||
RSpec.describe Pet, type: :model, versioning: true do
|
RSpec.describe Pet, type: :model, versioning: true do
|
||||||
it "baseline test setup" do
|
it "baseline test setup" do
|
||||||
expect(Pet.new).to be_versioned
|
expect(described_class.new).to be_versioned
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can be reified" do
|
it "can be reified" do
|
||||||
|
@ -13,8 +13,8 @@ RSpec.describe Pet, type: :model, versioning: true do
|
||||||
dog = Dog.create(name: "Snoopy")
|
dog = Dog.create(name: "Snoopy")
|
||||||
cat = Cat.create(name: "Garfield")
|
cat = Cat.create(name: "Garfield")
|
||||||
|
|
||||||
person.pets << Pet.create(animal: dog)
|
person.pets << described_class.create(animal: dog)
|
||||||
person.pets << Pet.create(animal: cat)
|
person.pets << described_class.create(animal: cat)
|
||||||
person.update(name: "Steve")
|
person.update(name: "Steve")
|
||||||
|
|
||||||
dog.update(name: "Beethoven")
|
dog.update(name: "Beethoven")
|
||||||
|
|
|
@ -4,8 +4,8 @@ require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe Plant, type: :model, versioning: true do
|
RSpec.describe Plant, type: :model, versioning: true do
|
||||||
it "baseline test setup" do
|
it "baseline test setup" do
|
||||||
expect(Plant.new).to be_versioned
|
expect(described_class.new).to be_versioned
|
||||||
expect(Plant.inheritance_column).to eq("species")
|
expect(described_class.inheritance_column).to eq("species")
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#descends_from_active_record?" do
|
describe "#descends_from_active_record?" do
|
||||||
|
@ -15,14 +15,14 @@ RSpec.describe Plant, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "works with non standard STI column contents" do
|
it "works with non standard STI column contents" do
|
||||||
plant = Plant.create
|
plant = described_class.create
|
||||||
plant.destroy
|
plant.destroy
|
||||||
|
|
||||||
tomato = Tomato.create
|
tomato = Tomato.create
|
||||||
tomato.destroy
|
tomato.destroy
|
||||||
|
|
||||||
reified = plant.versions.last.reify
|
reified = plant.versions.last.reify
|
||||||
expect(reified.class).to eq(Plant)
|
expect(reified.class).to eq(described_class)
|
||||||
|
|
||||||
reified = tomato.versions.last.reify
|
reified = tomato.versions.last.reify
|
||||||
expect(reified.class).to eq(Tomato)
|
expect(reified.class).to eq(Tomato)
|
||||||
|
|
|
@ -5,7 +5,7 @@ require "spec_helper"
|
||||||
# The `Post` model uses a custom version class, `PostVersion`
|
# The `Post` model uses a custom version class, `PostVersion`
|
||||||
RSpec.describe Post, type: :model, versioning: true do
|
RSpec.describe Post, type: :model, versioning: true do
|
||||||
it "inserts records into the correct table, post_versions" do
|
it "inserts records into the correct table, post_versions" do
|
||||||
post = Post.create
|
post = described_class.create
|
||||||
expect(PostVersion.count).to(eq(1))
|
expect(PostVersion.count).to(eq(1))
|
||||||
post.update(content: "Some new content")
|
post.update(content: "Some new content")
|
||||||
expect(PostVersion.count).to(eq(2))
|
expect(PostVersion.count).to(eq(2))
|
||||||
|
@ -14,20 +14,20 @@ RSpec.describe Post, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with the first version" do
|
context "with the first version" do
|
||||||
it "have the correct index" do
|
it "have the correct index" do
|
||||||
post = Post.create
|
post = described_class.create
|
||||||
version = post.versions.first
|
version = post.versions.first
|
||||||
expect(version.index).to(eq(0))
|
expect(version.index).to(eq(0))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "have versions of the custom class" do
|
it "have versions of the custom class" do
|
||||||
post = Post.create
|
post = described_class.create
|
||||||
expect(post.versions.first.class.name).to(eq("PostVersion"))
|
expect(post.versions.first.class.name).to(eq("PostVersion"))
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#changeset" do
|
describe "#changeset" do
|
||||||
it "returns nil because the object_changes column doesn't exist" do
|
it "returns nil because the object_changes column doesn't exist" do
|
||||||
post = Post.create
|
post = described_class.create
|
||||||
post.update(content: "Some new content")
|
post.update(content: "Some new content")
|
||||||
expect(post.versions.last.changeset).to(be_nil)
|
expect(post.versions.last.changeset).to(be_nil)
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
|
||||||
let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }
|
let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }
|
||||||
|
|
||||||
it "does not create a version" do
|
it "does not create a version" do
|
||||||
skipper = Skipper.create!(another_timestamp: t1)
|
skipper = described_class.create!(another_timestamp: t1)
|
||||||
expect {
|
expect {
|
||||||
skipper.update!(another_timestamp: t2)
|
skipper.update!(another_timestamp: t2)
|
||||||
}.not_to(change { skipper.versions.length })
|
}.not_to(change { skipper.versions.length })
|
||||||
|
@ -25,28 +25,28 @@ RSpec.describe Skipper, type: :model, versioning: true do
|
||||||
|
|
||||||
if ActiveRecord.gem_version >= Gem::Version.new("6")
|
if ActiveRecord.gem_version >= Gem::Version.new("6")
|
||||||
it "does not create a version for skipped attributes" do
|
it "does not create a version for skipped attributes" do
|
||||||
skipper = Skipper.create!(another_timestamp: t1)
|
skipper = described_class.create!(another_timestamp: t1)
|
||||||
expect {
|
expect {
|
||||||
skipper.touch(:another_timestamp, time: t2)
|
skipper.touch(:another_timestamp, time: t2)
|
||||||
}.not_to(change { skipper.versions.length })
|
}.not_to(change { skipper.versions.length })
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not create a version for ignored attributes" do
|
it "does not create a version for ignored attributes" do
|
||||||
skipper = Skipper.create!(created_at: t1)
|
skipper = described_class.create!(created_at: t1)
|
||||||
expect {
|
expect {
|
||||||
skipper.touch(:created_at, time: t2)
|
skipper.touch(:created_at, time: t2)
|
||||||
}.not_to(change { skipper.versions.length })
|
}.not_to(change { skipper.versions.length })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
it "creates a version even for skipped attributes" do
|
it "creates a version even for skipped attributes" do
|
||||||
skipper = Skipper.create!(another_timestamp: t1)
|
skipper = described_class.create!(another_timestamp: t1)
|
||||||
expect {
|
expect {
|
||||||
skipper.touch(:another_timestamp, time: t2)
|
skipper.touch(:another_timestamp, time: t2)
|
||||||
}.to(change { skipper.versions.length })
|
}.to(change { skipper.versions.length })
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a version even for ignored attributes" do
|
it "creates a version even for ignored attributes" do
|
||||||
skipper = Skipper.create!(created_at: t1)
|
skipper = described_class.create!(created_at: t1)
|
||||||
expect {
|
expect {
|
||||||
skipper.touch(:created_at, time: t2)
|
skipper.touch(:created_at, time: t2)
|
||||||
}.to(change { skipper.versions.length })
|
}.to(change { skipper.versions.length })
|
||||||
|
@ -54,7 +54,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a version for non-skipped timestamps" do
|
it "creates a version for non-skipped timestamps" do
|
||||||
skipper = Skipper.create!
|
skipper = described_class.create!
|
||||||
expect {
|
expect {
|
||||||
skipper.touch
|
skipper.touch
|
||||||
}.to(change { skipper.versions.length })
|
}.to(change { skipper.versions.length })
|
||||||
|
@ -67,7 +67,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
|
||||||
|
|
||||||
context "without preserve (default)" do
|
context "without preserve (default)" do
|
||||||
it "has no timestamp" do
|
it "has no timestamp" do
|
||||||
skipper = Skipper.create!(another_timestamp: t1)
|
skipper = described_class.create!(another_timestamp: t1)
|
||||||
skipper.update!(another_timestamp: t2, name: "Foobar")
|
skipper.update!(another_timestamp: t2, name: "Foobar")
|
||||||
skipper = skipper.versions.last.reify
|
skipper = skipper.versions.last.reify
|
||||||
expect(skipper.another_timestamp).to be(nil)
|
expect(skipper.another_timestamp).to be(nil)
|
||||||
|
@ -76,7 +76,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with preserve" do
|
context "with preserve" do
|
||||||
it "preserves its timestamp" do
|
it "preserves its timestamp" do
|
||||||
skipper = Skipper.create!(another_timestamp: t1)
|
skipper = described_class.create!(another_timestamp: t1)
|
||||||
skipper.update!(another_timestamp: t2, name: "Foobar")
|
skipper.update!(another_timestamp: t2, name: "Foobar")
|
||||||
skipper = skipper.versions.last.reify(unversioned_attributes: :preserve)
|
skipper = skipper.versions.last.reify(unversioned_attributes: :preserve)
|
||||||
expect(skipper.another_timestamp).to eq(t2)
|
expect(skipper.another_timestamp).to eq(t2)
|
||||||
|
|
|
@ -4,10 +4,10 @@ require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe Thing, type: :model do
|
RSpec.describe Thing, type: :model do
|
||||||
describe "#versions", versioning: true do
|
describe "#versions", versioning: true do
|
||||||
let(:thing) { Thing.create! }
|
let(:thing) { described_class.create! }
|
||||||
|
|
||||||
it "applies the scope option" do
|
it "applies the scope option" do
|
||||||
expect(Thing.reflect_on_association(:versions).scope).to be_a Proc
|
expect(described_class.reflect_on_association(:versions).scope).to be_a Proc
|
||||||
expect(thing.versions.to_sql).to end_with "ORDER BY id desc"
|
expect(thing.versions.to_sql).to end_with "ORDER BY id desc"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,14 @@ RSpec.describe Translation, type: :model, versioning: true do
|
||||||
expect(PaperTrail::Version.count).to(eq(0))
|
expect(PaperTrail::Version.count).to(eq(0))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when after touch" do
|
||||||
|
it "not change the number of versions" do
|
||||||
|
translation = described_class.create!(headline: "Headline")
|
||||||
|
translation.touch
|
||||||
|
expect(PaperTrail::Version.count).to(eq(0))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with US translations" do
|
context "with US translations" do
|
||||||
|
@ -31,7 +39,7 @@ RSpec.describe Translation, type: :model, versioning: true do
|
||||||
it "creation does not change the number of versions" do
|
it "creation does not change the number of versions" do
|
||||||
translation = described_class.new(headline: "Headline")
|
translation = described_class.new(headline: "Headline")
|
||||||
translation.language_code = "US"
|
translation.language_code = "US"
|
||||||
translation.type = "DRAFT"
|
translation.draft_status = "DRAFT"
|
||||||
translation.save!
|
translation.save!
|
||||||
expect(PaperTrail::Version.count).to(eq(0))
|
expect(PaperTrail::Version.count).to(eq(0))
|
||||||
end
|
end
|
||||||
|
@ -39,11 +47,20 @@ RSpec.describe Translation, type: :model, versioning: true do
|
||||||
it "update does not change the number of versions" do
|
it "update does not change the number of versions" do
|
||||||
translation = described_class.new(headline: "Headline")
|
translation = described_class.new(headline: "Headline")
|
||||||
translation.language_code = "US"
|
translation.language_code = "US"
|
||||||
translation.type = "DRAFT"
|
translation.draft_status = "DRAFT"
|
||||||
translation.save!
|
translation.save!
|
||||||
translation.update(content: "Content")
|
translation.update(content: "Content")
|
||||||
expect(PaperTrail::Version.count).to(eq(0))
|
expect(PaperTrail::Version.count).to(eq(0))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "touch does not change the number of versions" do
|
||||||
|
translation = described_class.new(headline: "Headline")
|
||||||
|
translation.language_code = "US"
|
||||||
|
translation.draft_status = "DRAFT"
|
||||||
|
translation.save!
|
||||||
|
translation.touch
|
||||||
|
expect(PaperTrail::Version.count).to(eq(0))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with non-drafts" do
|
context "with non-drafts" do
|
||||||
|
@ -52,14 +69,21 @@ RSpec.describe Translation, type: :model, versioning: true do
|
||||||
expect(PaperTrail::Version.count).to(eq(1))
|
expect(PaperTrail::Version.count).to(eq(1))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "update does not change the number of versions" do
|
it "update changes the number of versions" do
|
||||||
translation = described_class.create!(headline: "Headline", language_code: "US")
|
translation = described_class.create!(headline: "Headline", language_code: "US")
|
||||||
translation.update(content: "Content")
|
translation.update(content: "Content")
|
||||||
expect(PaperTrail::Version.count).to(eq(2))
|
expect(PaperTrail::Version.count).to(eq(2))
|
||||||
expect(translation.versions.size).to(eq(2))
|
expect(translation.versions.size).to(eq(2))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "destroy does not change the number of versions" do
|
it "touch changes the number of versions" do
|
||||||
|
translation = described_class.create!(headline: "Headline", language_code: "US")
|
||||||
|
translation.touch
|
||||||
|
expect(PaperTrail::Version.count).to(eq(2))
|
||||||
|
expect(translation.versions.size).to(eq(2))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "destroy changes the number of versions" do
|
||||||
translation = described_class.new(headline: "Headline")
|
translation = described_class.new(headline: "Headline")
|
||||||
translation.language_code = "US"
|
translation.language_code = "US"
|
||||||
translation.save!
|
translation.save!
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "spec_helper"
|
||||||
|
require "support/performance_helpers"
|
||||||
|
|
||||||
|
if ENV["DB"] == "postgres" && JsonbVersion.table_exists?
|
||||||
|
::RSpec.describe Vegetable do
|
||||||
|
describe "queries of versions", versioning: true do
|
||||||
|
let!(:vegetable) { described_class.create(name: "Veggie", mass: 1, color: "green") }
|
||||||
|
|
||||||
|
before do
|
||||||
|
vegetable.update(name: "Fidget")
|
||||||
|
vegetable.update(name: "Digit")
|
||||||
|
described_class.create(name: "Cucumber")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "return the vegetable whose name has changed" do
|
||||||
|
result = JsonbVersion.where_attribute_changes(:name).map(&:item)
|
||||||
|
expect(result).to include(vegetable)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the vegetable whose name was Fidget" do
|
||||||
|
result = JsonbVersion.where_object_changes_from({ name: "Fidget" }).map(&:item)
|
||||||
|
expect(result).to include(vegetable)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the vegetable whose name became Digit" do
|
||||||
|
result = JsonbVersion.where_object_changes_to({ name: "Digit" }).map(&:item)
|
||||||
|
expect(result).to include(vegetable)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the vegetable where the object was named Fidget before it changed" do
|
||||||
|
result = JsonbVersion.where_object({ name: "Fidget" }).map(&:item)
|
||||||
|
expect(result).to include(vegetable)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the vegetable that changed to Fidget" do
|
||||||
|
result = JsonbVersion.where_object_changes({ name: "Fidget" }).map(&:item)
|
||||||
|
expect(result).to include(vegetable)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
|
require "support/shared_examples/queries"
|
||||||
|
|
||||||
module PaperTrail
|
module PaperTrail
|
||||||
::RSpec.describe Version, type: :model do
|
::RSpec.describe Version, type: :model do
|
||||||
|
@ -60,7 +61,7 @@ module PaperTrail
|
||||||
describe "#paper_trail_originator" do
|
describe "#paper_trail_originator" do
|
||||||
context "with no previous versions" do
|
context "with no previous versions" do
|
||||||
it "returns nil" do
|
it "returns nil" do
|
||||||
expect(PaperTrail::Version.new.paper_trail_originator).to be_nil
|
expect(described_class.new.paper_trail_originator).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ module PaperTrail
|
||||||
describe "#previous" do
|
describe "#previous" do
|
||||||
context "with no previous versions" do
|
context "with no previous versions" do
|
||||||
it "returns nil" do
|
it "returns nil" do
|
||||||
expect(PaperTrail::Version.new.previous).to be_nil
|
expect(described_class.new.previous).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -88,7 +89,7 @@ module PaperTrail
|
||||||
widget = Widget.create!(name: FFaker::Name.name)
|
widget = Widget.create!(name: FFaker::Name.name)
|
||||||
widget.versions.first.update!(whodunnit: name)
|
widget.versions.first.update!(whodunnit: name)
|
||||||
widget.update!(name: FFaker::Name.first_name)
|
widget.update!(name: FFaker::Name.first_name)
|
||||||
expect(widget.versions.last.previous).to be_instance_of(PaperTrail::Version)
|
expect(widget.versions.last.previous).to be_instance_of(described_class)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -96,443 +97,40 @@ module PaperTrail
|
||||||
describe "#terminator" do
|
describe "#terminator" do
|
||||||
it "is an alias for the `whodunnit` attribute" do
|
it "is an alias for the `whodunnit` attribute" do
|
||||||
attributes = { whodunnit: FFaker::Name.first_name }
|
attributes = { whodunnit: FFaker::Name.first_name }
|
||||||
version = PaperTrail::Version.new(attributes)
|
version = described_class.new(attributes)
|
||||||
expect(version.terminator).to eq(attributes[:whodunnit])
|
expect(version.terminator).to eq(attributes[:whodunnit])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#version_author" do
|
describe "#version_author" do
|
||||||
it "is an alias for the `terminator` method" do
|
it "is an alias for the `terminator` method" do
|
||||||
version = PaperTrail::Version.new
|
version = described_class.new
|
||||||
expect(version.method(:version_author)).to eq(version.method(:terminator))
|
expect(version.method(:version_author)).to eq(version.method(:terminator))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when changing the data type of database columns on the fly" do
|
context "with text columns", versioning: true do
|
||||||
# TODO: Changing the data type of these database columns in the middle
|
include_examples "queries", :text, ::Widget, :an_integer
|
||||||
# of the test suite adds a fair amount of complexity. Is there a better
|
end
|
||||||
# way? We already have a `json_versions` table in our tests, maybe we
|
|
||||||
# could use that and add a `jsonb_versions` table?
|
|
||||||
column_overrides = [false]
|
|
||||||
if ENV["DB"] == "postgres"
|
if ENV["DB"] == "postgres"
|
||||||
column_overrides += %w[json jsonb]
|
context "with json columns", versioning: true do
|
||||||
end
|
include_examples(
|
||||||
|
"queries",
|
||||||
column_overrides.shuffle.each do |column_datatype_override|
|
:json,
|
||||||
context "with a #{column_datatype_override || 'text'} column" do
|
::Fruit, # uses JsonVersion
|
||||||
let(:widget) { Widget.new }
|
:mass
|
||||||
let(:name) { FFaker::Name.first_name }
|
|
||||||
let(:int) { column_datatype_override ? 1 : rand(2..6) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
if column_datatype_override
|
|
||||||
ActiveRecord::Base.connection.execute("SAVEPOINT pgtest;")
|
|
||||||
%w[object object_changes].each do |column|
|
|
||||||
ActiveRecord::Base.connection.execute(
|
|
||||||
"ALTER TABLE versions DROP COLUMN #{column};"
|
|
||||||
)
|
|
||||||
ActiveRecord::Base.connection.execute(
|
|
||||||
"ALTER TABLE versions ADD COLUMN #{column} #{column_datatype_override};"
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
PaperTrail::Version.reset_column_information
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
context "with jsonb columns", versioning: true do
|
||||||
PaperTrail.serializer = PaperTrail::Serializers::YAML
|
include_examples(
|
||||||
|
"queries",
|
||||||
if column_datatype_override
|
:jsonb,
|
||||||
ActiveRecord::Base.connection.execute("ROLLBACK TO SAVEPOINT pgtest;")
|
::Vegetable, # uses JsonbVersion
|
||||||
PaperTrail::Version.reset_column_information
|
:mass
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#where_attribute_changes", versioning: true do
|
|
||||||
it "requires its argument to be a string or a symbol" do
|
|
||||||
expect {
|
|
||||||
PaperTrail::Version.where_attribute_changes({})
|
|
||||||
}.to raise_error(ArgumentError)
|
|
||||||
expect {
|
|
||||||
PaperTrail::Version.where_attribute_changes([])
|
|
||||||
}.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_attribute_changes method" do
|
|
||||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
|
||||||
bicycle = Bicycle.create!(name: "abc")
|
|
||||||
bicycle.update!(name: "xyz")
|
|
||||||
|
|
||||||
allow(adapter).to(
|
|
||||||
receive(:where_attribute_changes).with(Version, :name)
|
|
||||||
).and_return([bicycle.versions[0], bicycle.versions[1]])
|
|
||||||
|
|
||||||
PaperTrail.config.object_changes_adapter = adapter
|
|
||||||
expect(
|
|
||||||
bicycle.versions.where_attribute_changes(:name)
|
|
||||||
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
|
||||||
expect(adapter).to have_received(:where_attribute_changes)
|
|
||||||
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_attribute_changes(:name)
|
|
||||||
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
|
||||||
else
|
|
||||||
expect {
|
|
||||||
bicycle.versions.where_attribute_changes(:name)
|
|
||||||
}.to raise_error(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_attribute_changes expected json or jsonb column, got text"
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only test json and jsonb columns. where_attribute_changes does
|
|
||||||
# not support text columns.
|
|
||||||
if column_datatype_override
|
|
||||||
it "locates versions according to their object_changes contents" do
|
|
||||||
widget.update!(name: "foobar", an_integer: 100)
|
|
||||||
widget.update!(an_integer: 17)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
widget.versions.where_attribute_changes(:name)
|
|
||||||
).to eq([widget.versions[0]])
|
|
||||||
expect(
|
|
||||||
widget.versions.where_attribute_changes("an_integer")
|
|
||||||
).to eq([widget.versions[0], widget.versions[1]])
|
|
||||||
expect(
|
|
||||||
widget.versions.where_attribute_changes(:a_float)
|
|
||||||
).to eq([])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
it "raises error" do
|
|
||||||
expect {
|
|
||||||
widget.versions.where_attribute_changes(:name).to_a
|
|
||||||
}.to raise_error(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_attribute_changes expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#where_object", versioning: true do
|
|
||||||
it "requires its argument to be a Hash" do
|
|
||||||
widget.update!(name: name, an_integer: int)
|
|
||||||
widget.update!(name: "foobar", an_integer: 100)
|
|
||||||
widget.update!(name: FFaker::Name.last_name, an_integer: 15)
|
|
||||||
expect {
|
|
||||||
PaperTrail::Version.where_object(:foo)
|
|
||||||
}.to raise_error(ArgumentError)
|
|
||||||
expect {
|
|
||||||
PaperTrail::Version.where_object([])
|
|
||||||
}.to raise_error(ArgumentError)
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with YAML serializer" do
|
|
||||||
it "locates versions according to their `object` contents" do
|
|
||||||
expect(PaperTrail.serializer).to be PaperTrail::Serializers::YAML
|
|
||||||
widget.update!(name: name, an_integer: int)
|
|
||||||
widget.update!(name: "foobar", an_integer: 100)
|
|
||||||
widget.update!(name: FFaker::Name.last_name, an_integer: 15)
|
|
||||||
expect(
|
|
||||||
PaperTrail::Version.where_object(an_integer: int)
|
|
||||||
).to eq([widget.versions[1]])
|
|
||||||
expect(
|
|
||||||
PaperTrail::Version.where_object(name: name)
|
|
||||||
).to eq([widget.versions[1]])
|
|
||||||
expect(
|
|
||||||
PaperTrail::Version.where_object(an_integer: 100)
|
|
||||||
).to eq([widget.versions[2]])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with JSON serializer" do
|
|
||||||
it "locates versions according to their `object` contents" do
|
|
||||||
PaperTrail.serializer = PaperTrail::Serializers::JSON
|
|
||||||
expect(PaperTrail.serializer).to be PaperTrail::Serializers::JSON
|
|
||||||
widget.update!(name: name, an_integer: int)
|
|
||||||
widget.update!(name: "foobar", an_integer: 100)
|
|
||||||
widget.update!(name: FFaker::Name.last_name, an_integer: 15)
|
|
||||||
expect(
|
|
||||||
PaperTrail::Version.where_object(an_integer: int)
|
|
||||||
).to eq([widget.versions[1]])
|
|
||||||
expect(
|
|
||||||
PaperTrail::Version.where_object(name: name)
|
|
||||||
).to eq([widget.versions[1]])
|
|
||||||
expect(
|
|
||||||
PaperTrail::Version.where_object(an_integer: 100)
|
|
||||||
).to eq([widget.versions[2]])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#where_object_changes", versioning: true do
|
|
||||||
it "requires its argument to be a Hash" do
|
|
||||||
expect {
|
|
||||||
PaperTrail::Version.where_object_changes(:foo)
|
|
||||||
}.to raise_error(ArgumentError)
|
|
||||||
expect {
|
|
||||||
PaperTrail::Version.where_object_changes([])
|
|
||||||
}.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 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
|
|
||||||
|
|
||||||
it "defaults to the original behavior" do
|
|
||||||
adapter = Class.new.new
|
|
||||||
PaperTrail.config.object_changes_adapter = adapter
|
|
||||||
bicycle = Bicycle.create!(name: "abc")
|
|
||||||
if column_datatype_override
|
|
||||||
expect(
|
|
||||||
bicycle.versions.where_object_changes(name: "abc")
|
|
||||||
).to match_array(bicycle.versions[0..1])
|
|
||||||
else
|
|
||||||
expect {
|
|
||||||
bicycle.versions.where_object_changes(name: "abc")
|
|
||||||
}.to raise_error(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_object_changes expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only test json and jsonb columns. where_object_changes no longer
|
|
||||||
# supports 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(name: name)
|
|
||||||
).to eq(widget.versions[0..1])
|
|
||||||
expect(
|
|
||||||
widget.versions.where_object_changes(an_integer: 100)
|
|
||||||
).to eq(widget.versions[1..2])
|
|
||||||
expect(
|
|
||||||
widget.versions.where_object_changes(an_integer: int)
|
|
||||||
).to eq([widget.versions.last])
|
|
||||||
expect(
|
|
||||||
widget.versions.where_object_changes(an_integer: 100, name: "foobar")
|
|
||||||
).to eq(widget.versions[1..2])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
it "raises error" do
|
|
||||||
expect {
|
|
||||||
widget.versions.where_object_changes(name: "foo").to_a
|
|
||||||
}.to raise_error(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_object_changes expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
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 {
|
|
||||||
bicycle.versions.where_object_changes_from(name: "abc")
|
|
||||||
}.to raise_error(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_object_changes_from expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_object_changes_from expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
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 {
|
|
||||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
|
||||||
}.to raise_error(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_object_changes_to expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
UnsupportedColumnType,
|
|
||||||
"where_object_changes_to expected json or jsonb column, got text"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ require "support/performance_helpers"
|
||||||
RSpec.describe Widget, type: :model, versioning: true do
|
RSpec.describe Widget, type: :model, versioning: true do
|
||||||
describe "#changeset" do
|
describe "#changeset" do
|
||||||
it "has expected values" do
|
it "has expected values" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
changeset = widget.versions.last.changeset
|
changeset = widget.versions.last.changeset
|
||||||
expect(changeset["name"]).to eq([nil, "Henry"])
|
expect(changeset["name"]).to eq([nil, "Henry"])
|
||||||
expect(changeset["id"]).to eq([nil, widget.id])
|
expect(changeset["id"]).to eq([nil, widget.id])
|
||||||
|
@ -24,7 +24,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "calls the adapter's load_changeset method" do
|
it "calls the adapter's load_changeset method" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||||
PaperTrail.config.object_changes_adapter = adapter
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
allow(adapter).to(
|
allow(adapter).to(
|
||||||
|
@ -39,7 +39,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
it "defaults to the original behavior" do
|
it "defaults to the original behavior" do
|
||||||
adapter = Class.new.new
|
adapter = Class.new.new
|
||||||
PaperTrail.config.object_changes_adapter = adapter
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
changeset = widget.versions.last.changeset
|
changeset = widget.versions.last.changeset
|
||||||
expect(changeset[:name]).to eq([nil, "Henry"])
|
expect(changeset[:name]).to eq([nil, "Henry"])
|
||||||
end
|
end
|
||||||
|
@ -48,44 +48,44 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with a new record" do
|
context "with a new record" do
|
||||||
it "not have any previous versions" do
|
it "not have any previous versions" do
|
||||||
expect(Widget.new.versions).to(eq([]))
|
expect(described_class.new.versions).to(eq([]))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "be live" do
|
it "be live" do
|
||||||
expect(Widget.new.paper_trail.live?).to(eq(true))
|
expect(described_class.new.paper_trail.live?).to(eq(true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a persisted record" do
|
context "with a persisted record" do
|
||||||
it "have one previous version" do
|
it "have one previous version" do
|
||||||
widget = Widget.create(name: "Henry", created_at: (Time.current - 1.day))
|
widget = described_class.create(name: "Henry", created_at: (Time.current - 1.day))
|
||||||
expect(widget.versions.length).to(eq(1))
|
expect(widget.versions.length).to(eq(1))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "be nil in its previous version" do
|
it "be nil in its previous version" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
expect(widget.versions.first.object).to(be_nil)
|
expect(widget.versions.first.object).to(be_nil)
|
||||||
expect(widget.versions.first.reify).to(be_nil)
|
expect(widget.versions.first.reify).to(be_nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "record the correct event" do
|
it "record the correct event" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
expect(widget.versions.first.event).to(match(/create/i))
|
expect(widget.versions.first.event).to(match(/create/i))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "be live" do
|
it "be live" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
expect(widget.paper_trail.live?).to(eq(true))
|
expect(widget.paper_trail.live?).to(eq(true))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "use the widget `updated_at` as the version's `created_at`" do
|
it "use the widget `updated_at` as the version's `created_at`" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
expect(widget.versions.first.created_at.to_i).to(eq(widget.updated_at.to_i))
|
expect(widget.versions.first.created_at.to_i).to(eq(widget.updated_at.to_i))
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when updated without any changes" do
|
context "when updated without any changes" do
|
||||||
it "to have two previous versions" do
|
it "to have two previous versions" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.touch
|
widget.touch
|
||||||
expect(widget.versions.length).to eq(2)
|
expect(widget.versions.length).to eq(2)
|
||||||
end
|
end
|
||||||
|
@ -93,13 +93,13 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when updated with changes" do
|
context "when updated with changes" do
|
||||||
it "have three previous versions" do
|
it "have three previous versions" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
expect(widget.versions.length).to(eq(2))
|
expect(widget.versions.length).to(eq(2))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "be available in its previous version" do
|
it "be available in its previous version" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
expect(widget.name).to(eq("Harry"))
|
expect(widget.name).to(eq("Harry"))
|
||||||
expect(widget.versions.last.object).not_to(be_nil)
|
expect(widget.versions.last.object).not_to(be_nil)
|
||||||
|
@ -109,19 +109,19 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "have the same ID in its previous version" do
|
it "have the same ID in its previous version" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
expect(widget.versions.last.reify.id).to(eq(widget.id))
|
expect(widget.versions.last.reify.id).to(eq(widget.id))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "record the correct event" do
|
it "record the correct event" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
expect(widget.versions.last.event).to(match(/update/i))
|
expect(widget.versions.last.event).to(match(/update/i))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "have versions that are not live" do
|
it "have versions that are not live" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.versions.map(&:reify).compact.each do |v|
|
widget.versions.map(&:reify).compact.each do |v|
|
||||||
expect(v.paper_trail).not_to be_live
|
expect(v.paper_trail).not_to be_live
|
||||||
|
@ -129,7 +129,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "have stored changes" do
|
it "have stored changes" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
last_obj_changes = widget.versions.last.object_changes
|
last_obj_changes = widget.versions.last.object_changes
|
||||||
actual = PaperTrail.serializer.load(last_obj_changes).reject do |k, _v|
|
actual = PaperTrail.serializer.load(last_obj_changes).reject do |k, _v|
|
||||||
|
@ -141,7 +141,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "return changes with indifferent access" do
|
it "return changes with indifferent access" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
expect(widget.versions.last.changeset[:name]).to(eq(%w[Henry Harry]))
|
expect(widget.versions.last.changeset[:name]).to(eq(%w[Henry Harry]))
|
||||||
expect(widget.versions.last.changeset["name"]).to(eq(%w[Henry Harry]))
|
expect(widget.versions.last.changeset["name"]).to(eq(%w[Henry Harry]))
|
||||||
|
@ -150,7 +150,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when updated, and has one associated object" do
|
context "when updated, and has one associated object" do
|
||||||
it "not copy the has_one association by default when reifying" do
|
it "not copy the has_one association by default when reifying" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
wotsit = widget.create_wotsit name: "John"
|
wotsit = widget.create_wotsit name: "John"
|
||||||
reified_widget = widget.versions.last.reify
|
reified_widget = widget.versions.last.reify
|
||||||
|
@ -161,7 +161,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when updated, and has many associated objects" do
|
context "when updated, and has many associated objects" do
|
||||||
it "copy the has_many associations when reifying" do
|
it "copy the has_many associations when reifying" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.fluxors.create(name: "f-zero")
|
widget.fluxors.create(name: "f-zero")
|
||||||
widget.fluxors.create(name: "f-one")
|
widget.fluxors.create(name: "f-one")
|
||||||
|
@ -175,7 +175,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when updated, and has many associated polymorphic objects" do
|
context "when updated, and has many associated polymorphic objects" do
|
||||||
it "copy the has_many associations when reifying" do
|
it "copy the has_many associations when reifying" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.whatchamajiggers.create(name: "f-zero")
|
widget.whatchamajiggers.create(name: "f-zero")
|
||||||
widget.whatchamajiggers.create(name: "f-zero")
|
widget.whatchamajiggers.create(name: "f-zero")
|
||||||
|
@ -189,7 +189,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when updated, polymorphic objects by themselves" do
|
context "when updated, polymorphic objects by themselves" do
|
||||||
it "not fail with a nil pointer on the polymorphic association" do
|
it "not fail with a nil pointer on the polymorphic association" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget = Whatchamajigger.new(name: "f-zero")
|
widget = Whatchamajigger.new(name: "f-zero")
|
||||||
widget.save!
|
widget.save!
|
||||||
|
@ -198,21 +198,21 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when updated, and then destroyed" do
|
context "when updated, and then destroyed" do
|
||||||
it "record the correct event" do
|
it "record the correct event" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.destroy
|
widget.destroy
|
||||||
expect(PaperTrail::Version.last.event).to(match(/destroy/i))
|
expect(PaperTrail::Version.last.event).to(match(/destroy/i))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "have three previous versions" do
|
it "have three previous versions" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.destroy
|
widget.destroy
|
||||||
expect(PaperTrail::Version.with_item_keys("Widget", widget.id).length).to(eq(3))
|
expect(PaperTrail::Version.with_item_keys("Widget", widget.id).length).to(eq(3))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the expected attributes for the reified widget" do
|
it "returns the expected attributes for the reified widget" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.destroy
|
widget.destroy
|
||||||
reified_widget = PaperTrail::Version.last.reify
|
reified_widget = PaperTrail::Version.last.reify
|
||||||
|
@ -239,7 +239,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "be re-creatable from its previous version" do
|
it "be re-creatable from its previous version" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.destroy
|
widget.destroy
|
||||||
reified_widget = PaperTrail::Version.last.reify
|
reified_widget = PaperTrail::Version.last.reify
|
||||||
|
@ -247,7 +247,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "restore its associations on its previous version" do
|
it "restore its associations on its previous version" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.fluxors.create(name: "flux")
|
widget.fluxors.create(name: "flux")
|
||||||
widget.destroy
|
widget.destroy
|
||||||
|
@ -257,9 +257,11 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "have nil item for last version" do
|
it "have nil item for last version" do
|
||||||
widget = Widget.create(name: "Henry")
|
widget = described_class.create(name: "Henry")
|
||||||
widget.update(name: "Harry")
|
widget.update(name: "Harry")
|
||||||
widget.destroy
|
widget.destroy
|
||||||
|
expect(widget.versions.first.item.object_id).not_to eq(widget.object_id)
|
||||||
|
expect(widget.versions.last.item.object_id).not_to eq(widget.object_id)
|
||||||
expect(widget.versions.last.item).to be_nil
|
expect(widget.versions.last.item).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -270,7 +272,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
let!(:t0) { Time.current }
|
let!(:t0) { Time.current }
|
||||||
let(:previous_widget) { widget.versions.last.reify }
|
let(:previous_widget) { widget.versions.last.reify }
|
||||||
let(:widget) {
|
let(:widget) {
|
||||||
Widget.create(
|
described_class.create(
|
||||||
name: "Warble",
|
name: "Warble",
|
||||||
a_text: "The quick brown fox",
|
a_text: "The quick brown fox",
|
||||||
an_integer: 42,
|
an_integer: 42,
|
||||||
|
@ -338,7 +340,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
let(:last_version) { widget.versions.last }
|
let(:last_version) { widget.versions.last }
|
||||||
|
|
||||||
it "reify previous version" do
|
it "reify previous version" do
|
||||||
assert_kind_of(Widget, last_version.reify)
|
assert_kind_of(described_class, last_version.reify)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "restore all forward-compatible attributes" do
|
it "restore all forward-compatible attributes" do
|
||||||
|
@ -362,7 +364,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
after { PaperTrail.enabled = true }
|
after { PaperTrail.enabled = true }
|
||||||
|
|
||||||
it "not add to its trail" do
|
it "not add to its trail" do
|
||||||
widget = Widget.create(name: "Zaphod")
|
widget = described_class.create(name: "Zaphod")
|
||||||
PaperTrail.enabled = false
|
PaperTrail.enabled = false
|
||||||
count = widget.versions.length
|
count = widget.versions.length
|
||||||
widget.update(name: "Beeblebrox")
|
widget.update(name: "Beeblebrox")
|
||||||
|
@ -372,23 +374,23 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with its paper trail turned off, when updated" do
|
context "with its paper trail turned off, when updated" do
|
||||||
after do
|
after do
|
||||||
PaperTrail.request.enable_model(Widget)
|
PaperTrail.request.enable_model(described_class)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "not add to its trail" do
|
it "not add to its trail" do
|
||||||
widget = Widget.create(name: "Zaphod")
|
widget = described_class.create(name: "Zaphod")
|
||||||
PaperTrail.request.disable_model(Widget)
|
PaperTrail.request.disable_model(described_class)
|
||||||
count = widget.versions.length
|
count = widget.versions.length
|
||||||
widget.update(name: "Beeblebrox")
|
widget.update(name: "Beeblebrox")
|
||||||
expect(widget.versions.length).to(eq(count))
|
expect(widget.versions.length).to(eq(count))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "add to its trail" do
|
it "add to its trail" do
|
||||||
widget = Widget.create(name: "Zaphod")
|
widget = described_class.create(name: "Zaphod")
|
||||||
PaperTrail.request.disable_model(Widget)
|
PaperTrail.request.disable_model(described_class)
|
||||||
count = widget.versions.length
|
count = widget.versions.length
|
||||||
widget.update(name: "Beeblebrox")
|
widget.update(name: "Beeblebrox")
|
||||||
PaperTrail.request.enable_model(Widget)
|
PaperTrail.request.enable_model(described_class)
|
||||||
widget.update(name: "Ford")
|
widget.update(name: "Ford")
|
||||||
expect(widget.versions.length).to(eq((count + 1)))
|
expect(widget.versions.length).to(eq((count + 1)))
|
||||||
end
|
end
|
||||||
|
@ -398,7 +400,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
context "with somebody making changes" do
|
context "with somebody making changes" do
|
||||||
context "when a record is created" do
|
context "when a record is created" do
|
||||||
it "tracks who made the change" do
|
it "tracks who made the change" do
|
||||||
widget = Widget.new(name: "Fidget")
|
widget = described_class.new(name: "Fidget")
|
||||||
PaperTrail.request.whodunnit = "Alice"
|
PaperTrail.request.whodunnit = "Alice"
|
||||||
widget.save
|
widget.save
|
||||||
version = widget.versions.last
|
version = widget.versions.last
|
||||||
|
@ -411,7 +413,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when created, then updated" do
|
context "when created, then updated" do
|
||||||
it "tracks who made the change" do
|
it "tracks who made the change" do
|
||||||
widget = Widget.new(name: "Fidget")
|
widget = described_class.new(name: "Fidget")
|
||||||
PaperTrail.request.whodunnit = "Alice"
|
PaperTrail.request.whodunnit = "Alice"
|
||||||
widget.save
|
widget.save
|
||||||
PaperTrail.request.whodunnit = "Bob"
|
PaperTrail.request.whodunnit = "Bob"
|
||||||
|
@ -426,7 +428,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when created, updated, and destroyed" do
|
context "when created, updated, and destroyed" do
|
||||||
it "tracks who made the change" do
|
it "tracks who made the change" do
|
||||||
widget = Widget.new(name: "Fidget")
|
widget = described_class.new(name: "Fidget")
|
||||||
PaperTrail.request.whodunnit = "Alice"
|
PaperTrail.request.whodunnit = "Alice"
|
||||||
widget.save
|
widget.save
|
||||||
PaperTrail.request.whodunnit = "Bob"
|
PaperTrail.request.whodunnit = "Bob"
|
||||||
|
@ -444,7 +446,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with an item with versions" do
|
context "with an item with versions" do
|
||||||
context "when the versions were created over time" do
|
context "when the versions were created over time" do
|
||||||
let(:widget) { Widget.create(name: "Widget") }
|
let(:widget) { described_class.create(name: "Widget") }
|
||||||
let(:t0) { 2.days.ago }
|
let(:t0) { 2.days.ago }
|
||||||
let(:t1) { 1.day.ago }
|
let(:t1) { 1.day.ago }
|
||||||
let(:t2) { 1.hour.ago }
|
let(:t2) { 1.hour.ago }
|
||||||
|
@ -501,7 +503,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
describe ".versions_between" do
|
describe ".versions_between" do
|
||||||
it "return versions in the time period" do
|
it "return versions in the time period" do
|
||||||
widget = Widget.create(name: "Widget")
|
widget = described_class.create(name: "Widget")
|
||||||
widget.update(name: "Fidget")
|
widget.update(name: "Fidget")
|
||||||
widget.update(name: "Digit")
|
widget.update(name: "Digit")
|
||||||
widget.versions[0].update(created_at: 30.days.ago)
|
widget.versions[0].update(created_at: 30.days.ago)
|
||||||
|
@ -524,11 +526,11 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the first version" do
|
context "with the first version" do
|
||||||
let(:widget) { Widget.create(name: "Widget") }
|
let(:widget) { described_class.create(name: "Widget") }
|
||||||
let(:version) { widget.versions.last }
|
let(:version) { widget.versions.last }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
widget = Widget.create(name: "Widget")
|
widget = described_class.create(name: "Widget")
|
||||||
widget.update(name: "Fidget")
|
widget.update(name: "Fidget")
|
||||||
widget.update(name: "Digit")
|
widget.update(name: "Digit")
|
||||||
end
|
end
|
||||||
|
@ -547,7 +549,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the last version" do
|
context "with the last version" do
|
||||||
let(:widget) { Widget.create(name: "Widget") }
|
let(:widget) { described_class.create(name: "Widget") }
|
||||||
let(:version) { widget.versions.last }
|
let(:version) { widget.versions.last }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -571,7 +573,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with a reified item" do
|
context "with a reified item" do
|
||||||
it "know which version it came from, and return its previous self" do
|
it "know which version it came from, and return its previous self" do
|
||||||
widget = Widget.create(name: "Bob")
|
widget = described_class.create(name: "Bob")
|
||||||
%w[Tom Dick Jane].each do |name|
|
%w[Tom Dick Jane].each do |name|
|
||||||
widget.update(name: name)
|
widget.update(name: name)
|
||||||
end
|
end
|
||||||
|
@ -585,7 +587,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
describe "#next_version" do
|
describe "#next_version" do
|
||||||
context "with a reified item" do
|
context "with a reified item" do
|
||||||
it "returns the object (not a Version) as it became next" do
|
it "returns the object (not a Version) as it became next" do
|
||||||
widget = Widget.create(name: "Bob")
|
widget = described_class.create(name: "Bob")
|
||||||
%w[Tom Dick Jane].each do |name|
|
%w[Tom Dick Jane].each do |name|
|
||||||
widget.update(name: name)
|
widget.update(name: name)
|
||||||
end
|
end
|
||||||
|
@ -598,7 +600,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with a non-reified item" do
|
context "with a non-reified item" do
|
||||||
it "always returns nil because cannot ever have a next version" do
|
it "always returns nil because cannot ever have a next version" do
|
||||||
widget = Widget.new
|
widget = described_class.new
|
||||||
expect(widget.paper_trail.next_version).to(be_nil)
|
expect(widget.paper_trail.next_version).to(be_nil)
|
||||||
widget.save
|
widget.save
|
||||||
%w[Tom Dick Jane].each do |name|
|
%w[Tom Dick Jane].each do |name|
|
||||||
|
@ -612,7 +614,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
describe "#previous_version" do
|
describe "#previous_version" do
|
||||||
context "with a reified item" do
|
context "with a reified item" do
|
||||||
it "returns the object (not a Version) as it was most recently" do
|
it "returns the object (not a Version) as it was most recently" do
|
||||||
widget = Widget.create(name: "Bob")
|
widget = described_class.create(name: "Bob")
|
||||||
%w[Tom Dick Jane].each do |name|
|
%w[Tom Dick Jane].each do |name|
|
||||||
widget.update(name: name)
|
widget.update(name: name)
|
||||||
end
|
end
|
||||||
|
@ -625,7 +627,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with a non-reified item" do
|
context "with a non-reified item" do
|
||||||
it "returns the object (not a Version) as it was most recently" do
|
it "returns the object (not a Version) as it was most recently" do
|
||||||
widget = Widget.new
|
widget = described_class.new
|
||||||
expect(widget.paper_trail.previous_version).to(be_nil)
|
expect(widget.paper_trail.previous_version).to(be_nil)
|
||||||
widget.save
|
widget.save
|
||||||
%w[Tom Dick Jane].each do |name|
|
%w[Tom Dick Jane].each do |name|
|
||||||
|
@ -638,7 +640,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "with an unsaved record" do
|
context "with an unsaved record" do
|
||||||
it "not have a version created on destroy" do
|
it "not have a version created on destroy" do
|
||||||
widget = Widget.new
|
widget = described_class.new
|
||||||
widget.destroy
|
widget.destroy
|
||||||
expect(widget.versions.empty?).to(eq(true))
|
expect(widget.versions.empty?).to(eq(true))
|
||||||
end
|
end
|
||||||
|
@ -646,7 +648,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
|
|
||||||
context "when measuring the memory allocation of" do
|
context "when measuring the memory allocation of" do
|
||||||
let(:widget) do
|
let(:widget) do
|
||||||
Widget.new(
|
described_class.new(
|
||||||
name: "Warble",
|
name: "Warble",
|
||||||
a_text: "The quick brown fox",
|
a_text: "The quick brown fox",
|
||||||
an_integer: 42,
|
an_integer: 42,
|
||||||
|
@ -725,7 +727,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "`have_a_version_with` matcher", versioning: true do
|
describe "`have_a_version_with` matcher", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
widget.update!(name: "Leonard", an_integer: 1)
|
widget.update!(name: "Leonard", an_integer: 1)
|
||||||
|
@ -743,21 +745,21 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
describe "versioning option" do
|
describe "versioning option" do
|
||||||
context "when enabled", versioning: true do
|
context "when enabled", versioning: true do
|
||||||
it "enables versioning" do
|
it "enables versioning" do
|
||||||
widget = Widget.create! name: "Bob", an_integer: 1
|
widget = described_class.create! name: "Bob", an_integer: 1
|
||||||
expect(widget.versions.size).to eq(1)
|
expect(widget.versions.size).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when disabled", versioning: false do
|
context "when disabled", versioning: false do
|
||||||
it "does not enable versioning" do
|
it "does not enable versioning" do
|
||||||
widget = Widget.create! name: "Bob", an_integer: 1
|
widget = described_class.create! name: "Bob", an_integer: 1
|
||||||
expect(widget.versions.size).to eq(0)
|
expect(widget.versions.size).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Callbacks", versioning: true do
|
describe "Callbacks", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
describe "before_save" do
|
describe "before_save" do
|
||||||
it "resets value for timestamp attrs for update so that value gets updated properly" do
|
it "resets value for timestamp attrs for update so that value gets updated properly" do
|
||||||
|
@ -768,7 +770,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "after_create" do
|
describe "after_create" do
|
||||||
let(:widget) { Widget.create!(name: "Foobar", created_at: Time.current - 1.week) }
|
let(:widget) { described_class.create!(name: "Foobar", created_at: Time.current - 1.week) }
|
||||||
|
|
||||||
it "corresponding version uses the widget's `updated_at`" do
|
it "corresponding version uses the widget's `updated_at`" do
|
||||||
expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
|
expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
|
||||||
|
@ -832,7 +834,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Association", versioning: true do
|
describe "Association", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
describe "sort order" do
|
describe "sort order" do
|
||||||
it "sorts by the timestamp order from the `VersionConcern`" do
|
it "sorts by the timestamp order from the `VersionConcern`" do
|
||||||
|
@ -844,19 +846,19 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#create", versioning: true do
|
describe "#create", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
it "creates a version record" do
|
it "creates a version record" do
|
||||||
wordget = Widget.create
|
wordget = described_class.create
|
||||||
assert_equal 1, wordget.versions.length
|
assert_equal 1, wordget.versions.length
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#destroy", versioning: true do
|
describe "#destroy", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
it "creates a version record" do
|
it "creates a version record" do
|
||||||
widget = Widget.create
|
widget = described_class.create
|
||||||
assert_equal 1, widget.versions.length
|
assert_equal 1, widget.versions.length
|
||||||
widget.destroy
|
widget.destroy
|
||||||
versions_for_widget = PaperTrail::Version.with_item_keys("Widget", widget.id)
|
versions_for_widget = PaperTrail::Version.with_item_keys("Widget", widget.id)
|
||||||
|
@ -869,7 +871,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
# the `widget.versions` association, instead of `with_item_keys`.
|
# the `widget.versions` association, instead of `with_item_keys`.
|
||||||
PaperTrail::Version.with_item_keys("Widget", widget.id)
|
PaperTrail::Version.with_item_keys("Widget", widget.id)
|
||||||
}
|
}
|
||||||
widget = Widget.create
|
widget = described_class.create
|
||||||
assert_equal 1, widget.versions.length
|
assert_equal 1, widget.versions.length
|
||||||
widget.destroy
|
widget.destroy
|
||||||
assert_equal 2, versions.call(widget).length
|
assert_equal 2, versions.call(widget).length
|
||||||
|
@ -883,7 +885,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#paper_trail.originator", versioning: true do
|
describe "#paper_trail.originator", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
describe "return value" do
|
describe "return value" do
|
||||||
let(:orig_name) { FFaker::Name.name }
|
let(:orig_name) { FFaker::Name.name }
|
||||||
|
@ -923,7 +925,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#version_at", versioning: true do
|
describe "#version_at", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
context "when Timestamp argument is AFTER object has been destroyed" do
|
context "when Timestamp argument is AFTER object has been destroyed" do
|
||||||
it "returns nil" do
|
it "returns nil" do
|
||||||
|
@ -935,7 +937,7 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "touch", versioning: true do
|
describe "touch", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
it "creates a version" do
|
it "creates a version" do
|
||||||
expect { widget.touch }.to change {
|
expect { widget.touch }.to change {
|
||||||
|
@ -953,10 +955,10 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".paper_trail.update_columns", versioning: true do
|
describe ".paper_trail.update_columns", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
it "creates a version record" do
|
it "creates a version record" do
|
||||||
widget = Widget.create
|
widget = described_class.create
|
||||||
expect(widget.versions.count).to eq(1)
|
expect(widget.versions.count).to eq(1)
|
||||||
widget.paper_trail.update_columns(name: "Bugle")
|
widget.paper_trail.update_columns(name: "Bugle")
|
||||||
expect(widget.versions.count).to eq(2)
|
expect(widget.versions.count).to eq(2)
|
||||||
|
@ -966,10 +968,10 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#update", versioning: true do
|
describe "#update", versioning: true do
|
||||||
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
|
let(:widget) { described_class.create! name: "Bob", an_integer: 1 }
|
||||||
|
|
||||||
it "creates a version record" do
|
it "creates a version record" do
|
||||||
widget = Widget.create
|
widget = described_class.create
|
||||||
assert_equal 1, widget.versions.length
|
assert_equal 1, widget.versions.length
|
||||||
widget.update(name: "Bugle")
|
widget.update(name: "Bugle")
|
||||||
assert_equal 2, widget.versions.length
|
assert_equal 2, widget.versions.length
|
||||||
|
|
|
@ -4,7 +4,7 @@ require "spec_helper"
|
||||||
|
|
||||||
RSpec.describe Wotsit, versioning: true do
|
RSpec.describe Wotsit, versioning: true do
|
||||||
it "update! records timestamps" do
|
it "update! records timestamps" do
|
||||||
wotsit = Wotsit.create!(name: "wotsit")
|
wotsit = described_class.create!(name: "wotsit")
|
||||||
wotsit.update!(name: "changed")
|
wotsit.update!(name: "changed")
|
||||||
reified = wotsit.versions.last.reify
|
reified = wotsit.versions.last.reify
|
||||||
expect(reified.created_at).not_to(be_nil)
|
expect(reified.created_at).not_to(be_nil)
|
||||||
|
@ -12,7 +12,7 @@ RSpec.describe Wotsit, versioning: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "update! does not raise error" do
|
it "update! does not raise error" do
|
||||||
wotsit = Wotsit.create!(name: "name1")
|
wotsit = described_class.create!(name: "name1")
|
||||||
expect { wotsit.update!(name: "name2") }.not_to(raise_error)
|
expect { wotsit.update!(name: "name2") }.not_to(raise_error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ module PaperTrail
|
||||||
|
|
||||||
context "when incompatible" do
|
context "when incompatible" do
|
||||||
it "writes a warning to stderr" do
|
it "writes a warning to stderr" do
|
||||||
ar_version = ::Gem::Version.new("7.0.0")
|
ar_version = ::Gem::Version.new("7.1.0")
|
||||||
expect {
|
expect {
|
||||||
described_class.check_activerecord(ar_version)
|
described_class.check_activerecord(ar_version)
|
||||||
}.to output(/not compatible/).to_stderr
|
}.to output(/not compatible/).to_stderr
|
||||||
|
|
|
@ -9,7 +9,7 @@ module PaperTrail
|
||||||
context "with a new record" do
|
context "with a new record" do
|
||||||
it "returns true" do
|
it "returns true" do
|
||||||
g = Gadget.new(created_at: Time.current)
|
g = Gadget.new(created_at: Time.current)
|
||||||
event = PaperTrail::Events::Base.new(g, false)
|
event = described_class.new(g, false)
|
||||||
expect(event.changed_notably?).to eq(true)
|
expect(event.changed_notably?).to eq(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -18,14 +18,14 @@ module PaperTrail
|
||||||
it "only acknowledges non-ignored attrs" do
|
it "only acknowledges non-ignored attrs" do
|
||||||
gadget = Gadget.create!(created_at: Time.current)
|
gadget = Gadget.create!(created_at: Time.current)
|
||||||
gadget.name = "Wrench"
|
gadget.name = "Wrench"
|
||||||
event = PaperTrail::Events::Base.new(gadget, false)
|
event = described_class.new(gadget, false)
|
||||||
expect(event.changed_notably?).to eq(true)
|
expect(event.changed_notably?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not acknowledge ignored attr (brand)" do
|
it "does not acknowledge ignored attr (brand)" do
|
||||||
gadget = Gadget.create!(created_at: Time.current)
|
gadget = Gadget.create!(created_at: Time.current)
|
||||||
gadget.brand = "Acme"
|
gadget.brand = "Acme"
|
||||||
event = PaperTrail::Events::Base.new(gadget, false)
|
event = described_class.new(gadget, false)
|
||||||
expect(event.changed_notably?).to eq(false)
|
expect(event.changed_notably?).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -35,7 +35,7 @@ module PaperTrail
|
||||||
gadget = Gadget.create!(created_at: Time.current)
|
gadget = Gadget.create!(created_at: Time.current)
|
||||||
gadget.name = "Wrench"
|
gadget.name = "Wrench"
|
||||||
gadget.updated_at = Time.current
|
gadget.updated_at = Time.current
|
||||||
event = PaperTrail::Events::Base.new(gadget, false)
|
event = described_class.new(gadget, false)
|
||||||
expect(event.changed_notably?).to eq(true)
|
expect(event.changed_notably?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ module PaperTrail
|
||||||
gadget = Gadget.create!(created_at: Time.current)
|
gadget = Gadget.create!(created_at: Time.current)
|
||||||
gadget.brand = "Acme"
|
gadget.brand = "Acme"
|
||||||
gadget.updated_at = Time.current
|
gadget.updated_at = Time.current
|
||||||
event = PaperTrail::Events::Base.new(gadget, false)
|
event = described_class.new(gadget, false)
|
||||||
expect(event.changed_notably?).to eq(false)
|
expect(event.changed_notably?).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -53,7 +53,7 @@ module PaperTrail
|
||||||
it "returns a hash lacking the skipped attribute" do
|
it "returns a hash lacking the skipped attribute" do
|
||||||
# Skipper has_paper_trail(..., skip: [:another_timestamp])
|
# Skipper has_paper_trail(..., skip: [:another_timestamp])
|
||||||
skipper = Skipper.create!(another_timestamp: Time.current)
|
skipper = Skipper.create!(another_timestamp: Time.current)
|
||||||
event = PaperTrail::Events::Base.new(skipper, false)
|
event = described_class.new(skipper, false)
|
||||||
attributes = event.send(:nonskipped_attributes_before_change, false)
|
attributes = event.send(:nonskipped_attributes_before_change, false)
|
||||||
expect(attributes).not_to have_key("another_timestamp")
|
expect(attributes).not_to have_key("another_timestamp")
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,17 +11,21 @@ module PaperTrail
|
||||||
name: "Carter",
|
name: "Carter",
|
||||||
path_to_stardom: "Mexican radio"
|
path_to_stardom: "Mexican radio"
|
||||||
)
|
)
|
||||||
data = PaperTrail::Events::Destroy.new(carter, true).data
|
data = described_class.new(carter, true).data
|
||||||
expect(data[:item_type]).to eq("Family::Family")
|
expect(data[:item_type]).to eq("Family::Family")
|
||||||
expect(data[:item_subtype]).to eq("Family::CelebrityFamily")
|
expect(data[:item_subtype]).to eq("Family::CelebrityFamily")
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with skipper" do
|
context "with skipper" do
|
||||||
let(:skipper) { Skipper.create!(another_timestamp: Time.current) }
|
let(:skipper) { Skipper.create!(another_timestamp: Time.current) }
|
||||||
let(:data) { PaperTrail::Events::Destroy.new(skipper, false).data }
|
let(:data) { described_class.new(skipper, false).data }
|
||||||
|
|
||||||
it "includes `object` without skipped attributes" do
|
it "includes `object` without skipped attributes" do
|
||||||
object = YAML.load(data[:object])
|
object = if ::YAML.respond_to?(:unsafe_load)
|
||||||
|
YAML.unsafe_load(data[:object])
|
||||||
|
else
|
||||||
|
YAML.load(data[:object])
|
||||||
|
end
|
||||||
expect(object["id"]).to eq(skipper.id)
|
expect(object["id"]).to eq(skipper.id)
|
||||||
expect(object).to have_key("updated_at")
|
expect(object).to have_key("updated_at")
|
||||||
expect(object).to have_key("created_at")
|
expect(object).to have_key("created_at")
|
||||||
|
@ -29,7 +33,11 @@ module PaperTrail
|
||||||
end
|
end
|
||||||
|
|
||||||
it "includes `object_changes` without skipped and ignored attributes" do
|
it "includes `object_changes` without skipped and ignored attributes" do
|
||||||
changes = YAML.load(data[:object_changes])
|
changes = if ::YAML.respond_to?(:unsafe_load)
|
||||||
|
YAML.unsafe_load(data[:object_changes])
|
||||||
|
else
|
||||||
|
YAML.load(data[:object_changes])
|
||||||
|
end
|
||||||
expect(changes["id"]).to eq([skipper.id, nil])
|
expect(changes["id"]).to eq([skipper.id, nil])
|
||||||
expect(changes["updated_at"][0]).to be_present
|
expect(changes["updated_at"][0]).to be_present
|
||||||
expect(changes["updated_at"][1]).to be_nil
|
expect(changes["updated_at"][1]).to be_nil
|
||||||
|
|
|
@ -13,7 +13,7 @@ module PaperTrail
|
||||||
path_to_stardom: "Mexican radio"
|
path_to_stardom: "Mexican radio"
|
||||||
)
|
)
|
||||||
carter.path_to_stardom = "Johnny"
|
carter.path_to_stardom = "Johnny"
|
||||||
data = PaperTrail::Events::Update.new(carter, false, false, nil).data
|
data = described_class.new(carter, false, false, nil).data
|
||||||
expect(data[:object_changes]).to eq(
|
expect(data[:object_changes]).to eq(
|
||||||
<<~YAML
|
<<~YAML
|
||||||
---
|
---
|
||||||
|
@ -32,7 +32,7 @@ module PaperTrail
|
||||||
path_to_stardom: "Mexican radio"
|
path_to_stardom: "Mexican radio"
|
||||||
)
|
)
|
||||||
carter.path_to_stardom = "Johnny"
|
carter.path_to_stardom = "Johnny"
|
||||||
data = PaperTrail::Events::Update.new(carter, false, true, nil).data
|
data = described_class.new(carter, false, true, nil).data
|
||||||
expect(data[:object_changes]).to be_nil
|
expect(data[:object_changes]).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,19 @@ module PaperTrail
|
||||||
expect(described_class.load(hash.to_yaml)).to eq(hash)
|
expect(described_class.load(hash.to_yaml)).to eq(hash)
|
||||||
expect(described_class.load(array.to_yaml)).to eq(array)
|
expect(described_class.load(array.to_yaml)).to eq(array)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "calls the expected load method based on Psych version" do
|
||||||
|
# Psych 4+ implements .unsafe_load
|
||||||
|
if ::YAML.respond_to?(:unsafe_load)
|
||||||
|
allow(::YAML).to receive(:unsafe_load)
|
||||||
|
described_class.load("string")
|
||||||
|
expect(::YAML).to have_received(:unsafe_load)
|
||||||
|
else # Psych < 4
|
||||||
|
allow(::YAML).to receive(:load)
|
||||||
|
described_class.load("string")
|
||||||
|
expect(::YAML).to have_received(:load)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".dump" do
|
describe ".dump" do
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
module PaperTrail
|
||||||
|
module TypeSerializers
|
||||||
|
::RSpec.describe PostgresArraySerializer do
|
||||||
|
let(:word_array) { [].fill(0, rand(4..8)) { ::FFaker::Lorem.word } }
|
||||||
|
let(:word_array_as_string) { word_array.join("|") }
|
||||||
|
|
||||||
|
let(:the_thing) { described_class.new("foo", "bar") }
|
||||||
|
|
||||||
|
describe ".deserialize" do
|
||||||
|
it "deserializes array to Ruby" do
|
||||||
|
expect(the_thing.deserialize(word_array)).to eq(word_array)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "deserializes string to Ruby array" do
|
||||||
|
allow(the_thing).to receive(:deserialize_with_ar).and_return(word_array)
|
||||||
|
expect(the_thing.deserialize(word_array_as_string)).to eq(word_array)
|
||||||
|
expect(the_thing).to have_received(:deserialize_with_ar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".dump" do
|
||||||
|
it "serializes Ruby to JSON" do
|
||||||
|
expect(the_thing.serialize(word_array)).to eq(word_array)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,7 @@ require "simplecov"
|
||||||
SimpleCov.start do
|
SimpleCov.start do
|
||||||
add_filter "spec"
|
add_filter "spec"
|
||||||
end
|
end
|
||||||
SimpleCov.minimum_coverage 92.4
|
SimpleCov.minimum_coverage(ENV["DB"] == "postgres" ? 97.3 : 92.4)
|
||||||
|
|
||||||
require "byebug"
|
require "byebug"
|
||||||
require_relative "support/pt_arel_helpers"
|
require_relative "support/pt_arel_helpers"
|
||||||
|
@ -53,7 +53,7 @@ end
|
||||||
# in `dummy_app/config/*`. By consolidating it here,
|
# in `dummy_app/config/*`. By consolidating it here,
|
||||||
#
|
#
|
||||||
# - It can better be understood, and documented in one place
|
# - It can better be understood, and documented in one place
|
||||||
# - It can more closely resememble a conventional app boot. For example, loading
|
# - It can more closely resemble a conventional app boot. For example, loading
|
||||||
# gems (like rspec-rails) _before_ loading the app.
|
# gems (like rspec-rails) _before_ loading the app.
|
||||||
|
|
||||||
# First, `config/boot.rb` would add gems to $LOAD_PATH.
|
# First, `config/boot.rb` would add gems to $LOAD_PATH.
|
||||||
|
|
|
@ -18,7 +18,7 @@ class PaperTrailSpecMigrator
|
||||||
@migrations_path = dummy_app_migrations_dir
|
@migrations_path = dummy_app_migrations_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
# Looks like the API for programatically running migrations will change
|
# Looks like the API for programmatically running migrations will change
|
||||||
# in rails 5.2. This is an undocumented change, AFAICT. Then again,
|
# in rails 5.2. This is an undocumented change, AFAICT. Then again,
|
||||||
# how many people use the programmatic interface? Most people probably
|
# how many people use the programmatic interface? Most people probably
|
||||||
# just use rake. Maybe we're doing it wrong.
|
# just use rake. Maybe we're doing it wrong.
|
||||||
|
|
|
@ -0,0 +1,388 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_examples "queries" do |column_type, model, name_of_integer_column|
|
||||||
|
let(:record) { model.new }
|
||||||
|
let(:name) { FFaker::Name.first_name }
|
||||||
|
let(:int) { column_type == :text ? 1 : rand(2..6) }
|
||||||
|
|
||||||
|
after do
|
||||||
|
PaperTrail.serializer = PaperTrail::Serializers::YAML
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#where_attribute_changes", versioning: true do
|
||||||
|
it "requires its argument to be a string or a symbol" do
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_attribute_changes({})
|
||||||
|
}.to raise_error(ArgumentError)
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_attribute_changes([])
|
||||||
|
}.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_attribute_changes method" do
|
||||||
|
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||||
|
bicycle = model.create!(name: "abc")
|
||||||
|
bicycle.update!(name: "xyz")
|
||||||
|
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:where_attribute_changes).with(model.paper_trail.version_class, :name)
|
||||||
|
).and_return([bicycle.versions[0], bicycle.versions[1]])
|
||||||
|
|
||||||
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
|
expect(
|
||||||
|
bicycle.versions.where_attribute_changes(:name)
|
||||||
|
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
||||||
|
expect(adapter).to have_received(:where_attribute_changes)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "defaults to the original behavior" do
|
||||||
|
adapter = Class.new.new
|
||||||
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
|
bicycle = model.create!(name: "abc")
|
||||||
|
bicycle.update!(name: "xyz")
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
expect {
|
||||||
|
bicycle.versions.where_attribute_changes(:name)
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_attribute_changes expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
expect(
|
||||||
|
bicycle.versions.where_attribute_changes(:name)
|
||||||
|
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
it "raises error" do
|
||||||
|
expect {
|
||||||
|
record.versions.where_attribute_changes(:name).to_a
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_attribute_changes expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
it "locates versions according to their object_changes contents" do
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name_of_integer_column => 17)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
record.versions.where_attribute_changes(:name)
|
||||||
|
).to eq([record.versions[0]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_attribute_changes(name_of_integer_column.to_s)
|
||||||
|
).to eq([record.versions[0], record.versions[1]])
|
||||||
|
expect(record.class.column_names).to include("color")
|
||||||
|
expect(
|
||||||
|
record.versions.where_attribute_changes(:color)
|
||||||
|
).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#where_object", versioning: true do
|
||||||
|
it "requires its argument to be a Hash" do
|
||||||
|
record.update!(name: name, name_of_integer_column => int)
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name: FFaker::Name.last_name, name_of_integer_column => 15)
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_object(:foo)
|
||||||
|
}.to raise_error(ArgumentError)
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_object([])
|
||||||
|
}.to raise_error(ArgumentError)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with YAML serializer" do
|
||||||
|
it "locates versions according to their `object` contents" do
|
||||||
|
expect(PaperTrail.serializer).to be PaperTrail::Serializers::YAML
|
||||||
|
record.update!(name: name, name_of_integer_column => int)
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name: FFaker::Name.last_name, name_of_integer_column => 15)
|
||||||
|
expect(
|
||||||
|
model.paper_trail.version_class.where_object(name_of_integer_column => int)
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
model.paper_trail.version_class.where_object(name: name)
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
model.paper_trail.version_class.where_object(name_of_integer_column => 100)
|
||||||
|
).to eq([record.versions[2]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with JSON serializer" do
|
||||||
|
it "locates versions according to their `object` contents" do
|
||||||
|
PaperTrail.serializer = PaperTrail::Serializers::JSON
|
||||||
|
expect(PaperTrail.serializer).to be PaperTrail::Serializers::JSON
|
||||||
|
record.update!(name: name, name_of_integer_column => int)
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name: FFaker::Name.last_name, name_of_integer_column => 15)
|
||||||
|
expect(
|
||||||
|
model.paper_trail.version_class.where_object(name_of_integer_column => int)
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
model.paper_trail.version_class.where_object(name: name)
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
model.paper_trail.version_class.where_object(name_of_integer_column => 100)
|
||||||
|
).to eq([record.versions[2]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#where_object_changes", versioning: true do
|
||||||
|
it "requires its argument to be a Hash" do
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_object_changes(:foo)
|
||||||
|
}.to raise_error(ArgumentError)
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_object_changes([])
|
||||||
|
}.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 method" do
|
||||||
|
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||||
|
bicycle = model.create!(name: "abc")
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:where_object_changes).with(model.paper_trail.version_class, 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
|
||||||
|
|
||||||
|
it "defaults to the original behavior" do
|
||||||
|
adapter = Class.new.new
|
||||||
|
PaperTrail.config.object_changes_adapter = adapter
|
||||||
|
bicycle = model.create!(name: "abc")
|
||||||
|
if column_type == :text
|
||||||
|
expect {
|
||||||
|
bicycle.versions.where_object_changes(name: "abc")
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_object_changes expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
expect(
|
||||||
|
bicycle.versions.where_object_changes(name: "abc")
|
||||||
|
).to match_array(bicycle.versions[0..1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
it "raises error" do
|
||||||
|
expect {
|
||||||
|
record.versions.where_object_changes(name: "foo").to_a
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_object_changes expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
it "locates versions according to their object_changes contents" do
|
||||||
|
record.update!(name: name, name_of_integer_column => 0)
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name: FFaker::Name.last_name, name_of_integer_column => int)
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes(name: name)
|
||||||
|
).to eq(record.versions[0..1])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes(name_of_integer_column => 100)
|
||||||
|
).to eq(record.versions[1..2])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes(name_of_integer_column => int)
|
||||||
|
).to eq([record.versions.last])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes(name_of_integer_column => 100, name: "foobar")
|
||||||
|
).to eq(record.versions[1..2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#where_object_changes_from", versioning: true do
|
||||||
|
it "requires its argument to be a Hash" do
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_object_changes_from(:foo)
|
||||||
|
}.to raise_error(ArgumentError)
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.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 = model.create!(name: "abc")
|
||||||
|
bicycle.update!(name: "xyz")
|
||||||
|
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:where_object_changes_from).with(model.paper_trail.version_class, 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 = model.create!(name: "abc")
|
||||||
|
bicycle.update!(name: "xyz")
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
expect {
|
||||||
|
bicycle.versions.where_object_changes_from(name: "abc")
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_object_changes_from expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
expect(
|
||||||
|
bicycle.versions.where_object_changes_from(name: "abc")
|
||||||
|
).to match_array([bicycle.versions[1]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
it "raises error" do
|
||||||
|
expect {
|
||||||
|
record.versions.where_object_changes_from(name: "foo").to_a
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_object_changes_from expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
it "locates versions according to their object_changes contents" do
|
||||||
|
record.update!(name: name, name_of_integer_column => 0)
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name: FFaker::Name.last_name, name_of_integer_column => int)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_from(name: name)
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_from(name_of_integer_column => 100)
|
||||||
|
).to eq([record.versions[2]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_from(name_of_integer_column => int)
|
||||||
|
).to eq([])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_from(name_of_integer_column => 100, name: "foobar")
|
||||||
|
).to eq([record.versions[2]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#where_object_changes_to", versioning: true do
|
||||||
|
it "requires its argument to be a Hash" do
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.where_object_changes_to(:foo)
|
||||||
|
}.to raise_error(ArgumentError)
|
||||||
|
expect {
|
||||||
|
model.paper_trail.version_class.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 = model.create!(name: "abc")
|
||||||
|
bicycle.update!(name: "xyz")
|
||||||
|
|
||||||
|
allow(adapter).to(
|
||||||
|
receive(:where_object_changes_to).with(model.paper_trail.version_class, 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 = model.create!(name: "abc")
|
||||||
|
bicycle.update!(name: "xyz")
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
expect {
|
||||||
|
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_object_changes_to expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
expect(
|
||||||
|
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||||
|
).to match_array([bicycle.versions[1]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if column_type == :text
|
||||||
|
it "raises error" do
|
||||||
|
expect {
|
||||||
|
record.versions.where_object_changes_to(name: "foo").to_a
|
||||||
|
}.to raise_error(
|
||||||
|
::PaperTrail::UnsupportedColumnType,
|
||||||
|
"where_object_changes_to expected json or jsonb column, got text"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
it "locates versions according to their object_changes contents" do
|
||||||
|
record.update!(name: name, name_of_integer_column => 0)
|
||||||
|
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||||
|
record.update!(name: FFaker::Name.last_name, name_of_integer_column => int)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_to(name: name)
|
||||||
|
).to eq([record.versions[0]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_to(name_of_integer_column => 100)
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_to(name_of_integer_column => int)
|
||||||
|
).to eq([record.versions[2]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_to(name_of_integer_column => 100, name: "foobar")
|
||||||
|
).to eq([record.versions[1]])
|
||||||
|
expect(
|
||||||
|
record.versions.where_object_changes_to(name_of_integer_column => -1)
|
||||||
|
).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue