120 lines
2.7 KiB
Ruby
120 lines
2.7 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
# Tuple of design and version
|
||
|
# * has a composite ID, with lazy_find
|
||
|
module DesignManagement
|
||
|
class DesignAtVersion
|
||
|
include ActiveModel::Validations
|
||
|
include GlobalID::Identification
|
||
|
include Gitlab::Utils::StrongMemoize
|
||
|
|
||
|
attr_reader :version
|
||
|
attr_reader :design
|
||
|
|
||
|
validates :version, presence: true
|
||
|
validates :design, presence: true
|
||
|
|
||
|
validate :design_and_version_belong_to_the_same_issue
|
||
|
validate :design_and_version_have_issue_id
|
||
|
|
||
|
def initialize(design: nil, version: nil)
|
||
|
@design, @version = design, version
|
||
|
end
|
||
|
|
||
|
def self.instantiate(attrs)
|
||
|
new(attrs).tap { |obj| obj.validate! }
|
||
|
end
|
||
|
|
||
|
# The ID, needed by GraphQL types and as part of the Lazy-fetch
|
||
|
# protocol, includes information about both the design and the version.
|
||
|
#
|
||
|
# The particular format is not interesting, and should be treated as opaque
|
||
|
# by all callers.
|
||
|
def id
|
||
|
"#{design.id}.#{version.id}"
|
||
|
end
|
||
|
|
||
|
def ==(other)
|
||
|
return false unless other && self.class == other.class
|
||
|
|
||
|
other.id == id
|
||
|
end
|
||
|
|
||
|
alias_method :eql?, :==
|
||
|
|
||
|
def self.lazy_find(id)
|
||
|
BatchLoader.for(id).batch do |ids, callback|
|
||
|
find(ids).each do |record|
|
||
|
callback.call(record.id, record)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.find(ids)
|
||
|
pairs = ids.map { |id| id.split('.').map(&:to_i) }
|
||
|
|
||
|
design_ids = pairs.map(&:first).uniq
|
||
|
version_ids = pairs.map(&:second).uniq
|
||
|
|
||
|
designs = ::DesignManagement::Design
|
||
|
.where(id: design_ids)
|
||
|
.index_by(&:id)
|
||
|
|
||
|
versions = ::DesignManagement::Version
|
||
|
.where(id: version_ids)
|
||
|
.index_by(&:id)
|
||
|
|
||
|
pairs.map do |(design_id, version_id)|
|
||
|
design = designs[design_id]
|
||
|
version = versions[version_id]
|
||
|
|
||
|
obj = new(design: design, version: version)
|
||
|
|
||
|
obj if obj.valid?
|
||
|
end.compact
|
||
|
end
|
||
|
|
||
|
def status
|
||
|
if not_created_yet?
|
||
|
:not_created_yet
|
||
|
elsif deleted?
|
||
|
:deleted
|
||
|
else
|
||
|
:current
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def deleted?
|
||
|
action&.deletion?
|
||
|
end
|
||
|
|
||
|
def not_created_yet?
|
||
|
action.nil?
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def action
|
||
|
strong_memoize(:most_recent_action) do
|
||
|
::DesignManagement::Action
|
||
|
.most_recent.up_to_version(version)
|
||
|
.find_by(design: design)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def design_and_version_belong_to_the_same_issue
|
||
|
id_a, id_b = [design, version].map { |obj| obj&.issue_id }
|
||
|
|
||
|
return if id_a == id_b
|
||
|
|
||
|
errors.add(:issue, 'must be the same on design and version')
|
||
|
end
|
||
|
|
||
|
def design_and_version_have_issue_id
|
||
|
return if [design, version].all? { |obj| obj.try(:issue_id).present? }
|
||
|
|
||
|
errors.add(:issue, 'must be present on both design and version')
|
||
|
end
|
||
|
end
|
||
|
end
|