| 1 | # frozen_string_literal: true |
| 2 | |
| 3 | # A request to merge one branch (head) into another (base) within a repository. |
| 4 | # Numbered per-repository, like GitHub. Created through PullRequestService so the |
| 5 | # same validations and authorization run regardless of caller (web UI or MCP). |
| 6 | class PullRequest < ApplicationRecord |
| 7 | STATES = %w[open closed merged].freeze |
| 8 | |
| 9 | belongs_to :repository |
| 10 | belongs_to :user # author |
| 11 | has_many :comments, as: :commentable, dependent: :destroy |
| 12 | |
| 13 | scope :open, -> { where(state: "open") } |
| 14 | scope :closed, -> { where(state: "closed") } |
| 15 | scope :merged, -> { where(state: "merged") } |
| 16 | |
| 17 | validates :number, presence: true, uniqueness: { scope: :repository_id } |
| 18 | validates :title, presence: true |
| 19 | validates :head_ref, presence: true |
| 20 | validates :base_ref, presence: true |
| 21 | validates :state, inclusion: { in: STATES } |
| 22 | validate :head_differs_from_base |
| 23 | |
| 24 | def author |
| 25 | user |
| 26 | end |
| 27 | |
| 28 | def open? |
| 29 | state == "open" |
| 30 | end |
| 31 | |
| 32 | def closed? |
| 33 | state == "closed" |
| 34 | end |
| 35 | |
| 36 | def merged? |
| 37 | state == "merged" |
| 38 | end |
| 39 | |
| 40 | def html_url |
| 41 | "#{repository.html_url}/pull/#{number}" |
| 42 | end |
| 43 | |
| 44 | private |
| 45 | |
| 46 | def head_differs_from_base |
| 47 | return if head_ref.blank? || base_ref.blank? |
| 48 | errors.add(:head_ref, "must be different from the base branch") if head_ref == base_ref |
| 49 | end |
| 50 | end |