Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

256 track all blank changes #257

Merged
merged 11 commits into from
Aug 23, 2024
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Metrics/BlockLength:
# Offense count: 1
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 120
Max: 121

# Offense count: 6
Metrics/CyclomaticComplexity:
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### 0.8.6 (Next)
### 0.9.0 (Next)

* [#257](https://github.com/mongoid/mongoid-history/pull/257): Add track_blank_changes option - [@BrianLMatthews](https://github.com/BrianLMatthews).
* Your contribution here.
dblock marked this conversation as resolved.
Show resolved Hide resolved

### 0.8.5 (2021/09/18)
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ group :test do
gem 'request_store'
gem 'rspec', '~> 3.1'
gem 'rubocop', '~> 0.49.0'
gem 'term-ansicolor', '~> 1.3.0'
gem 'yard'
end
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ class Post
:version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version
:track_create => true, # track document creation, default is true
:track_update => true, # track document updates, default is true
:track_destroy => true # track document destruction, default is true
:track_destroy => true, # track document destruction, default is true
:track_blank_changes => false # track changes from blank? to blank?, default is false
end

class Comment
Expand Down Expand Up @@ -283,6 +284,32 @@ end

It will now track only `_id` (Mandatory), `title` and `content` attributes for `pages` relation.

### Track all blank changes

Normally changes where both the original and modified values respond with `true` to `blank?` (for example `nil` to `false`) aren't tracked. However, there may be cases where it's important to track such changes, for example when a field isn't present (so appears to be `nil`) then is set to `false`. To track such changes, set the `track_blank_changes` option to `true` (it defaults to `false`) when turning on history tracking:

```ruby
class Book
include Mongoid::Document
...
field :summary
track_history # Use default of false for track_blank_changes
end

# summary change not tracked if summary hasn't been set (or has been set to something that responds true to blank?)
Book.find(id).update_attributes(:summary => '')

class Chapter
include Mongoid::Document
...
field :title
track_history :track_blank_changes => true
end

# title change tracked even if title hasn't been set
Chapter.find(id).update_attributes(:title => '')
```

### Retrieving the list of tracked static and dynamic fields

```ruby
Expand Down Expand Up @@ -604,6 +631,6 @@ You're encouraged to contribute to Mongoid History. See [CONTRIBUTING.md](CONTRI

## Copyright

Copyright (c) 2011-2020 Aaron Qian and Contributors.
Copyright (c) 2011-2024 Aaron Qian and Contributors.

MIT License. See [LICENSE.txt](LICENSE.txt) for further details.
3 changes: 2 additions & 1 deletion lib/mongoid/history/attributes/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def attributes
private

def changes_from_parent
track_blank_changes = trackable_class.history_trackable_options[:track_blank_changes]
parent_changes = {}
changes.each do |k, v|
change_value = begin
Expand All @@ -26,7 +27,7 @@ def changes_from_parent
elsif trackable_class.tracked_embeds_many?(k)
embeds_many_changes_from_parent(k, v)
elsif trackable_class.tracked?(k, :update)
{ k => format_field(k, v) } unless v.all?(&:blank?)
{ k => format_field(k, v) } unless !track_blank_changes && v.all?(&:blank?)
end
end
parent_changes.merge!(change_value) if change_value.present?
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/history/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def default_options
track_create: true,
track_update: true,
track_destroy: true,
track_blank_changes: false,
format: nil }
end

Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/history/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Mongoid
module History
VERSION = '0.8.5'.freeze
VERSION = '0.9.0'.freeze
end
end
90 changes: 61 additions & 29 deletions spec/unit/attributes/update_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -305,37 +305,69 @@ class EmbOne
end
end

context 'when original and modified values blank' do
before :each do
class DummyParent
include Mongoid::Document
include Mongoid::History::Trackable
store_in collection: :dummy_parents
has_and_belongs_to_many :other_dummy_parents
track_history on: :fields
end

class OtherDummyParent
include Mongoid::Document
has_and_belongs_to_many :dummy_parents
end
end

before :each do
allow(base).to receive(:changes) { changes }
DummyParent.track_history on: :other_dummy_parent_ids
end
[false, true].each do |original_nil|
context "when original value #{original_nil ? 'nil' : 'blank'} and modified value #{original_nil ? 'blank' : 'nil'}" do
[nil, false, true].each do |track_blank_changes|
context "when track_blank_changes #{track_blank_changes.nil? ? 'default' : track_blank_changes}" do
before :each do
class DummyParent
include Mongoid::Document
include Mongoid::History::Trackable
store_in collection: :dummy_parents
has_and_belongs_to_many :other_dummy_parents
field :boolean, type: Boolean
field :string, type: String
field :hash, type: Hash
end

class OtherDummyParent
include Mongoid::Document
has_and_belongs_to_many :dummy_parents
end

if track_blank_changes.nil?
DummyParent.track_history on: :fields
else
DummyParent.track_history \
on: :fields,
track_blank_changes: track_blank_changes
end

allow(base).to receive(:changes) { changes }
end

let(:base) { described_class.new(DummyParent.new) }
let(:changes) do
{ 'other_dummy_parent_ids' => [nil, []] }
end
subject { base.attributes }
it { expect(subject.keys).to_not include 'other_dummy_parent_ids' }
after :each do
Object.send(:remove_const, :DummyParent)
Object.send(:remove_const, :OtherDummyParent)
end

after :each do
Object.send(:remove_const, :DummyParent)
Object.send(:remove_const, :OtherDummyParent)
let(:base) { described_class.new(DummyParent.new) }
subject { base.attributes.keys }

# These can't be memoizing methods (i.e. lets) because of limits
# on where those can be used.

cmp = track_blank_changes ? 'should' : 'should_not'
cmp_name = cmp.humanize capitalize: false

[
{ n: 'many-to-many', f: 'other_dummy_parent_ids', v: [] },
{ n: 'boolean', f: 'boolean', v: false },
{ n: 'empty string', f: 'string', v: '' },
{ n: 'all whitespace string', f: 'string', v: " \t\n\r\f\v" }
# The second character in that string is an actual tab (0x9).
].each do |d|
context "#{d[:n]} field" do
let(:changes) do
{ d[:f] => original_nil ? [nil, d[:v]] : [d[:v], nil] }
end
it "changes #{cmp_name} include #{d[:f]}" do
send(cmp, include(d[:f]))
end
end
end
end
end
end
end
end
Expand Down
7 changes: 7 additions & 0 deletions spec/unit/options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class EmbFour
track_create: true,
track_update: true,
track_destroy: true,
track_blank_changes: false,
format: nil
}
end
Expand Down Expand Up @@ -173,6 +174,7 @@ class EmbFour
track_create: true,
track_update: true,
track_destroy: true,
track_blank_changes: false,
fields: %w[foo b],
dynamic: [],
relations: { embeds_one: {}, embeds_many: {} },
Expand Down Expand Up @@ -354,6 +356,11 @@ class EmbFour
it { expect(subject[:track_destroy]).to be true }
end

describe ':track_blank_changes' do
let(:options) { { track_blank_changes: true } }
it { expect(subject[:track_blank_changes]).to be true }
end

describe '#remove_reserved_fields' do
let(:options) { { on: %i[_id _type foo version modifier_id] } }
it { expect(subject[:fields]).to eq %w[foo] }
Expand Down
1 change: 1 addition & 0 deletions spec/unit/trackable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class MyModelWithNoModifier
track_create: true,
track_update: true,
track_destroy: true,
track_blank_changes: false,
fields: %w[foo],
relations: { embeds_one: {}, embeds_many: {} },
dynamic: [],
Expand Down
Loading