Skip to content

Commit

Permalink
add 'limit' option to has_paper_trail to override global PaperTrail.c…
Browse files Browse the repository at this point in the history
…onfig.version_limit setting (paper-trail-gem#1194)

* add 'limit' option to has_paper_trail allowing users to override the PaperTrail.config.version_limit value on a per-model basis. This feature requires the item_subtype column in the versions table.

* Suggestions to be squashed into PR 1194

* Squash: trim trailing whitespace

[ci skip]

* Squash: use item_subtype_column_present?
  • Loading branch information
enrico authored and aried3r committed Dec 14, 2020
1 parent abca35b commit baba6ef
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 3 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).

### Added

- None
- [#1194](/~https://github.com/paper-trail-gem/paper_trail/pull/1194) -
Added a 'limit' option to has_paper_trail, allowing models to override the
global `PaperTrail.config.version_limit` setting.

### Fixed

Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,30 @@ PaperTrail.config.version_limit = 3
PaperTrail.config.version_limit = nil
```

#### 2.e.1 Per-model limit

Models can override the global `PaperTrail.config.version_limit` setting.

Example:

```
# initializer
PaperTrail.config.version_limit = 10
# At most 10 versions
has_paper_trail
# At most 3 versions (2 updates, 1 create). Overrides global version_limit.
has_paper_trail limit: 2
# Infinite versions
has_paper_trail limit: nil
```

To use a per-model limit, your `versions` table must have an
`item_subtype` column. See [Section
4.b.1](/~https://github.com/paper-trail-gem/paper_trail#4b1-the-optional-item_subtype-column).

## 3. Working With Versions

### 3.a. Reverting And Undeleting A Model
Expand Down
16 changes: 16 additions & 0 deletions lib/paper_trail/model_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class ModelConfig
`abstract_class`. This is fine, but all application models must be
configured to use concrete (not abstract) version models.
STR
E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE = <<~STR.squish.freeze
To use PaperTrail's per-model limit in your %s model, you must have an
item_subtype column in your versions table. See documentation sections
2.e.1 Per-model limit, and 4.b.1 The optional item_subtype column.
STR
DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION = <<~STR.squish
Passing versions association name as `has_paper_trail versions: %{versions_name}`
is deprecated. Use `has_paper_trail versions: {name: %{versions_name}}` instead.
Expand Down Expand Up @@ -112,6 +117,7 @@ def setup(options = {})
@model_class.send :include, ::PaperTrail::Model::InstanceMethods
setup_options(options)
setup_associations(options)
check_presence_of_item_subtype_column(options)
@model_class.after_rollback { paper_trail.clear_rolled_back_versions }
setup_callbacks_from_options options[:on]
end
Expand Down Expand Up @@ -139,6 +145,16 @@ def cannot_record_after_destroy?
::ActiveRecord::Base.belongs_to_required_by_default
end

# Some options require the presence of the `item_subtype` column. Currently
# only `limit`, but in the future there may be others.
#
# @api private
def check_presence_of_item_subtype_column(options)
return unless options.key?(:limit)
return if version_class.item_subtype_column_present?
raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
end

def check_version_class_name(options)
# @api private - `version_class_name`
@model_class.class_attribute :version_class_name
Expand Down
1 change: 1 addition & 0 deletions lib/paper_trail/reifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def reify_attributes(model, version, attrs)
# this method returns the constant `Animal`. You can see this particular
# example in action in `spec/models/animal_spec.rb`.
#
# TODO: Duplication: similar `constantize` in VersionConcern#version_limit
def version_reification_class(version, attrs)
inheritance_column_name = version.item_type.constantize.inheritance_column
inher_col_value = attrs[inheritance_column_name]
Expand Down
22 changes: 21 additions & 1 deletion lib/paper_trail/version_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ module VersionConcern

# :nodoc:
module ClassMethods
def item_subtype_column_present?
column_names.include?("item_subtype")
end

def with_item_keys(item_type, item_id)
where item_type: item_type, item_id: item_id
end
Expand Down Expand Up @@ -329,13 +333,29 @@ def object_changes_deserialized
# Enforces the `version_limit`, if set. Default: no limit.
# @api private
def enforce_version_limit!
limit = PaperTrail.config.version_limit
limit = version_limit
return unless limit.is_a? Numeric
previous_versions = sibling_versions.not_creates.
order(self.class.timestamp_sort_order("asc"))
return unless previous_versions.size > limit
excess_versions = previous_versions - previous_versions.last(limit)
excess_versions.map(&:destroy)
end

# See docs section 2.e. Limiting the Number of Versions Created.
# The version limit can be global or per-model.
#
# @api private
#
# TODO: Duplication: similar `constantize` in Reifier#version_reification_class
def version_limit
if self.class.item_subtype_column_present?
klass = (item_subtype || item_type).constantize
if klass&.paper_trail_options&.key?(:limit)
return klass.paper_trail_options[:limit]
end
end
PaperTrail.config.version_limit
end
end
end
5 changes: 5 additions & 0 deletions spec/dummy_app/app/models/limited_bicycle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class LimitedBicycle < Vehicle
has_paper_trail limit: 3
end
5 changes: 5 additions & 0 deletions spec/dummy_app/app/models/unlimited_bicycle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class UnlimitedBicycle < Vehicle
has_paper_trail limit: nil
end
39 changes: 38 additions & 1 deletion spec/paper_trail/config_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require "securerandom"
require "spec_helper"

module PaperTrail
Expand Down Expand Up @@ -38,10 +39,46 @@ module PaperTrail
it "limits the number of versions to 3 (2 plus the created at event)" do
PaperTrail.config.version_limit = 2
widget = Widget.create!(name: "Henry")
6.times { widget.update_attribute(:name, FFaker::Lorem.word) }
6.times { widget.update_attribute(:name, SecureRandom.hex(8)) }
expect(widget.versions.first.event).to(eq("create"))
expect(widget.versions.size).to(eq(3))
end

it "overrides the general limits to 4 (3 plus the created at event)" do
PaperTrail.config.version_limit = 100
bike = LimitedBicycle.create!(name: "Limited Bike") # has_paper_trail limit: 3
10.times { bike.update_attribute(:name, SecureRandom.hex(8)) }
expect(bike.versions.first.event).to(eq("create"))
expect(bike.versions.size).to(eq(4))
end

it "overrides the general limits with unlimited versions for model" do
PaperTrail.config.version_limit = 3
bike = UnlimitedBicycle.create!(name: "Unlimited Bike") # has_paper_trail limit: nil
6.times { bike.update_attribute(:name, SecureRandom.hex(8)) }
expect(bike.versions.first.event).to(eq("create"))
expect(bike.versions.size).to eq(7)
end

it "is not enabled on non-papertrail STI base classes, but enabled on subclasses" do
PaperTrail.config.version_limit = 10
Vehicle.create!(name: "A Vehicle", type: "Vehicle")
limited_bike = LimitedBicycle.create!(name: "Limited")
limited_bike.name = "A new name"
limited_bike.save
assert_equal 2, limited_bike.versions.length
end

context "when item_subtype column is absent" do
it "uses global version_limit" do
PaperTrail.config.version_limit = 6
names = PaperTrail::Version.column_names - ["item_subtype"]
allow(PaperTrail::Version).to receive(:column_names).and_return(names)
bike = LimitedBicycle.create!(name: "My Bike") # has_paper_trail limit: 3
10.times { bike.update(name: SecureRandom.hex(8)) }
assert_equal 7, bike.versions.length
end
end
end
end
end
13 changes: 13 additions & 0 deletions spec/paper_trail/version_limit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ module PaperTrail
PaperTrail.config.version_limit = nil
end

it "cleans up old versions with limit specified in model" do
PaperTrail.config.version_limit = 10

# LimitedBicycle overrides the global version_limit
bike = LimitedBicycle.create(name: "Bike") # has_paper_trail limit: 3

15.times do |i|
bike.update(name: "Name #{i}")
end
expect(LimitedBicycle.find(bike.id).versions.count).to eq(4)
# 4 versions = 3 updates + 1 create.
end

it "cleans up old versions" do
PaperTrail.config.version_limit = 10
widget = Widget.create
Expand Down

0 comments on commit baba6ef

Please sign in to comment.