Current section

Files

Jump to
dymo lib dymo taggable.ex
Raw

lib/dymo/taggable.ex

defmodule Dymo.Taggable do
@moduledoc """
Allows to easily make models taggable.
To use this module, you can simply leverage the `use` macro inside
your model module:
```elixir
use Dymo.Taggable
```
Doing so will require you to have a join table set up between the model and `Dymo.Tag`
as well as a `many_to_many` relationship on your model (or use the `tag()` macro to declare
the relationship automatically).
For more information about their usage and typings, have a look at the `Dymo.Tagger`
behaviour and the `Dymo.TaggerImpl` implementation.
"""
require Dymo.Taggable.Protocol
alias Ecto.{Query, Schema}
alias Dymo.{Tag, Tagger}
alias Dymo.Taggable.Protocol
@type t :: Protocol.t()
defmacro __using__(opts) do
impl = Keyword.get(opts, :implementation, Dymo.TaggerImpl)
join_table = Keyword.get(opts, :join_table, Tagger.join_table(__CALLER__.module))
join_key = Keyword.get(opts, :join_key, Tagger.join_key(__CALLER__.module))
Module.put_attribute(__CALLER__.module, :tagger_impl, impl)
Module.put_attribute(__CALLER__.module, :join_table, join_table)
Module.put_attribute(__CALLER__.module, :join_key, join_key)
quote do
import Dymo.Taggable
@tagger_impl unquote(impl)
@before_compile Dymo.Taggable
end
end
defmacro __before_compile__(env) do
impl = Module.get_attribute(env.module, :tagger_impl)
join_table = Module.get_attribute(env.module, :join_table)
join_key = Module.get_attribute(env.module, :join_key)
quote do
require Dymo.Taggable.Protocol
alias Ecto.{Query, Schema}
alias Dymo.Tag
defimpl Dymo.Taggable.Protocol do
@spec labels(Dymo.Taggable.t(), keyword) :: Query.t()
def labels(taggable, opts \\ []),
do: unquote(impl).labels(taggable, unquote(join_table), unquote(join_key), opts)
@spec set_labels(Dymo.Taggable.t(), Tag.label_or_labels(), keyword) :: Schema.t()
def set_labels(taggable, labels, opts \\ []),
do: unquote(impl).set_labels(taggable, labels, opts)
@spec add_labels(Dymo.Taggable.t(), Tag.label_or_labels(), keyword) :: Schema.t()
def add_labels(taggable, labels, opts \\ []),
do: unquote(impl).add_labels(taggable, labels, opts)
@spec remove_labels(Dymo.Taggable.t(), Tag.label_or_labels(), keyword) :: Schema.t()
def remove_labels(taggable, labels, opts \\ []),
do: unquote(impl).remove_labels(taggable, labels, opts)
end
@spec all_labels(keyword) :: Query.t()
def all_labels(opts \\ []),
do:
unquote(impl).all_labels(
unquote(join_table),
unquote(join_key),
opts
)
@spec labeled_with(Tag.label_or_labels(), keyword) :: Query.t()
def labeled_with(label_or_labels, opts \\ []),
do:
unquote(impl).labeled_with(
__MODULE__,
label_or_labels,
unquote(join_table),
unquote(join_key),
opts
)
end
end
@doc "Use this macro in your Ecto schema to add `tags` field."
defmacro tags do
taggings =
__CALLER__.module
|> Module.get_attribute(:join_table)
|> case do
nil -> raise "Please declare `use #{__MODULE__}` before using `#{__MODULE__}.tags/1`."
taggings -> taggings
end
quote do
many_to_many :tags, Dymo.Tag,
join_through: unquote(taggings),
on_replace: :delete,
unique: true
end
end
@doc "Returns all labels in the given namespace."
@spec labels(t(), keyword) :: Query.t()
defdelegate labels(taggable, opts \\ []), to: Protocol
@doc "Sets all labels with the given namespace, replacing existing ones."
@spec set_labels(t(), Tag.label_or_labels(), keyword) :: Schema.t()
defdelegate set_labels(taggable, lbls, opts \\ []), to: Protocol
@doc "Adds labels to the given namespace."
@spec add_labels(t(), Tag.label_or_labels(), keyword) :: Schema.t()
defdelegate add_labels(taggable, lbls, opts \\ []), to: Protocol
@doc "Remove labels from the given namespace."
@spec remove_labels(t(), Tag.label_or_labels(), keyword) :: Schema.t()
defdelegate remove_labels(taggable, label_or_labels, opts \\ []), to: Protocol
@doc "Returns all labels associad with the given schema."
@spec all_labels(module, keyword) :: Query.t()
def all_labels(module, opts \\ []), do: module.all_labels(opts)
@doc "Returns all objects of given type labeled with given labels."
@spec labeled_with(module, Tag.label_or_labels(), keyword) :: Query.t()
def labeled_with(module, tags, opts \\ []), do: module.labeled_with(tags, opts)
end