Packages

State machines, statecharts and workflows for Ecto models.

Current section

Files

Jump to
ex_state_ecto lib ex_state ecto query.ex
Raw

lib/ex_state/ecto/query.ex

defmodule ExState.Ecto.Query do
@moduledoc """
`ExState.Ecto.Query` provides functions for querying workflow state in the
database through Ecto.
"""
import Ecto.Query
@doc """
where_state/2 takes a subject query and state and filters based on workflows that are in the
exact state that is passed. Nested states can be passed as a list of states and will be converted
to a valid state identifier in the query.
Pass the state as the keyword list `not: state` in order to query the inverse.
Examples:
investment #=> %Investment{workflow: %Workflow{state: "subscribing.confirming_options"}}
Investment
|> where_state("subscribing.confirming_options")
|> Repo.all() #=> [investment]
Investment
|> where_state([:subscribing, :confirming_options])
|> Repo.all() #=> [investment]
Investment
|> where_state(["subscribing", "confirming_options"])
|> Repo.all() #=> [investment]
Investment
|> where_state(:subscribing)
|> Repo.all() #=> []
Investment
|> where_state(not: [:subscribing, :confirming_suitability])
|> Repo.all() #=> [investment]
Investment
|> where_state(not: [:subscribing, :confirming_options])
|> Repo.all() #=> []
"""
def where_state(subject_query, not: state) do
subject_query
|> join_workflow_maybe()
|> where([workflow: workflow], workflow.state != ^to_state_id(state))
end
def where_state(subject_query, state) do
subject_query
|> join_workflow_maybe()
|> where([workflow: workflow], workflow.state == ^to_state_id(state))
end
@doc """
where_state_in/2 takes a subject query and a list of states and filters based on workflows that
are in one of the exact states that are passed. Nested states can be passed as a list of states
and will be converted to a valid state identifier in the query.
Pass the state as the keyword list `not: state` in order to query the inverse.
Examples:
investment1 #=> %Investment{workflow: %Workflow{state: "subscribing.confirming_options"}}
investment2 #=> %Investment{workflow: %Workflow{state: "executed"}}
Investment
|> where_state_in([
[:subscribing, :confirming_options],
:executed
])
|> Repo.all() #=> [investment1, investment2]
Investment
|> where_state_in(["subscribing.confirming_options"])
|> Repo.all() #=> [investment1]
Investment
|> where_state_in([:subscribing])
|> Repo.all() #=> []
Investment
|> where_state_in(not: [[:subscribing, :confirming_options]])
|> Repo.all() #=> [investment2]
Investment
|> where_state_in(not: [:subscribing])
|> Repo.all() #=> [investment1, investment2]
"""
def where_state_in(subject_query, not: states) do
subject_query
|> join_workflow_maybe()
|> where([workflow: workflow], workflow.state not in ^Enum.map(states, &to_state_id/1))
end
def where_state_in(subject_query, states) do
subject_query
|> join_workflow_maybe()
|> where([workflow: workflow], workflow.state in ^Enum.map(states, &to_state_id/1))
end
@doc """
where_any_state/2 takes a subject query and a state and filters based on workflows that are equal
to or in a child state of the given state. Nested states can be passed as a list of states
and will be converted to a valid state identifier in the query.
Examples:
investment #=> %Investment{workflow: %Workflow{state: "subscribing.confirming_options"}}
Investment
|> where_any_state(:subscribing)
|> Repo.all() #=> [investment]
Investment
|> where_any_state("subscribing")
|> Repo.all() #=> [investment]
Investment
|> where_any_state([:subscribing, :confirming_options])
|> Repo.all() #=> [investment]
Investment
|> where_any_state(:resubmitting)
|> Repo.all() #=> []
"""
def where_any_state(subject_query, state) do
state_id = to_state_id(state)
subject_query
|> join_workflow_maybe()
|> where(
[workflow: workflow],
workflow.state == ^state_id or ilike(workflow.state, ^"#{state_id}.%")
)
end
def where_step_complete(q, s) when is_atom(s),
do: where_step_complete(q, Atom.to_string(s))
def where_step_complete(subject_query, step_name) do
subject_query
|> join_workflow_maybe()
|> join_workflow_steps_maybe()
|> where([workflow_step: step], step.name == ^step_name and step.complete?)
end
def to_state_id(states) when is_list(states) do
Enum.map(states, &to_state_id/1) |> Enum.join(".")
end
def to_state_id(state) when is_atom(state), do: Atom.to_string(state)
def to_state_id(state) when is_bitstring(state), do: state
def to_state_list(state) when is_bitstring(state) do
String.split(state, ".") |> Enum.map(&String.to_atom/1)
end
def to_step_name(step) when is_atom(step), do: Atom.to_string(step)
def to_step_name(step) when is_bitstring(step), do: step
defp join_workflow_maybe(subject_query) do
if has_named_binding?(subject_query, :workflow) do
subject_query
else
join(subject_query, :inner, [sub], w in assoc(sub, :workflow), as: :workflow)
end
end
defp join_workflow_steps_maybe(subject_query) do
if has_named_binding?(subject_query, :workflow_step) do
subject_query
else
join(subject_query, :inner, [workflow: w], s in assoc(w, :steps), as: :workflow_step)
end
end
end