lib/bbox/candidates/candidate.ex

defmodule Bbox.Candidates.Candidate do
  @moduledoc """
  Candidate in the election and belongs to a party.
  """
  use Ecto.Schema
  import Ecto.Changeset
  alias Bbox.Parties.Party
  alias Bbox.Votes.Vote

  @primary_key false
  @foreign_key_type :string
  @derive {Jason.Encoder, except: [:__meta__, :party, :votes]}
  schema "candidates" do
    field(:first_name, :string)
    field(:last_name, :string)
    field(:email, :string)
    field(:code, :string)
    belongs_to(:party, Party, foreign_key: :party_initials, references: :initials)
    has_many(:votes, Vote, foreign_key: :candidate_code, references: :code)

    timestamps()
  end

  def changeset(candidate, params \\ %{}) do
    candidate
    |> cast(params, [:first_name, :last_name, :email])
    |> validate_required([:first_name, :last_name, :email])
    |> validate_format(:email, ~r/^\S+@\S+\.\S+$/, message: "invalid email")
    |> validate_length(:first_name, min: 3, message: "first name must be at least 3 characters")
    |> validate_length(:last_name, min: 3, message: "last name must be at least 3 characters")
    |> unique_constraint(:email, message: "email already exists")
    |> generate_code()
    |> unique_constraint(:code, message: "code already exists")
  end

  defp generate_code(%Ecto.Changeset{valid?: true} = changeset) do
    code =
      4
      |> :crypto.strong_rand_bytes()
      |> :binary.encode_hex()

    change(changeset, %{code: code})
  end

  defp generate_code(changeset), do: changeset
end