2020-11-24 15:15:51 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class IssueLink < ApplicationRecord
|
|
|
|
include FromUnion
|
|
|
|
|
|
|
|
belongs_to :source, class_name: 'Issue'
|
|
|
|
belongs_to :target, class_name: 'Issue'
|
|
|
|
|
|
|
|
validates :source, presence: true
|
|
|
|
validates :target, presence: true
|
|
|
|
validates :source, uniqueness: { scope: :target_id, message: 'is already related' }
|
|
|
|
validate :check_self_relation
|
2021-01-29 00:20:46 +05:30
|
|
|
validate :check_opposite_relation
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
scope :for_source_issue, ->(issue) { where(source_id: issue.id) }
|
|
|
|
scope :for_target_issue, ->(issue) { where(target_id: issue.id) }
|
|
|
|
|
|
|
|
TYPE_RELATES_TO = 'relates_to'
|
|
|
|
TYPE_BLOCKS = 'blocks'
|
2021-03-11 19:13:27 +05:30
|
|
|
# we don't store is_blocked_by in the db but need it for displaying the relation
|
|
|
|
# from the target (used in IssueLink.inverse_link_type)
|
2020-11-24 15:15:51 +05:30
|
|
|
TYPE_IS_BLOCKED_BY = 'is_blocked_by'
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
enum link_type: { TYPE_RELATES_TO => 0, TYPE_BLOCKS => 1 }
|
2020-11-24 15:15:51 +05:30
|
|
|
|
|
|
|
def self.inverse_link_type(type)
|
|
|
|
type
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def check_self_relation
|
|
|
|
return unless source && target
|
|
|
|
|
|
|
|
if source == target
|
|
|
|
errors.add(:source, 'cannot be related to itself')
|
|
|
|
end
|
|
|
|
end
|
2021-01-29 00:20:46 +05:30
|
|
|
|
|
|
|
def check_opposite_relation
|
|
|
|
return unless source && target
|
|
|
|
|
|
|
|
if IssueLink.find_by(source: target, target: source)
|
|
|
|
errors.add(:source, 'is already related to this issue')
|
|
|
|
end
|
|
|
|
end
|
2020-11-24 15:15:51 +05:30
|
|
|
end
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
IssueLink.prepend_mod_with('IssueLink')
|