lib/openagents/projects/project.ex

58e6347eeb72 · 1 KB

defmodule OpenAgents.Projects.Project do
  use Ecto.Schema
  import Ecto.Changeset

  alias OpenAgents.Repositories.Repository

  @states ~w(open closed)

  @doc "The states a project moves between."
  def states, do: @states

  schema "projects" do
    field :number, :integer
    field :title, :string
    field :description, :string
    field :owner, :string
    field :state, :string, default: "open"
    field :archived_at, :utc_datetime
    belongs_to :repository, Repository, type: :binary_id
    belongs_to :owner_user, OpenAgents.Accounts.User, type: :binary_id

    timestamps(type: :utc_datetime)
  end

  @doc """
  Whether `project` sits in the archive.

  Archiving is orthogonal to `state`. A closed project says the work it tracked
  reached an end; an archived project says the board is out of the working set,
  whatever became of the work. That is why the archive is a timestamp rather
  than a third `state` value: every reader of `open` and `closed` keeps reading
  the two values it always read.
  """
  def archived?(%__MODULE__{archived_at: nil}), do: false
  def archived?(%__MODULE__{}), do: true

  @doc false
  def changeset(project, attrs) do
    project
    |> cast(attrs, [
      :number,
      :title,
      :description,
      :owner,
      :state,
      :archived_at,
      :repository_id,
      :owner_user_id
    ])
    |> validate_required([:number, :title, :owner, :state, :repository_id])
    |> validate_inclusion(:state, @states)
    |> validate_length(:description, max: 20_000)
    |> unique_constraint([:repository_id, :number])
    |> check_constraint(:state, name: :projects_state_check, message: "is invalid")
    |> foreign_key_constraint(:repository_id)
    |> foreign_key_constraint(:owner_user_id)
    |> foreign_key_constraint(:owner_user_id, name: :projects_owner_membership_fkey)
  end
end