summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2015-07-13 18:28:10 -0400
committerRobert Speicher <rspeicher@gmail.com>2015-07-13 21:31:00 -0400
commite4c698fd5ce77e46e3851384c14271eb74c3c9ee (patch)
treeab0d59f0a789dfca1c25d4ec3b265225a7860bb3
parent59d7f4c97ff2869824cb5b1b0fbe14c983dab75d (diff)
downloadgitlab-ce-e4c698fd5ce77e46e3851384c14271eb74c3c9ee.tar.gz
Refactor Mentionable#notice_added_references
It now accounts for models that have changed but have already been persisted, such as when called from an UpdateService. Closes #1773
-rw-r--r--app/models/concerns/mentionable.rb36
-rw-r--r--spec/models/concerns/mentionable_spec.rb49
2 files changed, 74 insertions, 11 deletions
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 56849f28ff0..8ff670dd2bf 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -82,19 +82,33 @@ module Mentionable
# 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
+ 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/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index f7f66987b5f..82e8a83bb3d 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -28,4 +28,53 @@ describe Issue, "Mentionable" do
issue.create_cross_references!(project, author, [commit2])
end
end
+
+ describe '#notice_added_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.notice_added_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.notice_added_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.notice_added_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.notice_added_references
+ end
+ end
+
+ def create_issue(description:)
+ create(:issue, project: project, description: description)
+ end
+ end
end