Current section
Files
Jump to
Current section
Files
lib/dymo/tag.ex
defmodule Dymo.Tag do
@moduledoc """
This module provides functionality dedicated to handling tag data.
"""
use Ecto.Schema
import Ecto.Changeset
@me __MODULE__
@repo Dymo.repo()
@type id :: pos_integer
@typedoc """
The `Dymo.Tag.t()` schema defines simple tags identified by
a unique label.
"""
@type t :: %__MODULE__{}
schema "tags" do
# Regular fields.
field(:label, :string)
timestamps()
end
@doc """
Makes a changeset suited to manipulate the `Dymo.Tag` model.
"""
@spec changeset(map()) :: Ecto.Changeset.t()
def changeset(attrs),
do:
%@me{}
|> cast(attrs, [:label])
|> validate_required([:label])
|> unique_constraint(:label)
@doc """
This function gets an existing tag using its label. If the tag doesn't
exit, it is atomically created. It could be described as a "singleton"
helper.
## Examples
iex> %{id: id1a} = Tag.find_or_create!("novel")
iex> [%{id: id2a}, %{id: id3a}] = Tag.find_or_create!(["article", "book"])
iex> [%{id: id1b}, %{id: id2b}, %{id: id3b}] = Tag.find_or_create!(["novel", "article", "book"])
iex> {id1a, id2a, id3a} == {id1b, id2b, id3b}
true
"""
@spec find_or_create!(String.t() | [String.t()]) :: t
def find_or_create!(labels) when is_list(labels),
do:
labels
|> Enum.uniq()
|> Enum.map(&find_or_create!/1)
def find_or_create!(label) do
@me
|> @repo.get_by(label: label)
|> case do
nil -> upsert!(label)
tag -> tag
end
end
@spec upsert!(String.t()) :: t
defp upsert!(label) do
%{label: label}
|> changeset()
|> @repo.insert!(on_conflict: :nothing)
|> case do
%{id: nil} -> @repo.get_by!(@me, label: label)
tag -> tag
end
end
end