Commit c0faf91f authored by Robert Speicher's avatar Robert Speicher
Browse files

Add `to_reference` for models that support references

Now there is a single source of information for which attribute a model
uses to be referenced, and its special character.
parent 512b1de8c157
class Commit class Commit
include ActiveModel::Conversion
include StaticModel
extend ActiveModel::Naming extend ActiveModel::Naming
include ActiveModel::Conversion
include Mentionable include Mentionable
include Participable include Participable
include Referable
include StaticModel
attr_mentionable :safe_message attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users participant :author, :committer, :notes, :mentioned_users
...@@ -60,6 +62,14 @@ def ==(other) ...@@ -60,6 +62,14 @@ def ==(other)
(self.class === other) && (raw == other.raw) (self.class === other) && (raw == other.raw)
end end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}"
else
id
end
end
def diff_line_count def diff_line_count
@diff_line_count ||= Commit::diff_line_count(self.diffs) @diff_line_count ||= Commit::diff_line_count(self.diffs)
@diff_line_count @diff_line_count
...@@ -132,7 +142,7 @@ def closes_issues(current_user = self.committer) ...@@ -132,7 +142,7 @@ def closes_issues(current_user = self.committer)
# Mentionable override. # Mentionable override.
def gfm_reference def gfm_reference
"commit #{id}" "commit #{to_reference}"
end end
def author def author
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
# #
class CommitRange class CommitRange
include ActiveModel::Conversion include ActiveModel::Conversion
include Referable
attr_reader :sha_from, :notation, :sha_to attr_reader :sha_from, :notation, :sha_to
...@@ -59,6 +60,14 @@ def to_s ...@@ -59,6 +60,14 @@ def to_s
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
end end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
"#{project.to_reference}@#{to_s}"
else
to_s
end
end
# Returns a String for use in a link's title attribute # Returns a String for use in a link's title attribute
def reference_title def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}" "Commits #{suffixed_sha_from} through #{sha_to}"
......
class ExternalIssue class ExternalIssue
include Referable
def initialize(issue_identifier, project) def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project @issue_identifier, @project = issue_identifier, project
end end
...@@ -7,6 +9,10 @@ def to_s ...@@ -7,6 +9,10 @@ def to_s
@issue_identifier.to_s @issue_identifier.to_s
end end
def to_reference(_from_project = nil)
id
end
def id def id
@issue_identifier.to_s @issue_identifier.to_s
end end
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
require 'file_size_validator' require 'file_size_validator'
class Group < Namespace class Group < Namespace
include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members has_many :users, through: :group_members
...@@ -36,6 +38,14 @@ def search(query) ...@@ -36,6 +38,14 @@ def search(query)
def sort(method) def sort(method)
order_by(method) order_by(method)
end end
def reference_prefix
'@'
end
end
def to_reference(_from_project = nil)
"#{self.class.reference_prefix}#{name}"
end end
def human_name def human_name
......
...@@ -21,10 +21,11 @@ ...@@ -21,10 +21,11 @@
require 'file_size_validator' require 'file_size_validator'
class Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
include Issuable
include InternalId include InternalId
include Taskable include Issuable
include Referable
include Sortable include Sortable
include Taskable
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
...@@ -49,14 +50,28 @@ class Issue < ActiveRecord::Base ...@@ -49,14 +50,28 @@ class Issue < ActiveRecord::Base
state :closed state :closed
end end
def self.reference_prefix
'#'
end
def hook_attrs def hook_attrs
attributes attributes
end end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
end
# Mentionable overrides. # Mentionable overrides.
def gfm_reference def gfm_reference
"issue ##{iid}" "issue #{to_reference}"
end end
# Reset issue events cache # Reset issue events cache
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
# #
class Label < ActiveRecord::Base class Label < ActiveRecord::Base
include Referable
DEFAULT_COLOR = '#428BCA' DEFAULT_COLOR = '#428BCA'
default_value_for :color, DEFAULT_COLOR default_value_for :color, DEFAULT_COLOR
...@@ -34,6 +36,31 @@ class Label < ActiveRecord::Base ...@@ -34,6 +36,31 @@ class Label < ActiveRecord::Base
alias_attribute :name, :title alias_attribute :name, :title
def self.reference_prefix
'~'
end
# Returns the String necessary to reference this Label in Markdown
#
# format - Symbol format to use (default: :id, optional: :name)
#
# Note that its argument differs from other objects implementing Referable. If
# a non-Symbol argument is given (such as a Project), it will default to :id.
#
# Examples:
#
# Label.first.to_reference # => "~1"
# Label.first.to_reference(:name) # => "~\"bug\""
#
# Returns a String
def to_reference(format = :id)
if format == :name
%(#{self.class.reference_prefix}"#{name}")
else
"#{self.class.reference_prefix}#{id}"
end
end
def open_issues_count def open_issues_count
issues.opened.count issues.opened.count
end end
......
...@@ -25,10 +25,11 @@ ...@@ -25,10 +25,11 @@
require Rails.root.join("lib/static_model") require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base class MergeRequest < ActiveRecord::Base
include Issuable
include Taskable
include InternalId include InternalId
include Issuable
include Referable
include Sortable include Sortable
include Taskable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
...@@ -135,6 +136,20 @@ class MergeRequest < ActiveRecord::Base ...@@ -135,6 +136,20 @@ class MergeRequest < ActiveRecord::Base
scope :closed, -> { with_states(:closed, :merged) } scope :closed, -> { with_states(:closed, :merged) }
scope :declined, -> { with_states(:closed) } scope :declined, -> { with_states(:closed) }
def self.reference_prefix
'!'
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
end
def validate_branches def validate_branches
if target_project == source_project && target_branch == source_branch if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target" errors.add :branch_conflict, "You can not use same project/branch for source and target"
...@@ -291,7 +306,7 @@ def closes_issues(current_user = self.author) ...@@ -291,7 +306,7 @@ def closes_issues(current_user = self.author)
# Mentionable override. # Mentionable override.
def gfm_reference def gfm_reference
"merge request !#{iid}" "merge request #{to_reference}"
end end
def target_project_path def target_project_path
......
...@@ -33,11 +33,12 @@ ...@@ -33,11 +33,12 @@
require 'file_size_validator' require 'file_size_validator'
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include Sortable include Gitlab::ConfigHelper
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Gitlab::ConfigHelper
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include Referable
include Sortable
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Enumerize extend Enumerize
...@@ -305,6 +306,10 @@ def to_param ...@@ -305,6 +306,10 @@ def to_param
path path
end end
def to_reference(_from_project = nil)
path_with_namespace
end
def web_url def web_url
[gitlab_config.url, path_with_namespace].join('/') [gitlab_config.url, path_with_namespace].join('/')
end end
......
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
# #
class Snippet < ActiveRecord::Base class Snippet < ActiveRecord::Base
include Sortable
include Linguist::BlobHelper
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Linguist::BlobHelper
include Participable include Participable
include Referable
include Sortable
default_value_for :visibility_level, Snippet::PRIVATE default_value_for :visibility_level, Snippet::PRIVATE
...@@ -50,6 +51,20 @@ class Snippet < ActiveRecord::Base ...@@ -50,6 +51,20 @@ class Snippet < ActiveRecord::Base
participant :author, :notes participant :author, :notes
def self.reference_prefix
'$'
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
end
def self.content_types def self.content_types
[ [
".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java", ".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java",
......
...@@ -62,11 +62,13 @@ ...@@ -62,11 +62,13 @@
require 'file_size_validator' require 'file_size_validator'
class User < ActiveRecord::Base class User < ActiveRecord::Base
include Sortable
include Gitlab::ConfigHelper
include TokenAuthenticatable
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
include Gitlab::ConfigHelper
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Referable
include Sortable
include TokenAuthenticatable
default_value_for :admin, false default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_group, gitlab_config.default_can_create_group
...@@ -247,6 +249,10 @@ def by_username_or_id(name_or_id) ...@@ -247,6 +249,10 @@ def by_username_or_id(name_or_id)
def build_user(attrs = {}) def build_user(attrs = {})
User.new(attrs) User.new(attrs)
end end
def reference_prefix
'@'
end
end end
# #
...@@ -257,6 +263,10 @@ def to_param ...@@ -257,6 +263,10 @@ def to_param
username username
end end
def to_reference(_from_project = nil)
"#{self.class.reference_prefix}#{username}"
end
def notification def notification
@notification ||= Notification.new(self) @notification ||= Notification.new(self)
end end
......
...@@ -11,6 +11,29 @@ ...@@ -11,6 +11,29 @@
expect { described_class.new("Foo") }.to raise_error expect { described_class.new("Foo") }.to raise_error
end end
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
describe '#to_reference' do
let(:project) { double('project', to_reference: 'namespace1/project') }
before do
range.project = project
end
it 'returns a String reference to the object' do
expect(range.to_reference).to eq range.to_s
end
it 'supports a cross-project reference' do
cross = double('project')
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
end
end
describe '#to_s' do describe '#to_s' do
it 'is correct for three-dot syntax' do it 'is correct for three-dot syntax' do
expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}"
......
require 'spec_helper' require 'spec_helper'
describe Commit do describe Commit do
let(:project) { create :project } let(:project) { create(:project) }
let(:commit) { project.commit } let(:commit) { project.commit }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Mentionable) }
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(StaticModel) }
end
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(commit.to_reference).to eq commit.id
end
it 'supports a cross-project reference' do
cross = double('project')
expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}"
end
end
describe '#title' do describe '#title' do
it "returns no_commit_message when safe_message is blank" do it "returns no_commit_message when safe_message is blank" do
......
require 'spec_helper'
describe ExternalIssue do
let(:project) { double('project', to_reference: 'namespace1/project1') }
let(:issue) { described_class.new('EXT-1234', project) }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(issue.to_reference).to eq issue.id
end
end
describe '#title' do
it 'returns a title' do
expect(issue.title).to eq "External Issue #{issue}"
end
end
end
...@@ -18,16 +18,30 @@ ...@@ -18,16 +18,30 @@
describe Group do describe Group do
let!(:group) { create(:group) } let!(:group) { create(:group) }
describe "Associations" do describe 'associations' do
it { is_expected.to have_many :projects } it { is_expected.to have_many :projects }
it { is_expected.to have_many :group_members } it { is_expected.to have_many :group_members }
end end
it { is_expected.to validate_presence_of :name } describe 'modules' do
it { is_expected.to validate_uniqueness_of(:name) } subject { described_class }
it { is_expected.to validate_presence_of :path }
it { is_expected.to validate_uniqueness_of(:path) } it { is_expected.to include_module(Referable) }
it { is_expected.not_to validate_presence_of :owner } end
describe 'validations' do
it { is_expected.to validate_presence_of :name }
it { is_expected.to validate_uniqueness_of(:name) }
it { is_expected.to validate_presence_of :path }
it { is_expected.to validate_uniqueness_of(:path) }
it { is_expected.not_to validate_presence_of :owner }
end
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(group.to_reference).to eq "@#{group.name}"
end
end
describe :users do describe :users do
it { expect(group.users).to eq(group.owners) } it { expect(group.users).to eq(group.owners) }
......
...@@ -24,15 +24,30 @@ ...@@ -24,15 +24,30 @@
it { is_expected.to belong_to(:milestone) } it { is_expected.to belong_to(:milestone) }
end end
describe "Mass assignment" do
end
describe 'modules' do describe 'modules' do
subject { described_class }
it { is_expected.to include_module(InternalId) }
it { is_expected.to include_module(Issuable) } it { is_expected.to include_module(Issuable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(Taskable) }
end end
subject { create(:issue) } subject { create(:issue) }
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "##{subject.iid}"
end
it 'supports a cross-project reference' do