Merge pull request #1367 from paper-trail-gem/release-12.2.0
Release 12.2.0
This commit is contained in:
commit
f21e9a4368
|
@ -32,11 +32,11 @@ require "bundler/inline"
|
|||
|
||||
# STEP ONE: What versions are you using?
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
42
.rubocop.yml
42
.rubocop.yml
|
@ -14,25 +14,24 @@ inherit_from: .rubocop_todo.yml
|
|||
# - Only include permanent config; temporary goes in .rubocop_todo.yml
|
||||
|
||||
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -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
|
||||
|
|
23
README.md
23
README.md
|
@ -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
|
||||
```
|
||||
|
||||
|
|
13
Rakefile
13
Rakefile
|
@ -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
|
||||
|
|
|
@ -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: "../"
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,4 +2,6 @@
|
|||
|
||||
class Car < Vehicle
|
||||
has_paper_trail
|
||||
attribute :color, type: ActiveModel::Type::String
|
||||
attr_accessor :top_speed
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See also `Fruit` which uses `JsonVersion`.
|
||||
class Vegetable < ActiveRecord::Base
|
||||
has_paper_trail versions: {
|
||||
class_name: ENV["DB"] == "postgres" ? "JsonbVersion" : "PaperTrail::Version"
|
||||
}, on: %i[create update]
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class JsonbVersion < ActiveRecord::Base
|
||||
include PaperTrail::VersionConcern
|
||||
|
||||
self.table_name = "jsonb_versions"
|
||||
end
|
|
@ -128,16 +128,19 @@ class SetUpTestTables < ::ActiveRecord::Migration::Current
|
|||
add_index :no_object_versions, %i[item_type item_id]
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
require "support/performance_helpers"
|
||||
|
||||
if ENV["DB"] == "postgres" && JsonbVersion.table_exists?
|
||||
::RSpec.describe Vegetable do
|
||||
describe "queries of versions", versioning: true do
|
||||
let!(:vegetable) { described_class.create(name: "Veggie", mass: 1, color: "green") }
|
||||
|
||||
before do
|
||||
vegetable.update(name: "Fidget")
|
||||
vegetable.update(name: "Digit")
|
||||
described_class.create(name: "Cucumber")
|
||||
end
|
||||
|
||||
it "return the vegetable whose name has changed" do
|
||||
result = JsonbVersion.where_attribute_changes(:name).map(&:item)
|
||||
expect(result).to include(vegetable)
|
||||
end
|
||||
|
||||
it "returns the vegetable whose name was Fidget" do
|
||||
result = JsonbVersion.where_object_changes_from({ name: "Fidget" }).map(&:item)
|
||||
expect(result).to include(vegetable)
|
||||
end
|
||||
|
||||
it "returns the vegetable whose name became Digit" do
|
||||
result = JsonbVersion.where_object_changes_to({ name: "Digit" }).map(&:item)
|
||||
expect(result).to include(vegetable)
|
||||
end
|
||||
|
||||
it "returns the vegetable where the object was named Fidget before it changed" do
|
||||
result = JsonbVersion.where_object({ name: "Fidget" }).map(&:item)
|
||||
expect(result).to include(vegetable)
|
||||
end
|
||||
|
||||
it "returns the vegetable that changed to Fidget" do
|
||||
result = JsonbVersion.where_object_changes({ name: "Fidget" }).map(&:item)
|
||||
expect(result).to include(vegetable)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
module PaperTrail
|
||||
module TypeSerializers
|
||||
::RSpec.describe PostgresArraySerializer do
|
||||
let(:word_array) { [].fill(0, rand(4..8)) { ::FFaker::Lorem.word } }
|
||||
let(:word_array_as_string) { word_array.join("|") }
|
||||
|
||||
let(:the_thing) { described_class.new("foo", "bar") }
|
||||
|
||||
describe ".deserialize" do
|
||||
it "deserializes array to Ruby" do
|
||||
expect(the_thing.deserialize(word_array)).to eq(word_array)
|
||||
end
|
||||
|
||||
it "deserializes string to Ruby array" do
|
||||
allow(the_thing).to receive(:deserialize_with_ar).and_return(word_array)
|
||||
expect(the_thing.deserialize(word_array_as_string)).to eq(word_array)
|
||||
expect(the_thing).to have_received(:deserialize_with_ar)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".dump" do
|
||||
it "serializes Ruby to JSON" do
|
||||
expect(the_thing.serialize(word_array)).to eq(word_array)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,7 +7,7 @@ require "simplecov"
|
|||
SimpleCov.start do
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,388 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples "queries" do |column_type, model, name_of_integer_column|
|
||||
let(:record) { model.new }
|
||||
let(:name) { FFaker::Name.first_name }
|
||||
let(:int) { column_type == :text ? 1 : rand(2..6) }
|
||||
|
||||
after do
|
||||
PaperTrail.serializer = PaperTrail::Serializers::YAML
|
||||
end
|
||||
|
||||
describe "#where_attribute_changes", versioning: true do
|
||||
it "requires its argument to be a string or a symbol" do
|
||||
expect {
|
||||
model.paper_trail.version_class.where_attribute_changes({})
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
model.paper_trail.version_class.where_attribute_changes([])
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context "with object_changes_adapter configured" do
|
||||
after do
|
||||
PaperTrail.config.object_changes_adapter = nil
|
||||
end
|
||||
|
||||
it "calls the adapter's where_attribute_changes method" do
|
||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||
bicycle = model.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
allow(adapter).to(
|
||||
receive(:where_attribute_changes).with(model.paper_trail.version_class, :name)
|
||||
).and_return([bicycle.versions[0], bicycle.versions[1]])
|
||||
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
expect(
|
||||
bicycle.versions.where_attribute_changes(:name)
|
||||
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
||||
expect(adapter).to have_received(:where_attribute_changes)
|
||||
end
|
||||
|
||||
it "defaults to the original behavior" do
|
||||
adapter = Class.new.new
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
bicycle = model.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
if column_type == :text
|
||||
expect {
|
||||
bicycle.versions.where_attribute_changes(:name)
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_attribute_changes expected json or jsonb column, got text"
|
||||
)
|
||||
else
|
||||
expect(
|
||||
bicycle.versions.where_attribute_changes(:name)
|
||||
).to match_array([bicycle.versions[0], bicycle.versions[1]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if column_type == :text
|
||||
it "raises error" do
|
||||
expect {
|
||||
record.versions.where_attribute_changes(:name).to_a
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_attribute_changes expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
else
|
||||
it "locates versions according to their object_changes contents" do
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name_of_integer_column => 17)
|
||||
|
||||
expect(
|
||||
record.versions.where_attribute_changes(:name)
|
||||
).to eq([record.versions[0]])
|
||||
expect(
|
||||
record.versions.where_attribute_changes(name_of_integer_column.to_s)
|
||||
).to eq([record.versions[0], record.versions[1]])
|
||||
expect(record.class.column_names).to include("color")
|
||||
expect(
|
||||
record.versions.where_attribute_changes(:color)
|
||||
).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#where_object", versioning: true do
|
||||
it "requires its argument to be a Hash" do
|
||||
record.update!(name: name, name_of_integer_column => int)
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name: FFaker::Name.last_name, name_of_integer_column => 15)
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object(:foo)
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object([])
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context "with YAML serializer" do
|
||||
it "locates versions according to their `object` contents" do
|
||||
expect(PaperTrail.serializer).to be PaperTrail::Serializers::YAML
|
||||
record.update!(name: name, name_of_integer_column => int)
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name: FFaker::Name.last_name, name_of_integer_column => 15)
|
||||
expect(
|
||||
model.paper_trail.version_class.where_object(name_of_integer_column => int)
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
model.paper_trail.version_class.where_object(name: name)
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
model.paper_trail.version_class.where_object(name_of_integer_column => 100)
|
||||
).to eq([record.versions[2]])
|
||||
end
|
||||
end
|
||||
|
||||
context "with JSON serializer" do
|
||||
it "locates versions according to their `object` contents" do
|
||||
PaperTrail.serializer = PaperTrail::Serializers::JSON
|
||||
expect(PaperTrail.serializer).to be PaperTrail::Serializers::JSON
|
||||
record.update!(name: name, name_of_integer_column => int)
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name: FFaker::Name.last_name, name_of_integer_column => 15)
|
||||
expect(
|
||||
model.paper_trail.version_class.where_object(name_of_integer_column => int)
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
model.paper_trail.version_class.where_object(name: name)
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
model.paper_trail.version_class.where_object(name_of_integer_column => 100)
|
||||
).to eq([record.versions[2]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#where_object_changes", versioning: true do
|
||||
it "requires its argument to be a Hash" do
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object_changes(:foo)
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object_changes([])
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context "with object_changes_adapter configured" do
|
||||
after do
|
||||
PaperTrail.config.object_changes_adapter = nil
|
||||
end
|
||||
|
||||
it "calls the adapter's where_object_changes method" do
|
||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||
bicycle = model.create!(name: "abc")
|
||||
allow(adapter).to(
|
||||
receive(:where_object_changes).with(model.paper_trail.version_class, name: "abc")
|
||||
).and_return(bicycle.versions[0..1])
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
expect(
|
||||
bicycle.versions.where_object_changes(name: "abc")
|
||||
).to match_array(bicycle.versions[0..1])
|
||||
expect(adapter).to have_received(:where_object_changes)
|
||||
end
|
||||
|
||||
it "defaults to the original behavior" do
|
||||
adapter = Class.new.new
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
bicycle = model.create!(name: "abc")
|
||||
if column_type == :text
|
||||
expect {
|
||||
bicycle.versions.where_object_changes(name: "abc")
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_object_changes expected json or jsonb column, got text"
|
||||
)
|
||||
else
|
||||
expect(
|
||||
bicycle.versions.where_object_changes(name: "abc")
|
||||
).to match_array(bicycle.versions[0..1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if column_type == :text
|
||||
it "raises error" do
|
||||
expect {
|
||||
record.versions.where_object_changes(name: "foo").to_a
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_object_changes expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
else
|
||||
it "locates versions according to their object_changes contents" do
|
||||
record.update!(name: name, name_of_integer_column => 0)
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name: FFaker::Name.last_name, name_of_integer_column => int)
|
||||
expect(
|
||||
record.versions.where_object_changes(name: name)
|
||||
).to eq(record.versions[0..1])
|
||||
expect(
|
||||
record.versions.where_object_changes(name_of_integer_column => 100)
|
||||
).to eq(record.versions[1..2])
|
||||
expect(
|
||||
record.versions.where_object_changes(name_of_integer_column => int)
|
||||
).to eq([record.versions.last])
|
||||
expect(
|
||||
record.versions.where_object_changes(name_of_integer_column => 100, name: "foobar")
|
||||
).to eq(record.versions[1..2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#where_object_changes_from", versioning: true do
|
||||
it "requires its argument to be a Hash" do
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object_changes_from(:foo)
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object_changes_from([])
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context "with object_changes_adapter configured" do
|
||||
after do
|
||||
PaperTrail.config.object_changes_adapter = nil
|
||||
end
|
||||
|
||||
it "calls the adapter's where_object_changes_from method" do
|
||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||
bicycle = model.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
allow(adapter).to(
|
||||
receive(:where_object_changes_from).with(model.paper_trail.version_class, name: "abc")
|
||||
).and_return([bicycle.versions[1]])
|
||||
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
expect(adapter).to have_received(:where_object_changes_from)
|
||||
end
|
||||
|
||||
it "defaults to the original behavior" do
|
||||
adapter = Class.new.new
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
bicycle = model.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
if column_type == :text
|
||||
expect {
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_object_changes_from expected json or jsonb column, got text"
|
||||
)
|
||||
else
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_from(name: "abc")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if column_type == :text
|
||||
it "raises error" do
|
||||
expect {
|
||||
record.versions.where_object_changes_from(name: "foo").to_a
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_object_changes_from expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
else
|
||||
it "locates versions according to their object_changes contents" do
|
||||
record.update!(name: name, name_of_integer_column => 0)
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name: FFaker::Name.last_name, name_of_integer_column => int)
|
||||
|
||||
expect(
|
||||
record.versions.where_object_changes_from(name: name)
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
record.versions.where_object_changes_from(name_of_integer_column => 100)
|
||||
).to eq([record.versions[2]])
|
||||
expect(
|
||||
record.versions.where_object_changes_from(name_of_integer_column => int)
|
||||
).to eq([])
|
||||
expect(
|
||||
record.versions.where_object_changes_from(name_of_integer_column => 100, name: "foobar")
|
||||
).to eq([record.versions[2]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#where_object_changes_to", versioning: true do
|
||||
it "requires its argument to be a Hash" do
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object_changes_to(:foo)
|
||||
}.to raise_error(ArgumentError)
|
||||
expect {
|
||||
model.paper_trail.version_class.where_object_changes_to([])
|
||||
}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context "with object_changes_adapter configured" do
|
||||
after do
|
||||
PaperTrail.config.object_changes_adapter = nil
|
||||
end
|
||||
|
||||
it "calls the adapter's where_object_changes_to method" do
|
||||
adapter = instance_spy("CustomObjectChangesAdapter")
|
||||
bicycle = model.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
allow(adapter).to(
|
||||
receive(:where_object_changes_to).with(model.paper_trail.version_class, name: "xyz")
|
||||
).and_return([bicycle.versions[1]])
|
||||
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
expect(adapter).to have_received(:where_object_changes_to)
|
||||
end
|
||||
|
||||
it "defaults to the original behavior" do
|
||||
adapter = Class.new.new
|
||||
PaperTrail.config.object_changes_adapter = adapter
|
||||
bicycle = model.create!(name: "abc")
|
||||
bicycle.update!(name: "xyz")
|
||||
|
||||
if column_type == :text
|
||||
expect {
|
||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_object_changes_to expected json or jsonb column, got text"
|
||||
)
|
||||
else
|
||||
expect(
|
||||
bicycle.versions.where_object_changes_to(name: "xyz")
|
||||
).to match_array([bicycle.versions[1]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if column_type == :text
|
||||
it "raises error" do
|
||||
expect {
|
||||
record.versions.where_object_changes_to(name: "foo").to_a
|
||||
}.to raise_error(
|
||||
::PaperTrail::UnsupportedColumnType,
|
||||
"where_object_changes_to expected json or jsonb column, got text"
|
||||
)
|
||||
end
|
||||
else
|
||||
it "locates versions according to their object_changes contents" do
|
||||
record.update!(name: name, name_of_integer_column => 0)
|
||||
record.update!(name: "foobar", name_of_integer_column => 100)
|
||||
record.update!(name: FFaker::Name.last_name, name_of_integer_column => int)
|
||||
|
||||
expect(
|
||||
record.versions.where_object_changes_to(name: name)
|
||||
).to eq([record.versions[0]])
|
||||
expect(
|
||||
record.versions.where_object_changes_to(name_of_integer_column => 100)
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
record.versions.where_object_changes_to(name_of_integer_column => int)
|
||||
).to eq([record.versions[2]])
|
||||
expect(
|
||||
record.versions.where_object_changes_to(name_of_integer_column => 100, name: "foobar")
|
||||
).to eq([record.versions[1]])
|
||||
expect(
|
||||
record.versions.where_object_changes_to(name_of_integer_column => -1)
|
||||
).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue