diff --git a/CHANGELOG.md b/CHANGELOG.md index 098f85fd..3420825f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### 0.7.1 (Next) * Your contribution here. +* [#205](https://github.com/mongoid/mongoid-history/pull/205): Allow modifier field to be optional - [@yads](https://github.com/yads). ### 0.7.0 (2017/11/14) diff --git a/Gemfile b/Gemfile index 94a811ab..912dc5d1 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ when /3/ else gem 'mongoid', version end +gem 'mongoid-compatibility' group :development, :test do gem 'bundler' diff --git a/README.md b/README.md index 63c46f08..ef9fddc8 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,9 @@ class Post # telling Mongoid::History how you want to track changes # dynamic fields will be tracked automatically (for MongoId 4.0+ you should include Mongoid::Attributes::Dynamic to your model) track_history :on => [:title, :body], # track title and body fields only, default is :all - :modifier_field => :modifier, # adds "belongs_to :modifier" to track who made the change, default is :modifier + :modifier_field => :modifier, # adds "belongs_to :modifier" to track who made the change, default is :modifier, set to nil to not create modifier_field :modifier_field_inverse_of => :nil, # adds an ":inverse_of" option to the "belongs_to :modifier" relation, default is not set + :modifier_field_optional => true, # marks the modifier relationship as optional (requires Mongoid 6 or higher) :version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version :track_create => false, # track document creation, default is false :track_update => true, # track document updates, default is true diff --git a/lib/mongoid/history/trackable.rb b/lib/mongoid/history/trackable.rb index 4c213f9c..197decc4 100644 --- a/lib/mongoid/history/trackable.rb +++ b/lib/mongoid/history/trackable.rb @@ -12,9 +12,12 @@ def track_history(options = {}) field options[:version_field].to_sym, type: Integer - belongs_to_modifier_options = { class_name: Mongoid::History.modifier_class_name } - belongs_to_modifier_options[:inverse_of] = options[:modifier_field_inverse_of] if options.key?(:modifier_field_inverse_of) - belongs_to options[:modifier_field].to_sym, belongs_to_modifier_options + unless options[:modifier_field].nil? + belongs_to_modifier_options = { class_name: Mongoid::History.modifier_class_name } + belongs_to_modifier_options[:inverse_of] = options[:modifier_field_inverse_of] if options.key?(:modifier_field_inverse_of) + belongs_to_modifier_options[:optional] = true if options[:modifier_field_optional] && Mongoid::Compatibility::Version.mongoid6_or_newer? + belongs_to options[:modifier_field].to_sym, belongs_to_modifier_options + end include MyInstanceMethods extend SingletonMethods @@ -219,11 +222,12 @@ def modified_attributes_for_destroy def history_tracker_attributes(action) return @history_tracker_attributes if @history_tracker_attributes + modifier_field = history_trackable_options[:modifier_field] @history_tracker_attributes = { association_chain: traverse_association_chain, - scope: related_scope, - modifier: send(history_trackable_options[:modifier_field]) + scope: related_scope } + @history_tracker_attributes[:modifier] = send(modifier_field) if modifier_field original, modified = transform_changes(modified_attributes_for_action(action)) @@ -425,7 +429,12 @@ def tracked_fields # # @return [ Array < String > ] the list of reserved database field names def reserved_tracked_fields - @reserved_tracked_fields ||= ['_id', history_trackable_options[:version_field].to_s, "#{history_trackable_options[:modifier_field]}_id"] + @reserved_tracked_fields ||= begin + fields = ['_id', history_trackable_options[:version_field].to_s] + modifier_field = history_trackable_options[:modifier_field] + fields << "#{modifier_field}_id" if modifier_field + fields + end end def field_formats diff --git a/lib/mongoid/history/tracker.rb b/lib/mongoid/history/tracker.rb index 5e383fbd..60033693 100644 --- a/lib/mongoid/history/tracker.rb +++ b/lib/mongoid/history/tracker.rb @@ -14,7 +14,11 @@ module Tracker field :version, type: Integer field :action, type: String field :scope, type: String - belongs_to :modifier, class_name: Mongoid::History.modifier_class_name + modifier_options = { + class_name: Mongoid::History.modifier_class_name + } + modifier_options[:optional] = true if Mongoid::Compatibility::Version.mongoid6_or_newer? + belongs_to :modifier, modifier_options index(scope: 1) index(association_chain: 1) @@ -50,7 +54,7 @@ def undo_attr(modifier) undo_hash = affected.easy_unmerge(modified) undo_hash.easy_merge!(original) modifier_field = trackable.history_trackable_options[:modifier_field] - undo_hash[modifier_field] = modifier + undo_hash[modifier_field] = modifier if modifier_field (modified.keys - undo_hash.keys).each do |k| undo_hash[k] = nil end @@ -61,7 +65,7 @@ def redo_attr(modifier) redo_hash = affected.easy_unmerge(original) redo_hash.easy_merge!(modified) modifier_field = trackable.history_trackable_options[:modifier_field] - redo_hash[modifier_field] = modifier + redo_hash[modifier_field] = modifier if modifier_field localize_keys(redo_hash) end diff --git a/spec/unit/trackable_spec.rb b/spec/unit/trackable_spec.rb index 2087bdc6..99090f97 100644 --- a/spec/unit/trackable_spec.rb +++ b/spec/unit/trackable_spec.rb @@ -6,6 +6,12 @@ class MyModel field :foo end +class MyModelWithNoModifier + include Mongoid::Document + include Mongoid::History::Trackable + field :foo +end + class MyDynamicModel include Mongoid::Document include Mongoid::History::Trackable @@ -31,6 +37,7 @@ class HistoryTracker before :all do MyModel.track_history @persisted_history_options = Mongoid::History.trackable_class_options + MyModelWithNoModifier.track_history modifier_field: nil end before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options } let(:expected_option) do @@ -72,6 +79,12 @@ class HistoryTracker expect(MyModel.history_trackable_options).to eq(expected_option) end + context 'modifier_field set to nil' do + it 'should not have a modifier relationship' do + expect(MyModelWithNoModifier.reflect_on_association(:modifier)).to be_nil + end + end + describe '#tracked_fields' do it 'should return the tracked field list' do expect(MyModel.tracked_fields).to eq(regular_fields) @@ -82,6 +95,10 @@ class HistoryTracker it 'should return the protected field list' do expect(MyModel.reserved_tracked_fields).to eq(reserved_fields) end + + it 'should not include modifier_field if not specified' do + expect(MyModelWithNoModifier.reserved_tracked_fields).not_to include('modifier') + end end describe '#tracked_fields_for_action' do @@ -628,6 +645,7 @@ def self.name before :all do MyModel.track_history(on: :foo, track_create: true) @persisted_history_options = Mongoid::History.trackable_class_options + MyModelWithNoModifier.track_history modifier_field: nil end before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options } @@ -635,6 +653,12 @@ def self.name expect { MyModel.create!(foo: 'bar') }.to change(Tracker, :count).by(1) end + context 'no modifier_field' do + it 'should create history' do + expect { MyModelWithNoModifier.create!(foo: 'bar').to change(Tracker, :count).by(1) } + end + end + it 'should not create history when error raised' do expect(MyModel).to receive(:create!).and_raise(StandardError) expect do