Skip to content

Commit c8c4de1

Browse files
256 track all blank changes (#257)
* Add some tests for track_blank_changes option This is mostly a "get my feet wet" commit, but it adds some tests for the track_blank_changes option being present and having the right default. As I haven't added track_blank_changes as an option yet, these tests fail as expected. I did a rake with out-of-the-box code and saved the results in testresults/0-baseline.txt, after running them through this script: #!/usr/bin/env ruby until (line = gets).nil? line.gsub!(/_id: [[:xdigit:]]{24}/, '_id: <id>/') line.gsub!(/BSON::ObjectId\('[[:xdigit:]]{24}'\)/, '<id>') line.gsub!(/[\d.]+/, 'n') if line.start_with?('Finished in ') puts line end to get rid of _ids and the total run time, both of which change every run. I then ran rake after the changes in this commit and saved the output to testresults/1-track_blank_changes-default-tests.txt. I won't include the entire 197 line diff here, but the important part is: $ diff -u testresults/0-baseline.txt testresults/1-track_blank_changes-default-tests.txt | head --- testresults/0-baseline.txt 2024-08-05 11:17:55.000000000 -0700 +++ testresults/1-track_blank_changes-default-tests.txt 2024-08-05 11:21:01.000000000 -0700 [...] -432 examples, 0 failures, 2 pending +432 examples, 6 failures, 2 pending [...] Those 6 failures are all to be expected given that the track_blank_changes_option doesn't exist yet. There were no rubocop complaints. * Add some more all blank changes tests Originally there was a single test for all blank changes. This commit adds a number more. Now it tests: - Both [nil, <blank thing>] and [<blank thing>, nil] changes. - <blank thing> was originally just an empty Array. Now it can be: - [] - false - '' - A non-empty String consisting of all whitespace characters - {} - It tests with the track_blank_changes option the default, explicitly false, and explicitly true. The tests with track_blank_changes the default or explicitly false succeed, accidentally for now, as there is no such option and the code acts as if track_blank_changes is false, so tests that rely on that succeed. The tests that set track_blank_changes to true fail, as there's no such option and setting it has no effect on the operation of the code. Here's a diff of the test results with just the important parts, not all 422 lines: $ diff -u testresults/1-track_blank_changes-default-tests.txt testresults/2-addition-all-blank-changes.txt --- testresults/1-track_blank_changes-default-tests.txt 2024-08-05 11:21:01.000000000 -0700 +++ testresults/2-addition-all-blank-changes.txt 2024-08-05 11:34:14.000000000 -0700 -432 examples, 6 failures, 2 pending +455 examples, 14 failures, 2 pending The 6 failures are those introduced by commit 52cc180. The 8 additional failures are introduced in this commit. And, just to verify, I commented out the unless clause on lib/mongoid/history/attributes/update.rb:29, which makes track_blank_changes effectively always true, and the tests that don't set it or set it to false fail (as it's always implicitly true with the commented out unless), and the tests that set it to true succeed (the option isn't being set, there is no option, but the code acts as if it's true), all as I'd expect things to work. * Test setting track_blank_changes option There are a number of tests that setting an option value actually sets the option to that value, so this commit adds one for track_blank_changes. Unfortunately I added it before discovering that the whole section isn't testing anything other than that setting a value in a Hash and retrieving that value gives the same value back (and that two of tests even do that wrong). The two tests that are wrong are those for :track_create and :track_destroy. Because they're setting the options to the default value, it's not possible to tell if the tests pass because the values are actually being set or because they're not and just the default value is being used. More problematic is that adding a test like: describe ':any_name' do let(:options) { { any_name: 'any_value' } } it { expect(subject[:any_name]).to be 'any_value' } end passes, as long as any_name is something other than a few special option names (like on). None of the tests between lines 319 and 359 are really testing anything useful IMO. Nevertheless, I added the :track_blank_changes test. Here's the diff of the test output vs that generated after commit 8081c34: $ diff -u testresults/2-additional-all-blank-changes.txt testresults/3-set-track_blank_changes.txt --- testresults/2-additional-all-blank-changes.txt 2024-08-05 11:34:14.000000000 -0700 +++ testresults/3-set-track_blank_changes.txt 2024-08-05 15:46:06.000000000 -0700 @@ -591,6 +591,8 @@ is expected to equal false :track_destroy is expected to equal true + :track_blank_changes + is expected to equal true #remove_reserved_fields is expected to eq ["foo"] is expected to eq [] @@ -1076,7 +1078,7 @@ # /Users/blm/.rvm/gems/ruby-2.3.7/gems/rspec-core-3.13.0/exe/rspec:4:in `<main>' Finished in n minute n seconds (files took n seconds to load) -455 examples, 14 failures, 2 pending +456 examples, 14 failures, 2 pending Failed examples: * Add track_blank_changes as default option Time to start fixing some test failures instead of creating them. Add track_blank_changes as a default option, defaulting to false. $ diff -u testresults/3-set-track_blank_changes.txt testresults/4-add-track_blank_changes-option.txt --- testresults/3-set-track_blank_changes.txt 2024-08-05 15:46:06.000000000 -0700 +++ testresults/4-add-track_blank_changes-option.txt 2024-08-05 19:34:48.000000000 -0700 @@ -504,7 +504,7 @@ [...] -456 examples, 14 failures, 2 pending +456 examples, 8 failures, 2 pending [...] The failures fixed are exactly the 6 introduced in commit 52cc180. * Use track_blank_changes Use the track_blank_changes option. If it's true, any change reported by the change method is tracked, even if both the original and new values are blank (i.e. respond with true to blank?). The tests now all run successfully (and there are no rubocopy complaints, although I did increase the maximum class size in commit d3275e6 as Mongoid::History::Options was exactly at the class size limit). Here are the complete differences between the initial test output and now: $ diff -u testresults/0-baseline.txt testresults/5-use-track_blank_changes-option.txt --- testresults/0-baseline.txt 2024-08-05 11:17:55.000000000 -0700 +++ testresults/5-use-track_blank_changes-option.txt 2024-08-05 20:00:33.000000000 -0700 @@ -320,8 +320,62 @@ should save audit history under relation alias when original and modified value same is expected not to include "emb_ones" - when original and modified values blank - is expected not to include "other_dummy_parent_ids" + when original value blank and modified value nil + when track_blank_changes default + many-to-many field + changes should not include other_dummy_parent_ids + boolean field + changes should not include boolean + empty string field + changes should not include string + all whitespace string field + changes should not include string + when track_blank_changes false + many-to-many field + changes should not include other_dummy_parent_ids + boolean field + changes should not include boolean + empty string field + changes should not include string + all whitespace string field + changes should not include string + when track_blank_changes true + many-to-many field + changes should include other_dummy_parent_ids + boolean field + changes should include boolean + empty string field + changes should include string + all whitespace string field + changes should include string + when original value nil and modified value blank + when track_blank_changes default + many-to-many field + changes should not include other_dummy_parent_ids + boolean field + changes should not include boolean + empty string field + changes should not include string + all whitespace string field + changes should not include string + when track_blank_changes false + many-to-many field + changes should not include other_dummy_parent_ids + boolean field + changes should not include boolean + empty string field + changes should not include string + all whitespace string field + changes should not include string + when track_blank_changes true + many-to-many field + changes should include other_dummy_parent_ids + boolean field + changes should include boolean + empty string field + changes should include string + all whitespace string field + changes should include string Mongoid::History::Options :if @@ -537,6 +591,8 @@ is expected to equal false :track_destroy is expected to equal true + :track_blank_changes + is expected to equal true #remove_reserved_fields is expected to eq ["foo"] is expected to eq [] @@ -824,6 +880,6 @@ # ./spec/unit/attributes/create_spec.rb:340 Finished in n minute n seconds (files took n seconds to load) -432 examples, 0 failures, 2 pending +456 examples, 0 failures, 2 pending [Coveralls] Outside the CI environment, not sending data. 24 new tests, including testing when track_blank_changes is true, and still 0 failures. * Update CHANGLOG * Update version to 0.9.0 per request * Update the internal version number as well * Attempt to work around CI failures After exploring the failures locally, it appears that term-ansicolor is the culprit. Version 1.10.0 introduced an undocumented (as far as I can tell, but its CHANGES file stops at 1.7.1 :-( ) dependency on ruby 2.5, by using Hash#slice. So use 1.9.0 you say! Except 1.9.0 doesn't work at all (it's trying to call start_with? on a Symbol). I tried all the commits between 1.9.0 and 1.10.0 and all had some problem or another. Luckily, the latest version needed is 1.3.x (by coveralls), so if I specify that here, that's the one that's installed and bundle rake exec runs fine, so hopefully this will fix the CI failures. We'll see... * Update per request * Update per request
1 parent f01173b commit c8c4de1

File tree

10 files changed

+106
-35
lines changed

10 files changed

+106
-35
lines changed

Diff for: .rubocop_todo.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Metrics/BlockLength:
3636
# Offense count: 1
3737
# Configuration parameters: CountComments.
3838
Metrics/ClassLength:
39-
Max: 120
39+
Max: 121
4040

4141
# Offense count: 6
4242
Metrics/CyclomaticComplexity:

Diff for: CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
### 0.8.6 (Next)
1+
### 0.9.0 (Next)
22

3+
* [#257](https://github.com/mongoid/mongoid-history/pull/257): Add track_blank_changes option - [@BrianLMatthews](https://github.com/BrianLMatthews).
34
* Your contribution here.
45

56
### 0.8.5 (2021/09/18)

Diff for: Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ group :test do
4545
gem 'request_store'
4646
gem 'rspec', '~> 3.1'
4747
gem 'rubocop', '~> 0.49.0'
48+
gem 'term-ansicolor', '~> 1.3.0'
4849
gem 'yard'
4950
end

Diff for: README.md

+29-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ class Post
7575
:version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version
7676
:track_create => true, # track document creation, default is true
7777
:track_update => true, # track document updates, default is true
78-
:track_destroy => true # track document destruction, default is true
78+
:track_destroy => true, # track document destruction, default is true
79+
:track_blank_changes => false # track changes from blank? to blank?, default is false
7980
end
8081

8182
class Comment
@@ -283,6 +284,32 @@ end
283284

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

287+
### Track all blank changes
288+
289+
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:
290+
291+
```ruby
292+
class Book
293+
include Mongoid::Document
294+
...
295+
field :summary
296+
track_history # Use default of false for track_blank_changes
297+
end
298+
299+
# summary change not tracked if summary hasn't been set (or has been set to something that responds true to blank?)
300+
Book.find(id).update_attributes(:summary => '')
301+
302+
class Chapter
303+
include Mongoid::Document
304+
...
305+
field :title
306+
track_history :track_blank_changes => true
307+
end
308+
309+
# title change tracked even if title hasn't been set
310+
Chapter.find(id).update_attributes(:title => '')
311+
```
312+
286313
### Retrieving the list of tracked static and dynamic fields
287314

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

605632
## Copyright
606633

607-
Copyright (c) 2011-2020 Aaron Qian and Contributors.
634+
Copyright (c) 2011-2024 Aaron Qian and Contributors.
608635

609636
MIT License. See [LICENSE.txt](LICENSE.txt) for further details.

Diff for: lib/mongoid/history/attributes/update.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def attributes
1818
private
1919

2020
def changes_from_parent
21+
track_blank_changes = trackable_class.history_trackable_options[:track_blank_changes]
2122
parent_changes = {}
2223
changes.each do |k, v|
2324
change_value = begin
@@ -26,7 +27,7 @@ def changes_from_parent
2627
elsif trackable_class.tracked_embeds_many?(k)
2728
embeds_many_changes_from_parent(k, v)
2829
elsif trackable_class.tracked?(k, :update)
29-
{ k => format_field(k, v) } unless v.all?(&:blank?)
30+
{ k => format_field(k, v) } unless !track_blank_changes && v.all?(&:blank?)
3031
end
3132
end
3233
parent_changes.merge!(change_value) if change_value.present?

Diff for: lib/mongoid/history/options.rb

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def default_options
3434
track_create: true,
3535
track_update: true,
3636
track_destroy: true,
37+
track_blank_changes: false,
3738
format: nil }
3839
end
3940

Diff for: lib/mongoid/history/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Mongoid
22
module History
3-
VERSION = '0.8.5'.freeze
3+
VERSION = '0.9.0'.freeze
44
end
55
end

Diff for: spec/unit/attributes/update_spec.rb

+61-29
Original file line numberDiff line numberDiff line change
@@ -305,37 +305,69 @@ class EmbOne
305305
end
306306
end
307307

308-
context 'when original and modified values blank' do
309-
before :each do
310-
class DummyParent
311-
include Mongoid::Document
312-
include Mongoid::History::Trackable
313-
store_in collection: :dummy_parents
314-
has_and_belongs_to_many :other_dummy_parents
315-
track_history on: :fields
316-
end
317-
318-
class OtherDummyParent
319-
include Mongoid::Document
320-
has_and_belongs_to_many :dummy_parents
321-
end
322-
end
323-
324-
before :each do
325-
allow(base).to receive(:changes) { changes }
326-
DummyParent.track_history on: :other_dummy_parent_ids
327-
end
308+
[false, true].each do |original_nil|
309+
context "when original value #{original_nil ? 'nil' : 'blank'} and modified value #{original_nil ? 'blank' : 'nil'}" do
310+
[nil, false, true].each do |track_blank_changes|
311+
context "when track_blank_changes #{track_blank_changes.nil? ? 'default' : track_blank_changes}" do
312+
before :each do
313+
class DummyParent
314+
include Mongoid::Document
315+
include Mongoid::History::Trackable
316+
store_in collection: :dummy_parents
317+
has_and_belongs_to_many :other_dummy_parents
318+
field :boolean, type: Boolean
319+
field :string, type: String
320+
field :hash, type: Hash
321+
end
322+
323+
class OtherDummyParent
324+
include Mongoid::Document
325+
has_and_belongs_to_many :dummy_parents
326+
end
327+
328+
if track_blank_changes.nil?
329+
DummyParent.track_history on: :fields
330+
else
331+
DummyParent.track_history \
332+
on: :fields,
333+
track_blank_changes: track_blank_changes
334+
end
335+
336+
allow(base).to receive(:changes) { changes }
337+
end
328338

329-
let(:base) { described_class.new(DummyParent.new) }
330-
let(:changes) do
331-
{ 'other_dummy_parent_ids' => [nil, []] }
332-
end
333-
subject { base.attributes }
334-
it { expect(subject.keys).to_not include 'other_dummy_parent_ids' }
339+
after :each do
340+
Object.send(:remove_const, :DummyParent)
341+
Object.send(:remove_const, :OtherDummyParent)
342+
end
335343

336-
after :each do
337-
Object.send(:remove_const, :DummyParent)
338-
Object.send(:remove_const, :OtherDummyParent)
344+
let(:base) { described_class.new(DummyParent.new) }
345+
subject { base.attributes.keys }
346+
347+
# These can't be memoizing methods (i.e. lets) because of limits
348+
# on where those can be used.
349+
350+
cmp = track_blank_changes ? 'should' : 'should_not'
351+
cmp_name = cmp.humanize capitalize: false
352+
353+
[
354+
{ n: 'many-to-many', f: 'other_dummy_parent_ids', v: [] },
355+
{ n: 'boolean', f: 'boolean', v: false },
356+
{ n: 'empty string', f: 'string', v: '' },
357+
{ n: 'all whitespace string', f: 'string', v: " \t\n\r\f\v" }
358+
# The second character in that string is an actual tab (0x9).
359+
].each do |d|
360+
context "#{d[:n]} field" do
361+
let(:changes) do
362+
{ d[:f] => original_nil ? [nil, d[:v]] : [d[:v], nil] }
363+
end
364+
it "changes #{cmp_name} include #{d[:f]}" do
365+
send(cmp, include(d[:f]))
366+
end
367+
end
368+
end
369+
end
370+
end
339371
end
340372
end
341373
end

Diff for: spec/unit/options_spec.rb

+7
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class EmbFour
103103
track_create: true,
104104
track_update: true,
105105
track_destroy: true,
106+
track_blank_changes: false,
106107
format: nil
107108
}
108109
end
@@ -173,6 +174,7 @@ class EmbFour
173174
track_create: true,
174175
track_update: true,
175176
track_destroy: true,
177+
track_blank_changes: false,
176178
fields: %w[foo b],
177179
dynamic: [],
178180
relations: { embeds_one: {}, embeds_many: {} },
@@ -354,6 +356,11 @@ class EmbFour
354356
it { expect(subject[:track_destroy]).to be true }
355357
end
356358

359+
describe ':track_blank_changes' do
360+
let(:options) { { track_blank_changes: true } }
361+
it { expect(subject[:track_blank_changes]).to be true }
362+
end
363+
357364
describe '#remove_reserved_fields' do
358365
let(:options) { { on: %i[_id _type foo version modifier_id] } }
359366
it { expect(subject[:fields]).to eq %w[foo] }

Diff for: spec/unit/trackable_spec.rb

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class MyModelWithNoModifier
9090
track_create: true,
9191
track_update: true,
9292
track_destroy: true,
93+
track_blank_changes: false,
9394
fields: %w[foo],
9495
relations: { embeds_one: {}, embeds_many: {} },
9596
dynamic: [],

0 commit comments

Comments
 (0)