diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 56849f28ff08b380c04013af96d022681343b7f5..5b0ae41164200ed37a0eedfb994cacecdbcd4378 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -79,22 +79,36 @@ def create_cross_references!(p = project, a = author, without = []) end end - # If the mentionable_text field is about to change, locate any *added* references and create cross references for - # them. Invoke from an observer's #before_save implementation. - def notice_added_references(p = project, a = author) - ch = changed_attributes - original, mentionable_changed = "", false - self.class.mentionable_attrs.each do |attr| - if ch[attr] - original << ch[attr] - mentionable_changed = true - end - end + # When a mentionable field is changed, creates cross-reference notes that + # don't already exist + def create_new_cross_references!(p = project, a = author) + changes = detect_mentionable_changes + + return if changes.empty? - # Only proceed if the saved changes actually include a chance to an attr_mentionable field. - return unless mentionable_changed + original_text = changes.collect { |_, vals| vals.first }.join(' ') - preexisting = references(p, self.author, original) + preexisting = references(p, self.author, original_text) create_cross_references!(p, a, preexisting) end + + private + + # Returns a Hash of changed mentionable fields + # + # Preference is given to the `changes` Hash, but falls back to + # `previous_changes` if it's empty (i.e., the changes have already been + # persisted). + # + # See ActiveModel::Dirty. + # + # Returns a Hash. + def detect_mentionable_changes + source = (changes.present? ? changes : previous_changes).dup + + mentionable = self.class.mentionable_attrs + + # Only include changed fields that are mentionable + source.select { |key, val| mentionable.include?(key) } + end end diff --git a/app/models/note.rb b/app/models/note.rb index 68b9d433ae02d843dfc1e1ad0403d3d790c3e580..62567f471dc5dcb523d3b0b72e6abe995e577870 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -356,7 +356,7 @@ def reset_events_cache end def set_references - notice_added_references(project, author) + create_new_cross_references!(project, author) end def editable? diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index f848ecedd6b5fd6619b4428120839e966e626f11..eabab65c9b09dfb2cdf27ca0e2299d9948ebaaa8 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -35,7 +35,7 @@ def execute(issue) create_title_change_note(issue, issue.previous_changes['title'].first) end - issue.notice_added_references(issue.project, current_user) + issue.create_new_cross_references!(issue.project, current_user) execute_hooks(issue, 'update') end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index e5c5368f5d60fb23092df6cec7c9f1d6ab518c6a..589fad16165733691352086069ca0c81a2e1be5e 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -59,7 +59,7 @@ def execute(merge_request) merge_request.mark_as_unchecked end - merge_request.notice_added_references(merge_request.project, current_user) + merge_request.create_new_cross_references!(merge_request.project, current_user) execute_hooks(merge_request, 'update') end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index f7f66987b5f6ee516114884716f01bbe7836525a..2d6fe003215599acaf050a959299ab5d42ceadf6 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -28,4 +28,53 @@ issue.create_cross_references!(project, author, [commit2]) end end + + describe '#create_new_cross_references!' do + let(:project) { create(:project) } + let(:issues) { create_list(:issue, 2, project: project) } + + context 'before changes are persisted' do + it 'ignores pre-existing references' do + issue = create_issue(description: issues[0].to_reference) + + expect(SystemNoteService).not_to receive(:cross_reference) + + issue.description = 'New description' + issue.create_new_cross_references! + end + + it 'notifies new references' do + issue = create_issue(description: issues[0].to_reference) + + expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args) + + issue.description = issues[1].to_reference + issue.create_new_cross_references! + end + end + + context 'after changes are persisted' do + it 'ignores pre-existing references' do + issue = create_issue(description: issues[0].to_reference) + + expect(SystemNoteService).not_to receive(:cross_reference) + + issue.update_attributes(description: 'New description') + issue.create_new_cross_references! + end + + it 'notifies new references' do + issue = create_issue(description: issues[0].to_reference) + + expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args) + + issue.update_attributes(description: issues[1].to_reference) + issue.create_new_cross_references! + end + end + + def create_issue(description:) + create(:issue, project: project, description: description) + end + end end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index a2a0b6905f9561592e5cde5f13afd2f774fba08c..f0717e6178198736abbd34558de2ad14c09367a8 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -143,6 +143,6 @@ def common_mentionable_setup end set_mentionable_text.call(new_text) - subject.notice_added_references(project, author) + subject.create_new_cross_references!(project, author) end end