Merge pull request #1367 from paper-trail-gem/release-12.2.0

Release 12.2.0
This commit is contained in:
Jared Beck 2022-01-21 00:24:45 -05:00 committed by GitHub
commit f21e9a4368
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 975 additions and 737 deletions

View File

@ -32,11 +32,11 @@ require "bundler/inline"
# STEP ONE: What versions are you using?
gemfile(true) do
ruby "2.5.1"
ruby "3.0.2"
source "https://rubygems.org"
gem "activerecord", "5.2.0"
gem "activerecord", "6.1.4.1"
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"
end

View File

@ -14,7 +14,7 @@ jobs:
uses: ruby/setup-ruby@v1
with:
# See "Lowest supported ruby version" in CONTRIBUTING.md
ruby-version: '2.5'
ruby-version: '2.6'
- name: Bundle
run: |
gem install bundler
@ -63,13 +63,15 @@ jobs:
# in case it still produces any deprecation warnings.
#
# 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:
# rails 5.2 requires ruby < 3.0
# https://github.com/rails/rails/issues/40938
- ruby: '3.0'
gemfile: 'rails_5.2'
- ruby: '3.1'
gemfile: 'rails_5.2'
steps:
- name: Checkout source
uses: actions/checkout@v2

View File

@ -14,25 +14,24 @@ inherit_from: .rubocop_todo.yml
# - Only include permanent config; temporary goes in .rubocop_todo.yml
AllCops:
# Generated files, like schema.rb, are out of our control.
Exclude:
- gemfiles/vendor/bundle/**/* # This dir only shows up on travis ¯\_(ツ)_/¯
- spec/dummy_app/db/schema.rb # Generated, out of our control
- gemfiles/*
- spec/dummy_app/db/schema.rb
# Enable pending cops so we can adopt the code before they are switched on.
NewCops: enable
# See "Lowest supported ruby version" in CONTRIBUTING.md
TargetRubyVersion: 2.5
Bundler/OrderedGems:
Exclude:
- gemfiles/* # generated by Appraisal
TargetRubyVersion: 2.6
Layout/ArgumentAlignment:
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:
EnforcedStyle: trailing
Enabled: false
# Avoid blank lines inside methods. They are a sign that the method is too big.
Layout/EmptyLineAfterGuardClause:
@ -57,20 +56,11 @@ Layout/MultilineOperationIndentation:
Layout/ParameterAlignment:
EnforcedStyle: with_fixed_indentation
Layout/SpaceAroundMethodCallOperator:
Enabled: true
# 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.
Layout/SpaceAroundOperators:
AllowForAlignment: false
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
# Migrations often contain long up/down methods, and extracting smaller methods
# from these is of questionable value.
Metrics/AbcSize:
@ -100,12 +90,9 @@ Naming/FileName:
- Appraisals
# 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
# heredocs that are used as anonymous values (not a variable, constant, or
# named parameter).
#
# All heredocs containing SQL should be named SQL, to support editor syntax
# highlighting.
# name, so naming the delimiter doesn't add much value unless doing so improves
# syntax highlighting. For example, all heredocs containing SQL should be named
# SQL, to support editor syntax highlighting.
Naming/HeredocDelimiterNaming:
Enabled: false
@ -136,11 +123,6 @@ Rails/SkipsModelValidations:
RSpec/DescribeClass:
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,
# "no example will ever be longer than this"? Hard to say. Sometimes they're
# quite long.
@ -164,10 +146,6 @@ Style/BlockDelimiters:
Style/DoubleNegation:
Enabled: false
# This cop is unimportant in this repo.
Style/ExponentialNotation:
Enabled: false
# Avoid annotated tokens except in desperately complicated format strings.
# In 99% of format strings they actually make it less readable.
Style/FormatStringToken:

View File

@ -6,21 +6,6 @@
# Note that changes in the inspected code, or installation of new
# 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
# Cop supports --auto-correct.
Rails/ApplicationRecord:

View File

@ -24,3 +24,8 @@ appraise "rails-6.1" do
gem "rails", "~> 6.1.0"
gem "rails-controller-testing", "~> 1.0.5"
end
appraise "rails-7.0" do
gem "rails", "~> 7.0.0"
gem "rails-controller-testing", "~> 1.0.5"
end

View File

@ -17,6 +17,29 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
- 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)
### Breaking Changes

View File

@ -15,7 +15,7 @@ This is the _user guide_. See also, the
Choose version:
[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),
[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),
@ -1208,17 +1208,20 @@ class PostVersion < PaperTrail::Version
end
```
If you only use custom version classes and don't have a `versions` table, you
must let ActiveRecord know that the `PaperTrail::Version` class is an
`abstract_class`.
If you only use custom version classes and don't have a `versions` table, you must
let ActiveRecord know that your base version class (eg. `ApplicationVersion` below)
class is an `abstract_class`.
```ruby
# app/models/paper_trail/version.rb
module PaperTrail
class Version < ActiveRecord::Base
include PaperTrail::VersionConcern
self.abstract_class = true
end
# app/models/application_version.rb
class ApplicationVersion < ActiveRecord::Base
include PaperTrail::VersionConcern
self.abstract_class = true
end
class PostVersion < ApplicationVersion
self.table_name = :post_versions
self.sequence_name = :post_versions_id_seq
end
```

View File

@ -38,11 +38,8 @@ task :clean do
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] do
desc "Create the database."
task :create_db do
puts format("creating %s database", ENV["DB"])
case ENV["DB"]
when "mysql"
@ -59,6 +56,12 @@ task prepare: %i[clean install_database_yml] do
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"
desc "Run tests on PaperTrail with RSpec"
task(:spec).clear

View File

@ -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: "../"

View File

@ -28,7 +28,9 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
# MySQL users should also upgrade to at least rails 4.2, which is the first
# version of ActiveRecord with support for fractional seconds in MySQL.
# (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
end
add_index :versions, %i(item_type item_id)

View File

@ -26,6 +26,8 @@ module PaperTrail
named created_at.
EOS
RAILS_GTE_7_0 = ::ActiveRecord.gem_version >= ::Gem::Version.new("7.0.0")
extend PaperTrail::Cleaner
class << self

View File

@ -32,6 +32,12 @@ module PaperTrail
if defined_enums[attr] && val.is_a?(::String)
# Because PT 4 used to save the string version of enums to `object_changes`
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
AttributeSerializerFactory.for(@klass, attr).deserialize(val)
end

View File

@ -8,7 +8,7 @@ module PaperTrail
#
# 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
# tested against. See `.travis.yml`.
# tested against. See `.github/workflows/test.yml`.
#
# However, as of
# [#1213](https://github.com/paper-trail-gem/paper_trail/pull/1213) our
@ -18,7 +18,7 @@ module PaperTrail
# versions.
module Compatibility
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
PaperTrail %s is not compatible with ActiveRecord %s. We allow PT

View File

@ -116,6 +116,20 @@ module PaperTrail
@changes_in_latest_version ||= load_changes_in_latest_version
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
# and/or the `:skip` option. Returns true if an ignored attribute has
# changed.
@ -182,20 +196,28 @@ module PaperTrail
if value.respond_to?(:call)
value.call(@record)
elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
# If it is an attribute that is changing in an existing object,
# 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
metadatum_from_model_method(event, value)
else
value
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
def notable_changes
changes_in_latest_version.delete_if { |k, _v|
@ -207,16 +229,9 @@ module PaperTrail
def notably_changed
# Memoized to reduce memory usage
@notably_changed ||= begin
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.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
only = evaluate_only
cani = changed_and_not_ignored
only.empty? ? cani : (cani & only)
end
end

View File

@ -35,16 +35,21 @@ module PaperTrail
if record_object?
data[:object] = recordable_object(@is_touch)
end
if record_object_changes?
changes = @force_changes.nil? ? notable_changes : @force_changes
data[:object_changes] = prepare_object_changes(changes)
end
merge_object_changes_into(data)
merge_item_subtype_into(data)
merge_metadata_into(data)
end
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
# perform dirty-tracking. Specifically, methods from `Dirty`, like
# `saved_changes`, return the same values before and after `touch`.

View File

@ -40,8 +40,7 @@ module PaperTrail
@model_class.after_create { |r|
r.paper_trail.record_create if r.paper_trail.save_version?
}
return if @model_class.paper_trail_options[:on].include?(:create)
@model_class.paper_trail_options[:on] << :create
append_option_uniquely(:on, :create)
end
# Adds a callback that records a version before or after a "destroy" event.
@ -49,7 +48,6 @@ module PaperTrail
# @api public
def on_destroy(recording_order = "before")
assert_valid_recording_order_for_on_destroy(recording_order)
@model_class.send(
"#{recording_order}_destroy",
lambda do |r|
@ -57,9 +55,7 @@ module PaperTrail
r.paper_trail.record_destroy(recording_order)
end
)
return if @model_class.paper_trail_options[:on].include?(:destroy)
@model_class.paper_trail_options[:on] << :destroy
append_option_uniquely(:on, :destroy)
end
# Adds a callback that records a version after an "update" event.
@ -81,8 +77,7 @@ module PaperTrail
@model_class.after_update { |r|
r.paper_trail.clear_version_instance
}
return if @model_class.paper_trail_options[:on].include?(:update)
@model_class.paper_trail_options[:on] << :update
append_option_uniquely(:on, :update)
end
# Adds a callback that records a version after a "touch" event.
@ -96,11 +91,13 @@ module PaperTrail
# @api public
def on_touch
@model_class.after_touch { |r|
r.paper_trail.record_update(
force: RAILS_LT_6_0,
in_after_callback: true,
is_touch: true
)
if r.paper_trail.save_version?
r.paper_trail.record_update(
force: RAILS_LT_6_0,
in_after_callback: true,
is_touch: true
)
end
}
end
@ -127,6 +124,13 @@ module PaperTrail
RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.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`.
# @api private
def assert_concrete_activerecord_class(class_name)
@ -205,6 +209,14 @@ module PaperTrail
options
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)
options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
end
@ -239,12 +251,8 @@ module PaperTrail
@model_class.paper_trail_options = options.dup
%i[ignore skip only].each do |k|
@model_class.paper_trail_options[k] = [@model_class.paper_trail_options[k]].
flatten.
compact.
map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
@model_class.paper_trail_options[k] = event_attribute_option(k)
end
@model_class.paper_trail_options[:meta] ||= {}
end
end

View File

@ -9,7 +9,7 @@ module PaperTrail
extend self # makes all instance methods become module methods as well
def load(string)
::YAML.load string
::YAML.respond_to?(:unsafe_load) ? ::YAML.unsafe_load(string) : ::YAML.load(string)
end
# @param object (Hash | HashWithIndifferentAccess) - Coming from

View File

@ -16,7 +16,7 @@ module PaperTrail
extend ::ActiveSupport::Concern
included do
belongs_to :item, polymorphic: true, optional: true
belongs_to :item, polymorphic: true, optional: true, inverse_of: false
validates_presence_of :event
after_create :enforce_version_limit!
end
@ -376,10 +376,11 @@ module PaperTrail
#
# @api private
def version_limit
if limit_option?(item.class)
item.class.paper_trail_options[:limit]
elsif base_class_limit_option?(item.class)
item.class.base_class.paper_trail_options[:limit]
klass = item.class
if limit_option?(klass)
klass.paper_trail_options[:limit]
elsif base_class_limit_option?(klass)
klass.base_class.paper_trail_options[:limit]
else
PaperTrail.config.version_limit
end

View File

@ -8,7 +8,7 @@ module PaperTrail
# People are encouraged to use `PaperTrail.gem_version` instead.
module VERSION
MAJOR = 12
MINOR = 1
MINOR = 2
TINY = 0
# Set PRE to nil unless it's a pre-release (beta, rc, etc.)

View File

@ -43,9 +43,7 @@ has been destroyed.
# about 3 years, per https://www.ruby-lang.org/en/downloads/branches/
#
# See "Lowest supported ruby version" in CONTRIBUTING.md
#
# Ruby 2.5 reaches EoL on 2021-03-31.
s.required_ruby_version = ">= 2.5.0"
s.required_ruby_version = ">= 2.6.0"
# We no longer specify a maximum activerecord version.
# See discussion in paper_trail/compatibility.rb
@ -53,8 +51,8 @@ has been destroyed.
s.add_dependency "request_store", "~> 1.1"
s.add_development_dependency "appraisal", "~> 2.4.1"
s.add_development_dependency "byebug", "~> 11.0"
s.add_development_dependency "ffaker", "~> 2.19.0"
s.add_development_dependency "byebug", "~> 11.1"
s.add_development_dependency "ffaker", "~> 2.20"
s.add_development_dependency "generator_spec", "~> 0.9.4"
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 "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-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-rspec", "~> 2.4.0"
s.add_development_dependency "simplecov", ">= 0.21", "< 0.22"
s.add_development_dependency "rubocop-rspec", "~> 2.5.0"
s.add_development_dependency "simplecov", "~> 0.21.2"
# ## Database Adapters
#
@ -83,7 +81,7 @@ has been destroyed.
# 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
# `Appraisals`.
s.add_development_dependency "mysql2", "~> 0.5"
s.add_development_dependency "pg", ">= 0.18", "< 2.0"
s.add_development_dependency "mysql2", "~> 0.5.3"
s.add_development_dependency "pg", "~> 1.2"
s.add_development_dependency "sqlite3", "~> 1.4"
end

View File

@ -2,4 +2,6 @@
class Car < Vehicle
has_paper_trail
attribute :color, type: ActiveModel::Type::String
attr_accessor :top_speed
end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
# See also `Vegetable` which uses `JsonbVersion`.
class Fruit < ActiveRecord::Base
if ENV["DB"] == "postgres" || JsonVersion.table_exists?
if ENV["DB"] == "postgres"
has_paper_trail versions: { class_name: "JsonVersion" }
end
end

View File

@ -2,12 +2,8 @@
# Demonstrates the `if` and `unless` configuration options.
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(
if: proc { |t| t.language_code == "US" },
unless: proc { |t| t.type == "DRAFT" }
unless: proc { |t| t.draft_status == "DRAFT" }
)
end

View File

@ -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

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class JsonbVersion < ActiveRecord::Base
include PaperTrail::VersionConcern
self.table_name = "jsonb_versions"
end

View File

@ -128,16 +128,19 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
add_index :no_object_versions, %i[item_type item_id]
if ENV["DB"] == "postgres"
create_table :json_versions, force: true do |t|
t.string :item_type, null: false
t.integer :item_id, null: false
t.string :event, null: false
t.string :whodunnit
t.json :object
t.json :object_changes
t.datetime :created_at, limit: 6
%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.bigint :item_id, null: false
t.string :event, null: false
t.string :whodunnit
t.public_send j, :object
t.public_send j, :object_changes
t.datetime :created_at, limit: 6
end
add_index table_name, %i[item_type item_id]
end
add_index :json_versions, %i[item_type item_id]
end
create_table :not_on_updates, force: true do |t|
@ -249,10 +252,10 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
end
create_table :translations, force: true do |t|
t.string :headline
t.string :content
t.string :draft_status
t.string :headline
t.string :language_code
t.string :type
end
create_table :gadgets, force: true do |t|
@ -277,8 +280,9 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
end
create_table :fruits, force: true do |t|
t.string :name
t.string :color
t.integer :mass
t.string :name
end
create_table :boolits, force: true do |t|
@ -358,6 +362,12 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
t.integer :parent_id
t.integer :partner_id
end
create_table :vegetables, force: true do |t|
t.string :color
t.integer :mass
t.string :name
end
end
def down

View File

@ -4,8 +4,8 @@ require "spec_helper"
RSpec.describe Animal, type: :model, versioning: true do
it "baseline test setup" do
expect(Animal.new).to be_versioned
expect(Animal.inheritance_column).to eq("species")
expect(described_class.new).to be_versioned
expect(described_class.inheritance_column).to eq("species")
end
describe "#descends_from_active_record?" do
@ -15,7 +15,7 @@ RSpec.describe Animal, type: :model, versioning: true do
end
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 Muppet")
animal.destroy
@ -46,7 +46,7 @@ RSpec.describe Animal, type: :model, versioning: true do
it "allows the inheritance_column (species) to be updated" do
cat = Cat.create!(name: "Leo")
cat.update(name: "Spike", species: "Dog")
dog = Animal.find(cat.id)
dog = described_class.find(cat.id)
expect(dog).to be_instance_of(Dog)
end

View File

@ -187,7 +187,7 @@ RSpec.describe Article, type: :model, versioning: true do
end
context "with an item" do
let(:article) { Article.new(title: initial_title) }
let(:article) { described_class.new(title: initial_title) }
let(:initial_title) { "Foobar" }
context "when it is created" do

View File

@ -5,7 +5,7 @@ require "spec_helper"
RSpec.describe Book, versioning: true do
context "with :has_many :through" 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")
Person.create(name: "Solzhenitsyn")
count = PaperTrail::Version.count
@ -15,7 +15,7 @@ RSpec.describe Book, versioning: true do
end
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: "Solzhenitsyn")
count = PaperTrail::Version.count
@ -27,7 +27,7 @@ RSpec.describe Book, versioning: true do
end
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")
Person.create(name: "Solzhenitsyn")
(book.authors << dostoyevsky)
@ -39,7 +39,7 @@ RSpec.describe Book, versioning: true do
end
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")
Person.create(name: "Solzhenitsyn")
book.authors << dostoyevsky
@ -53,7 +53,7 @@ RSpec.describe Book, versioning: true do
context "when a persisted record is updated then destroyed" 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"]
expect(changes).to eq("id" => [nil, book.id], "title" => [nil, "A"])

View File

@ -4,7 +4,7 @@ require "spec_helper"
require "support/custom_json_serializer"
RSpec.describe Boolit, type: :model, versioning: true do
let(:boolit) { Boolit.create! }
let(:boolit) { described_class.create! }
before { boolit.update!(name: FFaker::Name.name) }
@ -20,7 +20,7 @@ RSpec.describe Boolit, type: :model, versioning: true do
before { boolit.update!(scoped: false) }
it "is NOT scoped" do
expect(Boolit.first).to be_nil
expect(described_class.first).to be_nil
end
it "still can be reified and persisted" do

View File

@ -7,9 +7,28 @@ RSpec.describe Car, type: :model do
describe "changeset", versioning: true 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")
assert_includes car.versions.last.changeset.keys, "name"
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

View File

@ -12,9 +12,9 @@ RSpec.describe CustomPrimaryKeyRecord, type: :model do
version = custom_primary_key_record.versions.last
expect(version).to be_a(CustomPrimaryKeyRecordVersion)
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
expect(CustomPrimaryKeyRecordVersion.last.reify).to be_a(CustomPrimaryKeyRecord)
expect(CustomPrimaryKeyRecordVersion.last.reify).to be_a(described_class)
end
end
end

View File

@ -5,7 +5,7 @@ require "spec_helper"
RSpec.describe Document, type: :model, versioning: true do
describe "have_a_version_with matcher" do
it "works with custom versions association" do
document = Document.create!(name: "Foo")
document = described_class.create!(name: "Foo")
document.update!(name: "Bar")
expect(document).to have_a_version_with(name: "Foo")
end
@ -13,7 +13,7 @@ RSpec.describe Document, type: :model, versioning: true do
describe "#paper_trail.next_version" do
it "returns the expected document" do
doc = Document.create
doc = described_class.create
doc.update(name: "Doc 1")
reified = doc.paper_trail_versions.last.reify
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
it "returns the expected document" do
doc = Document.create
doc = described_class.create
doc.update(name: "Doc 1")
doc.update(name: "Doc 2")
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
it "returns the expected version records" do
doc = Document.create
doc = described_class.create
doc.update(name: "Doc 1")
expect(doc.paper_trail_versions.length).to(eq(2))
expect(doc.paper_trail_versions.map(&:event)).to(
@ -43,7 +43,7 @@ RSpec.describe Document, type: :model, versioning: true do
describe "#versions" do
it "does not respond to versions method" do
doc = Document.create
doc = described_class.create
doc.update(name: "Doc 1")
expect(doc).not_to respond_to(:versions)
end

View File

@ -5,7 +5,7 @@ require "support/performance_helpers"
RSpec.describe(FooWidget, versioning: true) do
context "with a subclass" do
let(:foo) { FooWidget.create }
let(:foo) { described_class.create }
before do
foo.update!(name: "Foo")
@ -26,7 +26,7 @@ RSpec.describe(FooWidget, versioning: true) do
before { foo.destroy }
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.next).to(be_nil)
end

View File

@ -2,19 +2,54 @@
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
describe "have_a_version_with_changes matcher" do
it "works with Fruit because Fruit uses JsonVersion" do
# 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
# here.
banana = Fruit.create!(color: "Red", name: "Banana")
banana = described_class.create!(color: "Red", name: "Banana")
banana.update!(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: "Yellow", name: "Kiwi")
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

View File

@ -3,7 +3,7 @@
require "spec_helper"
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 }
@ -35,7 +35,11 @@ RSpec.describe Gadget, type: :model do
gadget.update_attribute(:updated_at, Time.current + 1)
}.to(change { gadget.versions.size }.by(1))
expect(
YAML.load(gadget.versions.last.object_changes).keys
if ::YAML.respond_to?(:unsafe_load)
YAML.unsafe_load(gadget.versions.last.object_changes).keys
else
YAML.load(gadget.versions.last.object_changes).keys
end
).to eq(["updated_at"])
end
end

View File

@ -4,10 +4,10 @@ require "spec_helper"
RSpec.describe JoinedVersion, type: :model, versioning: true do
let(:widget) { Widget.create!(name: FFaker::Name.name) }
let(:version) { JoinedVersion.first }
let(:version) { described_class.first }
describe "default_scope" do
it { expect(JoinedVersion.default_scopes).not_to be_empty }
it { expect(described_class.default_scopes).not_to be_empty }
end
describe "VersionConcern::ClassMethods" do
@ -15,19 +15,19 @@ RSpec.describe JoinedVersion, type: :model, versioning: true do
describe "#subsequent" 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
describe "#preceding" 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
describe "#between" 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

View File

@ -5,7 +5,7 @@ require "spec_helper"
# The `json_versions` table tests postgres' `json` data type. So, that
# table is only created when testing against postgres.
if JsonVersion.table_exists?
RSpec.describe JsonVersion, type: :model do
RSpec.describe JsonVersion, type: :model, versioning: true do
it "includes the VersionConcern module" do
expect(described_class).to include(PaperTrail::VersionConcern)
end

View File

@ -27,7 +27,11 @@ RSpec.describe NoObject, versioning: true do
# New feature: destroy populates object_changes
# 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["letter"]).to eq([n.letter, nil])
expect(h["created_at"][0]).to be_present
@ -38,7 +42,7 @@ RSpec.describe NoObject, versioning: true do
describe "reify" do
it "raises error" do
n = NoObject.create!(letter: "A")
n = described_class.create!(letter: "A")
v = n.versions.last
expect { v.reify }.to(
raise_error(
@ -51,7 +55,7 @@ RSpec.describe NoObject, versioning: true do
describe "where_object" do
it "raises error" do
n = NoObject.create!(letter: "A")
n = described_class.create!(letter: "A")
expect {
n.versions.where_object(foo: "bar")
}.to(

View File

@ -9,21 +9,21 @@ require "spec_helper"
RSpec.describe Person, type: :model, versioning: true do
describe "#time_zone" 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))
end
end
context "when the model is saved" 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!
len = person.versions.last.object_changes.length
expect((len < 105)).to(be_truthy)
end
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!
as_stored_in_version = HashWithIndifferentAccess[
YAML.load(person.versions.last.object_changes)
@ -34,14 +34,14 @@ RSpec.describe Person, type: :model, versioning: true do
end
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!
unserialized_value = Person::TimeZoneSerializer.load(person.time_zone)
expect(person.versions.last.changeset[:time_zone].last).to(eq(unserialized_value))
end
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
person.save!
expect(
@ -50,7 +50,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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
person.save!
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
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.assign_attributes(time_zone: "Pacific Time (US & Canada)")
person.save!
@ -70,7 +70,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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.assign_attributes(time_zone: "Pacific Time (US & Canada)")
person.save!
@ -79,7 +79,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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!
attribute_value_before_change = person.time_zone
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
@ -93,7 +93,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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.assign_attributes(time_zone: "Pacific Time (US & Canada)")
person.save!
@ -106,7 +106,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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!
attribute_value_before_change = person.time_zone
person.assign_attributes(time_zone: "Pacific Time (US & Canada)")
@ -116,7 +116,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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.assign_attributes(time_zone: "Pacific Time (US & Canada)")
person.save!
@ -125,7 +125,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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.assign_attributes(time_zone: "Pacific Time (US & Canada)")
changes_before_save = person.changes.dup
@ -136,7 +136,7 @@ RSpec.describe Person, type: :model, versioning: true do
end
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.assign_attributes(time_zone: "Pacific Time (US & Canada)")
changes_before_save = person.changes.dup
@ -151,7 +151,7 @@ RSpec.describe Person, type: :model, versioning: true do
describe "#cars and bicycles" do
it "can be reified" do
person = Person.create(name: "Frank")
person = described_class.create(name: "Frank")
car = Car.create(name: "BMW 325")
bicycle = Bicycle.create(name: "BMX 1.0")

View File

@ -5,7 +5,7 @@ require "rails/generators"
RSpec.describe Pet, type: :model, versioning: true do
it "baseline test setup" do
expect(Pet.new).to be_versioned
expect(described_class.new).to be_versioned
end
it "can be reified" do
@ -13,8 +13,8 @@ RSpec.describe Pet, type: :model, versioning: true do
dog = Dog.create(name: "Snoopy")
cat = Cat.create(name: "Garfield")
person.pets << Pet.create(animal: dog)
person.pets << Pet.create(animal: cat)
person.pets << described_class.create(animal: dog)
person.pets << described_class.create(animal: cat)
person.update(name: "Steve")
dog.update(name: "Beethoven")

View File

@ -4,8 +4,8 @@ require "spec_helper"
RSpec.describe Plant, type: :model, versioning: true do
it "baseline test setup" do
expect(Plant.new).to be_versioned
expect(Plant.inheritance_column).to eq("species")
expect(described_class.new).to be_versioned
expect(described_class.inheritance_column).to eq("species")
end
describe "#descends_from_active_record?" do
@ -15,14 +15,14 @@ RSpec.describe Plant, type: :model, versioning: true do
end
it "works with non standard STI column contents" do
plant = Plant.create
plant = described_class.create
plant.destroy
tomato = Tomato.create
tomato.destroy
reified = plant.versions.last.reify
expect(reified.class).to eq(Plant)
expect(reified.class).to eq(described_class)
reified = tomato.versions.last.reify
expect(reified.class).to eq(Tomato)

View File

@ -5,7 +5,7 @@ require "spec_helper"
# The `Post` model uses a custom version class, `PostVersion`
RSpec.describe Post, type: :model, versioning: true do
it "inserts records into the correct table, post_versions" do
post = Post.create
post = described_class.create
expect(PostVersion.count).to(eq(1))
post.update(content: "Some new content")
expect(PostVersion.count).to(eq(2))
@ -14,20 +14,20 @@ RSpec.describe Post, type: :model, versioning: true do
context "with the first version" do
it "have the correct index" do
post = Post.create
post = described_class.create
version = post.versions.first
expect(version.index).to(eq(0))
end
end
it "have versions of the custom class" do
post = Post.create
post = described_class.create
expect(post.versions.first.class.name).to(eq("PostVersion"))
end
describe "#changeset" 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")
expect(post.versions.last.changeset).to(be_nil)
end

View File

@ -11,7 +11,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
let(:t2) { Time.zone.local(2015, 7, 15, 20, 34, 30) }
it "does not create a version" do
skipper = Skipper.create!(another_timestamp: t1)
skipper = described_class.create!(another_timestamp: t1)
expect {
skipper.update!(another_timestamp: t2)
}.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")
it "does not create a version for skipped attributes" do
skipper = Skipper.create!(another_timestamp: t1)
skipper = described_class.create!(another_timestamp: t1)
expect {
skipper.touch(:another_timestamp, time: t2)
}.not_to(change { skipper.versions.length })
end
it "does not create a version for ignored attributes" do
skipper = Skipper.create!(created_at: t1)
skipper = described_class.create!(created_at: t1)
expect {
skipper.touch(:created_at, time: t2)
}.not_to(change { skipper.versions.length })
end
else
it "creates a version even for skipped attributes" do
skipper = Skipper.create!(another_timestamp: t1)
skipper = described_class.create!(another_timestamp: t1)
expect {
skipper.touch(:another_timestamp, time: t2)
}.to(change { skipper.versions.length })
end
it "creates a version even for ignored attributes" do
skipper = Skipper.create!(created_at: t1)
skipper = described_class.create!(created_at: t1)
expect {
skipper.touch(:created_at, time: t2)
}.to(change { skipper.versions.length })
@ -54,7 +54,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
end
it "creates a version for non-skipped timestamps" do
skipper = Skipper.create!
skipper = described_class.create!
expect {
skipper.touch
}.to(change { skipper.versions.length })
@ -67,7 +67,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
context "without preserve (default)" 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 = skipper.versions.last.reify
expect(skipper.another_timestamp).to be(nil)
@ -76,7 +76,7 @@ RSpec.describe Skipper, type: :model, versioning: true do
context "with preserve" 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 = skipper.versions.last.reify(unversioned_attributes: :preserve)
expect(skipper.another_timestamp).to eq(t2)

View File

@ -4,10 +4,10 @@ require "spec_helper"
RSpec.describe Thing, type: :model do
describe "#versions", versioning: true do
let(:thing) { Thing.create! }
let(:thing) { described_class.create! }
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"
end

View File

@ -24,6 +24,14 @@ RSpec.describe Translation, type: :model, versioning: true do
expect(PaperTrail::Version.count).to(eq(0))
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
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
translation = described_class.new(headline: "Headline")
translation.language_code = "US"
translation.type = "DRAFT"
translation.draft_status = "DRAFT"
translation.save!
expect(PaperTrail::Version.count).to(eq(0))
end
@ -39,11 +47,20 @@ RSpec.describe Translation, type: :model, versioning: true do
it "update does not change the number of versions" do
translation = described_class.new(headline: "Headline")
translation.language_code = "US"
translation.type = "DRAFT"
translation.draft_status = "DRAFT"
translation.save!
translation.update(content: "Content")
expect(PaperTrail::Version.count).to(eq(0))
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
context "with non-drafts" do
@ -52,14 +69,21 @@ RSpec.describe Translation, type: :model, versioning: true do
expect(PaperTrail::Version.count).to(eq(1))
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.update(content: "Content")
expect(PaperTrail::Version.count).to(eq(2))
expect(translation.versions.size).to(eq(2))
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.language_code = "US"
translation.save!

View File

@ -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

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require "spec_helper"
require "support/shared_examples/queries"
module PaperTrail
::RSpec.describe Version, type: :model do
@ -60,7 +61,7 @@ module PaperTrail
describe "#paper_trail_originator" do
context "with no previous versions" 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
@ -78,7 +79,7 @@ module PaperTrail
describe "#previous" do
context "with no previous versions" do
it "returns nil" do
expect(PaperTrail::Version.new.previous).to be_nil
expect(described_class.new.previous).to be_nil
end
end
@ -88,7 +89,7 @@ module PaperTrail
widget = Widget.create!(name: FFaker::Name.name)
widget.versions.first.update!(whodunnit: 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
@ -96,442 +97,39 @@ module PaperTrail
describe "#terminator" do
it "is an alias for the `whodunnit` attribute" do
attributes = { whodunnit: FFaker::Name.first_name }
version = PaperTrail::Version.new(attributes)
version = described_class.new(attributes)
expect(version.terminator).to eq(attributes[:whodunnit])
end
end
describe "#version_author" 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))
end
end
context "when changing the data type of database columns on the fly" do
# TODO: Changing the data type of these database columns in the middle
# of the test suite adds a fair amount of complexity. Is there a better
# 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"
column_overrides += %w[json jsonb]
context "with text columns", versioning: true do
include_examples "queries", :text, ::Widget, :an_integer
end
if ENV["DB"] == "postgres"
context "with json columns", versioning: true do
include_examples(
"queries",
:json,
::Fruit, # uses JsonVersion
:mass
)
end
column_overrides.shuffle.each do |column_datatype_override|
context "with a #{column_datatype_override || 'text'} column" do
let(:widget) { Widget.new }
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
PaperTrail::Version.reset_column_information
end
end
after do
PaperTrail.serializer = PaperTrail::Serializers::YAML
if column_datatype_override
ActiveRecord::Base.connection.execute("ROLLBACK TO SAVEPOINT pgtest;")
PaperTrail::Version.reset_column_information
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
# 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
context "with jsonb columns", versioning: true do
include_examples(
"queries",
:jsonb,
::Vegetable, # uses JsonbVersion
:mass
)
end
end
end

View File

@ -6,7 +6,7 @@ require "support/performance_helpers"
RSpec.describe Widget, type: :model, versioning: true do
describe "#changeset" do
it "has expected values" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
changeset = widget.versions.last.changeset
expect(changeset["name"]).to eq([nil, "Henry"])
expect(changeset["id"]).to eq([nil, widget.id])
@ -24,7 +24,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
it "calls the adapter's load_changeset method" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
adapter = instance_spy("CustomObjectChangesAdapter")
PaperTrail.config.object_changes_adapter = adapter
allow(adapter).to(
@ -39,7 +39,7 @@ RSpec.describe Widget, type: :model, versioning: true do
it "defaults to the original behavior" do
adapter = Class.new.new
PaperTrail.config.object_changes_adapter = adapter
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
changeset = widget.versions.last.changeset
expect(changeset[:name]).to eq([nil, "Henry"])
end
@ -48,44 +48,44 @@ RSpec.describe Widget, type: :model, versioning: true do
context "with a new record" do
it "not have any previous versions" do
expect(Widget.new.versions).to(eq([]))
expect(described_class.new.versions).to(eq([]))
end
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
context "with a persisted record" 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))
end
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.reify).to(be_nil)
end
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))
end
it "be live" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
expect(widget.paper_trail.live?).to(eq(true))
end
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))
end
context "when updated without any changes" do
it "to have two previous versions" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.touch
expect(widget.versions.length).to eq(2)
end
@ -93,13 +93,13 @@ RSpec.describe Widget, type: :model, versioning: true do
context "when updated with changes" do
it "have three previous versions" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
expect(widget.versions.length).to(eq(2))
end
it "be available in its previous version" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
expect(widget.name).to(eq("Harry"))
expect(widget.versions.last.object).not_to(be_nil)
@ -109,19 +109,19 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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")
expect(widget.versions.last.reify.id).to(eq(widget.id))
end
it "record the correct event" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
expect(widget.versions.last.event).to(match(/update/i))
end
it "have versions that are not live" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
widget.versions.map(&:reify).compact.each do |v|
expect(v.paper_trail).not_to be_live
@ -129,7 +129,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
it "have stored changes" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
last_obj_changes = widget.versions.last.object_changes
actual = PaperTrail.serializer.load(last_obj_changes).reject do |k, _v|
@ -141,7 +141,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
it "return changes with indifferent access" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
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]))
@ -150,7 +150,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "when updated, and has one associated object" 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")
wotsit = widget.create_wotsit name: "John"
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
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.fluxors.create(name: "f-zero")
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
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.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
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 = Whatchamajigger.new(name: "f-zero")
widget.save!
@ -198,21 +198,21 @@ RSpec.describe Widget, type: :model, versioning: true do
context "when updated, and then destroyed" do
it "record the correct event" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
widget.destroy
expect(PaperTrail::Version.last.event).to(match(/destroy/i))
end
it "have three previous versions" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
widget.destroy
expect(PaperTrail::Version.with_item_keys("Widget", widget.id).length).to(eq(3))
end
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.destroy
reified_widget = PaperTrail::Version.last.reify
@ -239,7 +239,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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.destroy
reified_widget = PaperTrail::Version.last.reify
@ -247,7 +247,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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.fluxors.create(name: "flux")
widget.destroy
@ -257,9 +257,11 @@ RSpec.describe Widget, type: :model, versioning: true do
end
it "have nil item for last version" do
widget = Widget.create(name: "Henry")
widget = described_class.create(name: "Henry")
widget.update(name: "Harry")
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
end
end
@ -270,7 +272,7 @@ RSpec.describe Widget, type: :model, versioning: true do
let!(:t0) { Time.current }
let(:previous_widget) { widget.versions.last.reify }
let(:widget) {
Widget.create(
described_class.create(
name: "Warble",
a_text: "The quick brown fox",
an_integer: 42,
@ -338,7 +340,7 @@ RSpec.describe Widget, type: :model, versioning: true do
let(:last_version) { widget.versions.last }
it "reify previous version" do
assert_kind_of(Widget, last_version.reify)
assert_kind_of(described_class, last_version.reify)
end
it "restore all forward-compatible attributes" do
@ -362,7 +364,7 @@ RSpec.describe Widget, type: :model, versioning: true do
after { PaperTrail.enabled = true }
it "not add to its trail" do
widget = Widget.create(name: "Zaphod")
widget = described_class.create(name: "Zaphod")
PaperTrail.enabled = false
count = widget.versions.length
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
after do
PaperTrail.request.enable_model(Widget)
PaperTrail.request.enable_model(described_class)
end
it "not add to its trail" do
widget = Widget.create(name: "Zaphod")
PaperTrail.request.disable_model(Widget)
widget = described_class.create(name: "Zaphod")
PaperTrail.request.disable_model(described_class)
count = widget.versions.length
widget.update(name: "Beeblebrox")
expect(widget.versions.length).to(eq(count))
end
it "add to its trail" do
widget = Widget.create(name: "Zaphod")
PaperTrail.request.disable_model(Widget)
widget = described_class.create(name: "Zaphod")
PaperTrail.request.disable_model(described_class)
count = widget.versions.length
widget.update(name: "Beeblebrox")
PaperTrail.request.enable_model(Widget)
PaperTrail.request.enable_model(described_class)
widget.update(name: "Ford")
expect(widget.versions.length).to(eq((count + 1)))
end
@ -398,7 +400,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "with somebody making changes" do
context "when a record is created" do
it "tracks who made the change" do
widget = Widget.new(name: "Fidget")
widget = described_class.new(name: "Fidget")
PaperTrail.request.whodunnit = "Alice"
widget.save
version = widget.versions.last
@ -411,7 +413,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "when created, then updated" do
it "tracks who made the change" do
widget = Widget.new(name: "Fidget")
widget = described_class.new(name: "Fidget")
PaperTrail.request.whodunnit = "Alice"
widget.save
PaperTrail.request.whodunnit = "Bob"
@ -426,7 +428,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "when created, updated, and destroyed" do
it "tracks who made the change" do
widget = Widget.new(name: "Fidget")
widget = described_class.new(name: "Fidget")
PaperTrail.request.whodunnit = "Alice"
widget.save
PaperTrail.request.whodunnit = "Bob"
@ -444,7 +446,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "with an item with versions" 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(:t1) { 1.day.ago }
let(:t2) { 1.hour.ago }
@ -501,7 +503,7 @@ RSpec.describe Widget, type: :model, versioning: true do
describe ".versions_between" 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: "Digit")
widget.versions[0].update(created_at: 30.days.ago)
@ -524,11 +526,11 @@ RSpec.describe Widget, type: :model, versioning: true do
end
context "with the first version" do
let(:widget) { Widget.create(name: "Widget") }
let(:widget) { described_class.create(name: "Widget") }
let(:version) { widget.versions.last }
before do
widget = Widget.create(name: "Widget")
widget = described_class.create(name: "Widget")
widget.update(name: "Fidget")
widget.update(name: "Digit")
end
@ -547,7 +549,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
context "with the last version" do
let(:widget) { Widget.create(name: "Widget") }
let(:widget) { described_class.create(name: "Widget") }
let(:version) { widget.versions.last }
before do
@ -571,7 +573,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "with a reified item" 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|
widget.update(name: name)
end
@ -585,7 +587,7 @@ RSpec.describe Widget, type: :model, versioning: true do
describe "#next_version" do
context "with a reified item" 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|
widget.update(name: name)
end
@ -598,7 +600,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "with a non-reified item" 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)
widget.save
%w[Tom Dick Jane].each do |name|
@ -612,7 +614,7 @@ RSpec.describe Widget, type: :model, versioning: true do
describe "#previous_version" do
context "with a reified item" 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|
widget.update(name: name)
end
@ -625,7 +627,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "with a non-reified item" 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)
widget.save
%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
it "not have a version created on destroy" do
widget = Widget.new
widget = described_class.new
widget.destroy
expect(widget.versions.empty?).to(eq(true))
end
@ -646,7 +648,7 @@ RSpec.describe Widget, type: :model, versioning: true do
context "when measuring the memory allocation of" do
let(:widget) do
Widget.new(
described_class.new(
name: "Warble",
a_text: "The quick brown fox",
an_integer: 42,
@ -725,7 +727,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
widget.update!(name: "Leonard", an_integer: 1)
@ -743,21 +745,21 @@ RSpec.describe Widget, type: :model, versioning: true do
describe "versioning option" do
context "when enabled", versioning: true 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)
end
end
context "when disabled", versioning: false 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)
end
end
end
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
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
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
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
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
it "sorts by the timestamp order from the `VersionConcern`" do
@ -844,19 +846,19 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
wordget = Widget.create
wordget = described_class.create
assert_equal 1, wordget.versions.length
end
end
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
widget = Widget.create
widget = described_class.create
assert_equal 1, widget.versions.length
widget.destroy
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`.
PaperTrail::Version.with_item_keys("Widget", widget.id)
}
widget = Widget.create
widget = described_class.create
assert_equal 1, widget.versions.length
widget.destroy
assert_equal 2, versions.call(widget).length
@ -883,7 +885,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
let(:orig_name) { FFaker::Name.name }
@ -923,7 +925,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
it "returns nil" do
@ -935,7 +937,7 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
expect { widget.touch }.to change {
@ -953,10 +955,10 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
widget = Widget.create
widget = described_class.create
expect(widget.versions.count).to eq(1)
widget.paper_trail.update_columns(name: "Bugle")
expect(widget.versions.count).to eq(2)
@ -966,10 +968,10 @@ RSpec.describe Widget, type: :model, versioning: true do
end
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
widget = Widget.create
widget = described_class.create
assert_equal 1, widget.versions.length
widget.update(name: "Bugle")
assert_equal 2, widget.versions.length

View File

@ -4,7 +4,7 @@ require "spec_helper"
RSpec.describe Wotsit, versioning: true do
it "update! records timestamps" do
wotsit = Wotsit.create!(name: "wotsit")
wotsit = described_class.create!(name: "wotsit")
wotsit.update!(name: "changed")
reified = wotsit.versions.last.reify
expect(reified.created_at).not_to(be_nil)
@ -12,7 +12,7 @@ RSpec.describe Wotsit, versioning: true do
end
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)
end
end

View File

@ -14,7 +14,7 @@ module PaperTrail
context "when incompatible" 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 {
described_class.check_activerecord(ar_version)
}.to output(/not compatible/).to_stderr

View File

@ -9,7 +9,7 @@ module PaperTrail
context "with a new record" do
it "returns true" do
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)
end
end
@ -18,14 +18,14 @@ module PaperTrail
it "only acknowledges non-ignored attrs" do
gadget = Gadget.create!(created_at: Time.current)
gadget.name = "Wrench"
event = PaperTrail::Events::Base.new(gadget, false)
event = described_class.new(gadget, false)
expect(event.changed_notably?).to eq(true)
end
it "does not acknowledge ignored attr (brand)" do
gadget = Gadget.create!(created_at: Time.current)
gadget.brand = "Acme"
event = PaperTrail::Events::Base.new(gadget, false)
event = described_class.new(gadget, false)
expect(event.changed_notably?).to eq(false)
end
end
@ -35,7 +35,7 @@ module PaperTrail
gadget = Gadget.create!(created_at: Time.current)
gadget.name = "Wrench"
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)
end
@ -43,7 +43,7 @@ module PaperTrail
gadget = Gadget.create!(created_at: Time.current)
gadget.brand = "Acme"
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)
end
end
@ -53,7 +53,7 @@ module PaperTrail
it "returns a hash lacking the skipped attribute" do
# Skipper has_paper_trail(..., skip: [:another_timestamp])
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)
expect(attributes).not_to have_key("another_timestamp")
end

View File

@ -11,17 +11,21 @@ module PaperTrail
name: "Carter",
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_subtype]).to eq("Family::CelebrityFamily")
end
context "with skipper" do
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
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).to have_key("updated_at")
expect(object).to have_key("created_at")
@ -29,7 +33,11 @@ module PaperTrail
end
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["updated_at"][0]).to be_present
expect(changes["updated_at"][1]).to be_nil

View File

@ -13,7 +13,7 @@ module PaperTrail
path_to_stardom: "Mexican radio"
)
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(
<<~YAML
---
@ -32,7 +32,7 @@ module PaperTrail
path_to_stardom: "Mexican radio"
)
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
end
end

View File

@ -22,6 +22,19 @@ module PaperTrail
expect(described_class.load(hash.to_yaml)).to eq(hash)
expect(described_class.load(array.to_yaml)).to eq(array)
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
describe ".dump" do

View File

@ -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

View File

@ -7,7 +7,7 @@ require "simplecov"
SimpleCov.start do
add_filter "spec"
end
SimpleCov.minimum_coverage 92.4
SimpleCov.minimum_coverage(ENV["DB"] == "postgres" ? 97.3 : 92.4)
require "byebug"
require_relative "support/pt_arel_helpers"
@ -53,7 +53,7 @@ end
# in `dummy_app/config/*`. By consolidating it here,
#
# - 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.
# First, `config/boot.rb` would add gems to $LOAD_PATH.

View File

@ -18,7 +18,7 @@ class PaperTrailSpecMigrator
@migrations_path = dummy_app_migrations_dir
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,
# how many people use the programmatic interface? Most people probably
# just use rake. Maybe we're doing it wrong.

View File

@ -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