zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

commit c09416a16a155d99d8f271b008ae1095c2657e9f
parent 7d48e0bb16461a3bce5416b6e99b0130fa115182
Author: srfsh <dev@srf.sh>
Date:   Tue,  8 Nov 2022 12:40:18 +0300

Zenflows{,Test}: refactor

The changes include:

* Add new multi_* CRUD functions to Domain modules to better
  encapsulate logic, which allows other multi_* functions to use
  them.  It would require WET code all over the place if we were
  to use create|update|delete functions with the same logic.

* Make create|update|delete functions use the new multi_* functions
  behind the scenes.

* Simplify Zenflows.DB.Schema's using macro, and make explicit
  aliases in schemas and Domain modules.

* Seperate paging logic and Relay Connection logic into Zenflows.DB.Page
  and Zenflows.GQL.Connection.

* Make Zenflows.DB.Page a struct that represents paging information
  independant of Relay Connection.

* Make resolvers returning paged data use Zenflows.GQL.Connection module.

* Make Domain.all/1 functions use Zenflows.DB.Page module.

* Make Filter modules more generic so that they represent what
  function they're used in the Domain modules.

* Move Zenflows.VF.Validate to Zenflows.DB.Validate make schemas
  use the new added functionality that checks logical-existance and
  value-equality.

* Add "raise-exception-on-error" variant of functions (the ones
  that end with "!") in Domain modules to ease testing and prototyping.

* Simplify code all over the place.

* Fix bugs discovered by the refactoring.

Diffstat:
Dsrc/zenflows/db/filter.ex | 49-------------------------------------------------
Asrc/zenflows/db/page.ex | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/zenflows/db/paging.ex | 166-------------------------------------------------------------------------------
Msrc/zenflows/db/schema.ex | 13+++----------
Asrc/zenflows/db/validate.ex | 371+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/file.ex | 7++++---
Msrc/zenflows/file/domain.ex | 26++++++++++++++++++--------
Asrc/zenflows/gql/connection.ex | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/keypairoom/domain.ex | 9+++++----
Msrc/zenflows/sw_pass/domain.ex | 16++++++++--------
Asrc/zenflows/validate.ex | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/vf/action/type.ex | 2+-
Msrc/zenflows/vf/agent.ex | 2+-
Msrc/zenflows/vf/agent/domain.ex | 35+++++++++++++++++++++--------------
Msrc/zenflows/vf/agent/filter.ex | 43++++++++++++++++++-------------------------
Msrc/zenflows/vf/agent/resolv.ex | 8++++++--
Msrc/zenflows/vf/agent/type.ex | 2+-
Msrc/zenflows/vf/agent_relationship.ex | 7++++---
Msrc/zenflows/vf/agent_relationship/domain.ex | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/zenflows/vf/agent_relationship/resolv.ex | 8++++++--
Msrc/zenflows/vf/agent_relationship/type.ex | 2+-
Msrc/zenflows/vf/agent_relationship_role.ex | 8+++++---
Msrc/zenflows/vf/agent_relationship_role/domain.ex | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/zenflows/vf/agent_relationship_role/resolv.ex | 8++++++--
Msrc/zenflows/vf/agent_relationship_role/type.ex | 2+-
Msrc/zenflows/vf/agreement.ex | 7++++---
Msrc/zenflows/vf/agreement/domain.ex | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/zenflows/vf/agreement/resolv.ex | 8++++++--
Msrc/zenflows/vf/agreement/type.ex | 2+-
Msrc/zenflows/vf/appreciation.ex | 8+++++---
Msrc/zenflows/vf/claim.ex | 7++++---
Msrc/zenflows/vf/commitment.ex | 118++++++-------------------------------------------------------------------------
Msrc/zenflows/vf/duration.ex | 35++++++++++++-----------------------
Msrc/zenflows/vf/duration/type.ex | 2+-
Msrc/zenflows/vf/economic_event.ex | 273+++++++++++++++++++++++--------------------------------------------------------
Msrc/zenflows/vf/economic_event/domain.ex | 511+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/zenflows/vf/economic_event/resolv.ex | 25++++++++-----------------
Msrc/zenflows/vf/economic_event/type.ex | 2+-
Msrc/zenflows/vf/economic_resource.ex | 28++++++----------------------
Msrc/zenflows/vf/economic_resource/domain.ex | 150++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/zenflows/vf/economic_resource/filter.ex | 68++++++++++++++++++++++++++++++--------------------------------------
Msrc/zenflows/vf/economic_resource/resolv.ex | 8++++++--
Msrc/zenflows/vf/economic_resource/type.ex | 2+-
Msrc/zenflows/vf/event_or_commitment.ex | 53+++++------------------------------------------------
Msrc/zenflows/vf/fulfillment.ex | 7++++---
Msrc/zenflows/vf/intent.ex | 55++++++-------------------------------------------------
Msrc/zenflows/vf/intent/domain.ex | 161+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/zenflows/vf/intent/resolv.ex | 8++++++--
Msrc/zenflows/vf/intent/type.ex | 2+-
Msrc/zenflows/vf/measure.ex | 40++++++++++++++--------------------------
Msrc/zenflows/vf/measure/resolv.ex | 2+-
Msrc/zenflows/vf/measure/type.ex | 2+-
Msrc/zenflows/vf/organization.ex | 10++++++----
Msrc/zenflows/vf/organization/domain.ex | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/zenflows/vf/organization/filter.ex | 43++++++++++++++++++-------------------------
Msrc/zenflows/vf/organization/resolv.ex | 8++++++--
Msrc/zenflows/vf/organization/type.ex | 2+-
Msrc/zenflows/vf/person.ex | 25++++++++++---------------
Msrc/zenflows/vf/person/domain.ex | 126++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/zenflows/vf/person/filter.ex | 71+++++++++++++++++++++++++++++++----------------------------------------
Msrc/zenflows/vf/person/resolv.ex | 26++++++++++++++++++++++++--
Msrc/zenflows/vf/person/type.ex | 10+++++++++-
Msrc/zenflows/vf/plan.ex | 8+++++---
Msrc/zenflows/vf/plan/domain.ex | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/zenflows/vf/plan/resolv.ex | 8++++++--
Msrc/zenflows/vf/plan/type.ex | 2+-
Msrc/zenflows/vf/process.ex | 7++++---
Msrc/zenflows/vf/process/domain.ex | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/zenflows/vf/process/resolv.ex | 8++++++--
Msrc/zenflows/vf/process/type.ex | 2+-
Msrc/zenflows/vf/process_specification.ex | 7++++---
Msrc/zenflows/vf/process_specification/domain.ex | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/zenflows/vf/process_specification/resolv.ex | 8++++++--
Msrc/zenflows/vf/process_specification/type.ex | 2+-
Msrc/zenflows/vf/product_batch.ex | 7++++---
Msrc/zenflows/vf/product_batch/domain.ex | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/zenflows/vf/product_batch/resolv.ex | 8++++++--
Msrc/zenflows/vf/product_batch/type.ex | 2+-
Msrc/zenflows/vf/proposal.ex | 7++++---
Msrc/zenflows/vf/proposal/domain.ex | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/zenflows/vf/proposal/filter.ex | 131++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/zenflows/vf/proposal/resolv.ex | 8++++++--
Msrc/zenflows/vf/proposal/type.ex | 2+-
Msrc/zenflows/vf/proposed_intent.ex | 6++++--
Msrc/zenflows/vf/proposed_intent/domain.ex | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/zenflows/vf/proposed_intent/resolv.ex | 2+-
Msrc/zenflows/vf/proposed_intent/type.ex | 2+-
Msrc/zenflows/vf/proposed_to.ex | 6++++--
Msrc/zenflows/vf/recipe_exchange.ex | 7++++---
Msrc/zenflows/vf/recipe_exchange/domain.ex | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/zenflows/vf/recipe_exchange/resolv.ex | 8++++++--
Msrc/zenflows/vf/recipe_exchange/type.ex | 2+-
Msrc/zenflows/vf/recipe_flow.ex | 35++++++++++-------------------------
Msrc/zenflows/vf/recipe_flow/domain.ex | 134++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/zenflows/vf/recipe_flow/resolv.ex | 8++++++--
Msrc/zenflows/vf/recipe_flow/type.ex | 2+-
Msrc/zenflows/vf/recipe_process.ex | 7++++---
Msrc/zenflows/vf/recipe_process/domain.ex | 119++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/zenflows/vf/recipe_process/resolv.ex | 8++++++--
Msrc/zenflows/vf/recipe_process/type.ex | 2+-
Msrc/zenflows/vf/recipe_resource.ex | 14++++++--------
Msrc/zenflows/vf/recipe_resource/domain.ex | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/zenflows/vf/recipe_resource/resolv.ex | 8++++++--
Msrc/zenflows/vf/recipe_resource/type.ex | 2+-
Msrc/zenflows/vf/resource_specification.ex | 10++++++----
Msrc/zenflows/vf/resource_specification/domain.ex | 123++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/zenflows/vf/resource_specification/resolv.ex | 8++++++--
Msrc/zenflows/vf/resource_specification/type.ex | 2+-
Msrc/zenflows/vf/role_behavior.ex | 7++++---
Msrc/zenflows/vf/role_behavior/domain.ex | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/zenflows/vf/role_behavior/resolv.ex | 8++++++--
Msrc/zenflows/vf/role_behavior/type.ex | 2+-
Msrc/zenflows/vf/satisfaction.ex | 7++++---
Msrc/zenflows/vf/scenario.ex | 7++++---
Msrc/zenflows/vf/scenario/domain.ex | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/zenflows/vf/scenario/resolv.ex | 8++++++--
Msrc/zenflows/vf/scenario/type.ex | 2+-
Msrc/zenflows/vf/scenario_definition.ex | 7++++---
Msrc/zenflows/vf/scenario_definition/domain.ex | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/zenflows/vf/scenario_definition/resolv.ex | 8++++++--
Msrc/zenflows/vf/scenario_definition/type.ex | 2+-
Msrc/zenflows/vf/settlement.ex | 7++++---
Msrc/zenflows/vf/spatial_thing.ex | 7++++---
Msrc/zenflows/vf/spatial_thing/domain.ex | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/zenflows/vf/spatial_thing/resolv.ex | 8++++++--
Msrc/zenflows/vf/spatial_thing/type.ex | 2+-
Msrc/zenflows/vf/time_unit/type.ex | 2+-
Msrc/zenflows/vf/unit.ex | 7++++---
Msrc/zenflows/vf/unit/domain.ex | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/zenflows/vf/unit/resolv.ex | 8++++++--
Msrc/zenflows/vf/unit/type.ex | 2+-
Dsrc/zenflows/vf/validate.ex | 146-------------------------------------------------------------------------------
Atest/db/page.test.exs | 298+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtest/db/paging.test.exs | 298-------------------------------------------------------------------------------
Mtest/file.test.exs | 7+++++--
Mtest/vf/action.test.exs | 12++++++------
Mtest/vf/appreciation.test.exs | 4++--
Mtest/vf/claim.test.exs | 4++--
Mtest/vf/commitment.test.exs | 120++++++++++++++++++++++++++++++++-----------------------------------------------
Mtest/vf/duration.test.exs | 32++++++++++++++++----------------
Mtest/vf/economic_event.test.exs | 192++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtest/vf/economic_event/domain.test.exs | 699+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mtest/vf/economic_resource/domain.test.exs | 3+--
Mtest/vf/event_or_commitment.test.exs | 14+++++++-------
Mtest/vf/fulfillment.test.exs | 4++--
Mtest/vf/measure.test.exs | 40++++++++++++++++++++--------------------
Mtest/vf/proposal.test.exs | 4++--
Mtest/vf/proposed_intent.test.exs | 4++--
Mtest/vf/proposed_to.test.exs | 4++--
Mtest/vf/recipe_flow/domain.test.exs | 8+++++---
Mtest/vf/satisfaction.test.exs | 4++--
Mtest/vf/settlement.test.exs | 4++--
Dtest/vf/validate.test.exs | 245-------------------------------------------------------------------------------
153 files changed, 4600 insertions(+), 3540 deletions(-)

diff --git a/src/zenflows/db/filter.ex b/src/zenflows/db/filter.ex @@ -1,49 +0,0 @@ -# Zenflows is designed to implement the Valueflows vocabulary, -# written and maintained by srfsh <info@dyne.org>. -# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. - -defmodule Zenflows.DB.Filter do -@moduledoc "Filtering helpers for Filter modules." - -alias Ecto.Changeset - -@type params() :: %{atom() => term()} -@type error() :: {:error, Changeset.t()} -@type result() :: {:ok, Ecto.Query.t()} | error() - -def escape_like(v) do - Regex.replace(~r/\\|%|_/, v, &"\\#{&1}") -end - -@doc """ -Changeset helper to check that `a` and `b` are not provided at the same time. -""" -@spec check_xor(Changeset.t(), atom(), atom()) :: Changeset.t() -def check_xor(cset, a, b) do - x = Changeset.get_change(cset, a) - y = Changeset.get_change(cset, b) - - if x && y do - msg = "can't provide both" - - cset - |> Changeset.add_error(a, msg) - |> Changeset.add_error(b, msg) - else - cset - end -end -end diff --git a/src/zenflows/db/page.ex b/src/zenflows/db/page.ex @@ -0,0 +1,93 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.DB.Page do +@moduledoc "Paging utilities to page results." + +import Ecto.Query + +alias Zenflows.DB.{ID, Repo} + +@enforce_keys ~w[dir cur num filter]a +defstruct [:cur, :num, :filter, dir: :forw] + +@typedoc """ +Represents a generic struct that has all required information to +(possibly filter and) paginate the rows of the database. + +The fields are: +* `dir` - represents the *direction* that the database should query. +* `cur` - represents the *cursor* handle to use to paginate. +* `num` - represents the *number* of rows (+1) to fetch. +* `filter` - represents the filters to be used for querying. +""" + +@type t() :: %__MODULE__{ + dir: :forw | :back, + cur: nil | ID.t(), + num: non_neg_integer(), + filter: nil | map(), +} + +@doc """ +Parses a half-baked map/keyword into a `t:t()`. + +You should use this function for functions that expect `t:t()`, and +you don't have any other means to generate a `t:t()` (such as with +`Zenflows.GQL.Connection.parse/2` that converts Relay-specific +paging information into `t:t()`). +""" +@spec new(map() | Keyword.t()) :: t() +def new(_ \\ %{}) +def new(params) when is_list(params), do: Map.new(params) |> new() +def new(params) when is_map(params) do + %__MODULE__{ + dir: params[:dir] || :forw, + cur: params[:cur], + num: params[:num] || Zenflows.GQL.Connection.def_page_size(), + filter: params[:filter], + } +end + +@doc """ +Similar to `c:Ecto.Repo.all/2`, but the result is paginated. + +Basically, transforms the queryable `q` into another query that +limits the amount of returned records according to `dir`, `cur` and +`num` (read `t:t()` on what those variables are used for). +""" +@spec all(Ecto.Queryable.t(), t()) :: [Ecto.Schema.t()] +def all(q, %{dir: dir, cur: cur, num: num}) do + order_by = + case dir do + :forw -> [asc: :id] + :back -> [desc: :id] + end + where = + case {dir, cur} do + {_, nil} -> [] + {:forw, cur} -> dynamic([x], x.id > ^cur) + {:back, cur} -> dynamic([x], x.id < ^cur) + end + from(x in q, + where: ^where, + order_by: ^order_by, + limit: ^num + 1, + select: x) + |> Repo.all() +end +end diff --git a/src/zenflows/db/paging.ex b/src/zenflows/db/paging.ex @@ -1,166 +0,0 @@ -# Zenflows is designed to implement the Valueflows vocabulary, -# written and maintained by srfsh <info@dyne.org>. -# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. - -defmodule Zenflows.DB.Paging do -@moduledoc "Paging helpers for Domain modules." - -import Ecto.Query - -alias Zenflows.DB.{ID, Repo} - -@type result() :: {:ok, t()} | {:error, String.t()} - -@type t() :: %{ - page_info: page_info(), - edges: [edges()], -} - -@type page_info() :: %{ - start_cursor: ID.t() | nil, - end_cursor: ID.t() | nil, - has_previous_page: boolean(), - has_next_page: boolean(), - total_count: non_neg_integer(), - page_limit: non_neg_integer(), -} - -@type edges() :: %{ - cursor: ID.t(), - node: struct(), -} - -@type params() :: %{atom() => term()} - -@spec def_page_size() :: non_neg_integer() -def def_page_size() do - conf()[:def_page_size] -end - -@spec max_page_size() :: non_neg_integer() -def max_page_size() do - conf()[:max_page_size] -end - -@spec conf() :: Keyword.t() -defp conf() do - Application.fetch_env!(:zenflows, Zenflows.GQL) -end - -@spec parse(params()) :: {:error, String.t()} | {:ok, {:forw | :back, ID.t() | nil, non_neg_integer()}} -def parse(%{first: _, last: _}), do: {:error, "first and last can't be provided at the same time"} -def parse(%{after: _, before: _}), do: {:error, "after and before can't be provided at the same time"} - -def parse(%{after: _, last: _}), do: {:error, "after and last can't be provided at the same time"} -def parse(%{before: _, first: _}), do: {:error, "before and first can't be provided at the same time"} - -def parse(%{first: num}) when num < 0, do: {:error, "first must be positive"} -def parse(%{last: num}) when num < 0, do: {:error, "last must be positive"} - -def parse(%{after: cur, first: num}), do: {:ok, {:forw, cur, normalize(num)}} -def parse(%{after: cur}), do: {:ok, {:forw, cur, def_page_size()}} - -def parse(%{before: cur, last: num}), do: {:ok, {:back, cur, normalize(num)}} -def parse(%{before: cur}), do: {:ok, {:back, cur, def_page_size()}} - -def parse(%{first: num}), do: {:ok, {:forw, nil, normalize(num)}} -def parse(%{last: num}), do: {:ok, {:back, nil, normalize(num)}} - -def parse(_), do: {:ok, {:forw, nil, def_page_size()}} - -@spec normalize(integer()) :: non_neg_integer() -defp normalize(num) do - # credo:disable-for-next-line Credo.Check.Refactor.MatchInCondition - if num > (max = max_page_size()), - do: max, - else: num -end - -@doc """ -Page Ecto schemas. - -Only supports forward or backward paging with or without cursors. -""" -@spec page(atom() | Ecto.Query.t(), params()) :: result() -def page(schema_or_query, params) do - with {:ok, {dir, cur, num}} <- parse(params) do - {page_fun, order_by} = - case dir do - :forw -> {&forw/3, [asc: :id]} - :back -> {&back/3, [desc: :id]} - end - where = - case {dir, cur} do - {_, nil} -> [] - {:forw, cur} -> dynamic([s], s.id > ^cur) - {:back, cur} -> dynamic([s], s.id < ^cur) - end - {:ok, - from(s in schema_or_query, - where: ^where, - order_by: ^order_by, - limit: ^num + 1, - select: %{cursor: s.id, node: s}) - |> Repo.all() - |> page_fun.(cur, num)} - end -end - -@spec forw(edges(), ID.t() | nil, non_neg_integer()) :: t() -def forw(edges, cur, num) do - {edges, count} = - Enum.reduce(edges, {[], 0}, fn e, {edges, count} -> - {[e | edges], count + 1} - end) - - {edges, has_next?, count} = - # we indeed have fetched num+1 records - if count - 1 == num do - [_ | edges] = edges - {edges, true, count - 1} - else - {edges, false, count} - end - - {edges, first, last} = - case edges do - [] -> {[], nil, nil} - _ -> - [last | _] = edges - [first | _] = edges = Enum.reverse(edges) - {edges, first, last} - end - - %{ - edges: edges, - page_info: %{ - start_cursor: first[:cursor], - end_cursor: last[:cursor], - has_next_page: has_next?, - has_previous_page: cur != nil, - total_count: count, - page_limit: num, - }, - } -end - -@spec back(edges(), ID.t() | nil, non_neg_integer()) :: t() -def back(edges, cur, num) do - # Currently, this part of the algorithm doesn't care about - # whether we do forward or backward paging. - forw(edges, cur, num) -end -end diff --git a/src/zenflows/db/schema.ex b/src/zenflows/db/schema.ex @@ -20,21 +20,14 @@ defmodule Zenflows.DB.Schema do Just a wrapper around Ecto.Schema to customize it. """ -@type params() :: %{required(binary()) => term()} | %{required(atom()) => term()} +@type t() :: Ecto.Schema.t() @type id() :: Zenflows.DB.ID.t() +@type params() :: %{required(binary()) => term()} | %{required(atom()) => term()} -defmacro __using__(opts) do - types? = Keyword.get(opts, :types?, true) - +defmacro __using__(_) do quote do use Ecto.Schema - alias Ecto.{Changeset, Schema} - - if unquote(types?) do - @typep params() :: Zenflows.DB.Schema.params() - end - @primary_key {:id, Zenflows.DB.ID, autogenerate: true} @foreign_key_type Zenflows.DB.ID @timestamps_opts type: :utc_datetime_usec, inserted_at: false diff --git a/src/zenflows/db/validate.ex b/src/zenflows/db/validate.ex @@ -0,0 +1,371 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.DB.Validate do +@moduledoc "Ecto.Changeset validation helpers." + +alias Ecto.Changeset + +require Logger + +@typedoc """ +Used to specify whether to fetch the field's value from the `:changes`, +`:data`, or `:both` of an `Ecto.Changeset`, respectively. + +Basically, uses `Ecto.Changeset.fetch_field/2` behind the scenes, +but `nil` values will mean empty/not-provided. +""" +@type fetch_method() :: :change | :data | :both + +@doc """ +Escape possible characters that could represent expressions in SQL's +`LIKE` keyword of a field. The value is changed if it is present. +""" +@spec escape_like(Changeset.t(), atom()) :: Changeset.t() +def escape_like(cset, field) do + case Changeset.fetch_change(cset, field) do + {:ok, v} -> + v = Regex.replace(~r/\\|%|_/, v, &"\\#{&1}") + Changeset.put_change(cset, field, v) + :error -> cset + end +end + +@doc """ +Use the OR logic on the existance of `fields`, and error if the +result is false. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. + +Here's a truth table as a quick reference: + +| p | q | = | +|---|---|---| +| 0 | 0 | 0 | +| 1 | 0 | 1 | +| 0 | 1 | 1 | +| 1 | 1 | 1 | +""" +@spec exist_or(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def exist_or(cset, fields, opts \\ []) do + meth = Keyword.get(opts, :method, :change) + if Enum.any?(fields, &field_exists?(meth, cset, &1)) do + cset + else + Enum.reduce(fields, cset, + &Changeset.add_error(&2, &1, "at least one of them must be provided")) + end +end + +@doc """ +Use the XOR logic on the existance of `fields`, and error if the +result is false. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. + +Here's a truth table as a quick reference: + +| p | q | = | +|---|---|---| +| 0 | 0 | 0 | +| 1 | 0 | 1 | +| 0 | 1 | 1 | +| 1 | 1 | 0 | +""" +@spec exist_xor(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def exist_xor(cset, [h | t] = fields, opts \\ []) do + meth = Keyword.get(opts, :method, :change) + h_exists? = field_exists?(meth, cset, h) + + if Enum.all?(t, &(h_exists? != field_exists?(meth, cset, &1))) do + cset + else + Enum.reduce(fields, cset, + &Changeset.add_error(&2, &1, "exactly one of them must be provided")) + end +end + +@doc """ +Use the NAND logic on the existance of `fields`, and error if the +result is false. + +Note: `fields` must contain at least 2 items, and make sure to +read `t:fetch_method()`'s doc. + +Here's a truth table as a quick reference: + +| p | q | = | +|---|---|---| +| 0 | 0 | 1 | +| 1 | 0 | 1 | +| 0 | 1 | 1 | +| 1 | 1 | 0 | +""" +@spec exist_nand(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def exist_nand(cset, fields, opts \\ []) do + meth = Keyword.get(opts, :method, :change) + if Enum.all?(fields, &field_exists?(meth, cset, &1)) do + Enum.reduce(fields, cset, + &Changeset.add_error(&2, &1, "one or none of them must be provided")) + else + cset + end +end + +@doc """ +Compare the values of `fields`, and error if they aren't equal to +each other. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. Also, empty fields will be skipped to +allow more flexibility. Use `Ecto.Changeset.validate_required/3` +to achieve otherwise. + +Here's a truth table as a quick reference: + +| p | q | = | +|---|---|---| +| 0 | 0 | 1 | +| 1 | 0 | 0 | +| 0 | 1 | 0 | +| 1 | 1 | 1 | +""" +@spec value_eq(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def value_eq(cset, [a, b], opts \\ []) do + meth = Keyword.get(opts, :method, :change) + with {:ok, x} <- field_fetch(meth, cset, a), + {:ok, y} <- field_fetch(meth, cset, b) do + if x == y do + cset + else + msg = "all of them must be the same" + cset + |> Changeset.add_error(a, msg) + |> Changeset.add_error(b, msg) + end + else _ -> + cset + end +end + +@doc """ +Compare the values of `fields`, and error if they aren't all +different. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. Also, empty fields will be skipped to +allow more flexibility. Use `Ecto.Changeset.validate_required/3` +to achieve otherwise. + +Here's a truth table as a quick reference: + +| p | q | = | +|---|---|---| +| 0 | 0 | 1 | +| 1 | 0 | 1 | +| 0 | 1 | 1 | +| 1 | 1 | 0 | +""" +@spec value_ne(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def value_ne(cset, [a, b], opts \\ []) do + meth = Keyword.get(opts, :method, :change) + with {:ok, x} <- field_fetch(meth, cset, a), + {:ok, y} <- field_fetch(meth, cset, b) do + if x != y do + cset + else + msg = "all of them must be different" + cset + |> Changeset.add_error(a, msg) + |> Changeset.add_error(b, msg) + end + else _ -> + cset + end +end + +@doc "Validate that given `field` is a valid email address." +@spec email(Changeset.t(), atom()) :: Changeset.t() +def email(cset, field) do + # works good enough for now + Changeset.validate_format(cset, field, ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/) +end + +@doc """ +Validate that given `field` is [1, 256] bytes long. + +The name "name" is a reference to short texts, as in email subject, +usernames, full names, and so on. +""" +@spec name(Changeset.t(), atom()) :: Changeset.t() +def name(cset, field), do: byte_range(cset, field, 1, 256) + +@doc """ +Validate that given `field` is [1, 2048] bytes long. + +The name "note" is a reference to long texts, as in email bodies, +descriptions, notes, and so on. +""" +@spec note(Changeset.t(), atom()) :: Changeset.t() +def note(cset, field), do: byte_range(cset, field, 1, 2048) + +@doc """ +Validate that given `field` is [16, 2048] bytes long. + +The name "key" is a reference to encoded cryptographic keys (so the +size is not the size of the binary key). +""" +@spec key(Changeset.t(), atom()) :: Changeset.t() +def key(cset, field), do: byte_range(cset, field, 16, 2048) + +@doc """ +Validate that given `field` is [1, 512] bytes long. + +The name "uri" is a reference to not-literal URIs, but a size used +for mostly URIs. +""" +@spec uri(Changeset.t(), atom()) :: Changeset.t() +def uri(cset, field), do: byte_range(cset, field, 1, 512) + +@doc """ +Validate that given `field` is [1B, 25MiB] long, and log a warning +if it is longer than 4MiB. + +The name "img" is a reference to Base64-encoded image binary data. +""" +@spec img(Changeset.t(), atom()) :: Changeset.t() +def img(cset, field) do + Changeset.validate_change(cset, field, __MODULE__, fn + _, str when byte_size(str) < 1 -> + [{field, "should be at least 1B long"}] + _, str when byte_size(str) > 25 * 1024 * 1024 -> + [{field, "should be at most 25MiB long"}] + _, str when byte_size(str) > 4 * 1024 * 1024 -> + Logger.warning("file exceeds 4MiB") + [] + _, _ -> + [] + end) +end + +@doc """ +Validate that given `field` is a list of binaries, which are [1, +512] bytes long in size, and the list itself contains [1, 128] +items. + +The name "class" is a reference to list of strings used for tagging, +categorization and so on. +""" +@spec class(Changeset.t(), atom()) :: Changeset.t() +def class(cset, field) do + Changeset.validate_change(cset, field, __MODULE__, fn + _, [] -> + [{field, "must contain at least 1 item"}] + _, list -> + case do_class(list, 0, 128) do + {:exceeds, _ind} -> + [{field, "must contain at most 128 items"}] + {:short, ind} -> + [{field, "the item at #{ind + 1} cannot be shorter than 1 byte"}] + {:long, ind} -> + [{field, "the item at #{ind + 1} cannot be longer than 512 bytes"}] + {:valid, _ind} -> + [] + end + end) +end + +# The rationale of this function is to loop over the list while +# decreasing `rem` (reminder) and increasing `ind` (index) until +# either one of these happen (in that order): +# +# 1. `rem` is equal to 0 +# 2. one of the items in the list is shorter than 1 byte long +# 3. one of the items in the list is longer than 512 bytes long +# +@spec do_class([String.t()], non_neg_integer(), non_neg_integer()) + :: {:exceeds | :short | :long | :valid, non_neg_integer()} +defp do_class([], ind, _), do: {:valid, ind - 1} +defp do_class([h | t], ind, rem) do + cond do + rem == 0 -> {:exceeds, ind} + byte_size(h) < 1 -> {:short, ind} + byte_size(h) > 512 -> {:long, ind} + true -> do_class(t, ind + 1, rem - 1) + end +end + +@doc """ +Validate that the given binary (in Elixir terms) is in this inclusive +octects/bytes range. +""" +@spec byte_range(Changeset.t(), atom(), non_neg_integer(), non_neg_integer()) + :: Changeset.t() +def byte_range(cset, field, min, max) do + Changeset.validate_change(cset, field, __MODULE__, fn + _, bin when byte_size(bin) < min -> + [{field, "should be at least #{min} byte(s) long"}] + _, bin when byte_size(bin) > max -> + [{field, "should be at most #{max} byte(s) long"}] + _, _ -> + [] + end) +end + +@spec field_exists?(fetch_method(), Changeset.t(), atom()) :: boolean() +defp field_exists?(:change, cset, field) do + case Changeset.fetch_field(cset, field) do + {:changes, x} when not is_nil(x) -> true + _ -> false + end +end +defp field_exists?(:data, cset, field) do + case Changeset.fetch_field(cset, field) do + {:data, x} when not is_nil(x) -> true + _ -> false + end +end +defp field_exists?(:both, cset, field) do + case Changeset.fetch_field(cset, field) do + {:changes, x} when not is_nil(x) -> true + {:data, x} when not is_nil(x) -> true + _ -> false + end +end + +@spec field_fetch(fetch_method(), Changeset.t(), atom()) :: {:ok, term()} | :error +defp field_fetch(:change, cset, field) do + case Changeset.fetch_field(cset, field) do + {:changes, v} -> {:ok, v} + _ -> :error + end +end +defp field_fetch(:data, cset, field) do + case Changeset.fetch_field(cset, field) do + {:data, v} -> {:ok, v} + _ -> :error + end +end +defp field_fetch(:both, cset, field) do + case Changeset.fetch_field(cset, field) do + :error -> :error + {_, v} -> {:ok, v} + end +end +end diff --git a/src/zenflows/file.ex b/src/zenflows/file.ex @@ -24,13 +24,14 @@ use Zenflows.DB.Schema require Logger +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Agent, EconomicResource, Intent, RecipeResource, ResourceSpecification, - Validate, } @type t() :: %__MODULE__{ @@ -78,8 +79,8 @@ end resource_specification_id intent_id ]a -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/file/domain.ex b/src/zenflows/file/domain.ex @@ -18,13 +18,11 @@ defmodule Zenflows.File.Domain do @moduledoc "Domain logic of Files." -alias Zenflows.DB.{Paging, Repo} +alias Ecto.Changeset +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.File -@typep repo() :: Ecto.Repo.t() -@typep id() :: Zenflows.DB.Schema.id() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, File.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -35,8 +33,20 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(File, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: File.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [File.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(File, page)} +end + +@spec all!(Page.t()) :: [File.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end end diff --git a/src/zenflows/gql/connection.ex b/src/zenflows/gql/connection.ex @@ -0,0 +1,143 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.GQL.Connection do +@moduledoc "GraphQL Relay Connection helpers." + +alias Ecto.Changeset +alias Zenflows.DB.{ID, Page, Schema, Validate} + +@enforce_keys [:page_info, :edges] +defstruct @enforce_keys + +@type t() :: %__MODULE__{ + page_info: page_info(), + edges: [edge()], +} + +@type page_info() :: %{ + start_cursor: ID.t() | nil, + end_cursor: ID.t() | nil, + has_previous_page: boolean(), + has_next_page: boolean(), + total_count: non_neg_integer(), + page_limit: non_neg_integer(), +} + +@type edge() :: %{ + cursor: ID.t(), + node: Ecto.Schema.t(), +} + +@doc """ +Converts the given list of schemas (that you get by using +`Zenflows.DB.Page.all()`) into a Relay connection. +""" +@spec from_list([Ecto.Schema.t()], Page.t()) :: t() +def from_list(records, %{cur: cur, num: num}) do + # Currently, we don't differenciate between forwards or + # backwards paging, so we only care whether `cur` is nil or + # not. + + {edges, count} = + Enum.reduce(records, {[], 0}, fn r, {edges, count} -> + {[%{cursor: r.id, node: r} | edges], count + 1} + end) + + {edges, has_next?, count} = + # we indeed have fetched num+1 records + if count - 1 == num do + [_ | edges] = edges + {edges, true, count - 1} + else + {edges, false, count} + end + + {edges, first, last} = + case edges do + [] -> {[], nil, nil} + _ -> + [last | _] = edges + [first | _] = edges = Enum.reverse(edges) + {edges, first, last} + end + + %__MODULE__{ + edges: edges, + page_info: %{ + start_cursor: first[:cursor], + end_cursor: last[:cursor], + has_next_page: has_next?, + has_previous_page: cur != nil, + total_count: count, + page_limit: num, + }, + } +end + +@doc """ +Parses a Relay-specific map with filters into a generic +`t:Zenflows.DB.Page.t()`. +""" +@spec parse(Schema.params()) :: {:ok, Page.t()} | {:error, Changeset.t()} +def parse(params) do + with {:ok, data} <- Changeset.apply_action(changeset(params), nil) do + after_ = data[:after] + first = data[:first] + before = data[:before] + last = data[:last] + + {:ok, %Page{ + dir: if(before || last, do: :back, else: :forw), + cur: after_ || before, + num: if((n = first || last), do: normalize(n), else: def_page_size()), + filter: data[:filter], + }} + end +end + +@doc false +@spec changeset(Schema.params()) :: Changeset.t() +def changeset(params) do + {%{}, %{after: ID, first: :integer, before: ID, last: :integer, filter: :map}} + |> Changeset.cast(params, [:after, :first, :before, :last, :filter]) + |> Changeset.validate_number(:first, greater_than_or_equal_to: 0) + |> Changeset.validate_number(:last, greater_than_or_equal_to: 0) + |> Validate.exist_nand([:first, :last]) + |> Validate.exist_nand([:after, :before]) + |> Validate.exist_nand([:after, :last]) + |> Validate.exist_nand([:before, :first]) +end + +@spec normalize(non_neg_integer()) :: non_neg_integer() +defp normalize(num) do + # credo:disable-for-next-line Credo.Check.Refactor.MatchInCondition + if num > (max = max_page_size()), + do: max, else: num +end + +@doc "The default page size for paging." +@spec def_page_size() :: non_neg_integer() +def def_page_size(), do: Keyword.fetch!(conf(), :def_page_size) + +@doc "The maximum page size for paging." +@spec max_page_size() :: non_neg_integer() +def max_page_size(), do: Keyword.fetch!(conf(), :max_page_size) + +@spec conf() :: Keyword.t() +defp conf(), do: Application.fetch_env!(:zenflows, Zenflows.GQL) +end diff --git a/src/zenflows/keypairoom/domain.ex b/src/zenflows/keypairoom/domain.ex @@ -24,14 +24,15 @@ tasks. alias Zenflows.Restroom alias Zenflows.VF.Person -def keypairoom_server(false, data) do - if Person.Domain.exists?(email: Map.fetch!(data, "email")), +@spec keypairoom_server(boolean(), map()) :: {:ok, String.t()} | {:error, term()} +def keypairoom_server(false, %{"email" => email} = data) do + if Person.Domain.exists?(email: email), do: Restroom.keypairoom_server(data), else: {:error, "email doesn't exists"} end -def keypairoom_server(true, data) do - if Person.Domain.exists?(email: Map.fetch!(data, "email")), +def keypairoom_server(true, %{"email" => email} = data) do + if Person.Domain.exists?(email: email), do: {:error, "email exists"}, else: Restroom.keypairoom_server(data) end diff --git a/src/zenflows/sw_pass/domain.ex b/src/zenflows/sw_pass/domain.ex @@ -66,7 +66,7 @@ def import_repos(url) do case repo.get_by(ResourceSpecification, params) do nil -> params - |> ResourceSpecification.chgset() + |> ResourceSpecification.changeset() |> repo.insert() res_spec -> {:ok, res_spec} end @@ -77,7 +77,7 @@ def import_repos(url) do case repo.get_by(ResourceSpecification, params) do nil -> params - |> ResourceSpecification.chgset() + |> ResourceSpecification.changeset() |> repo.insert() res_spec -> {:ok, res_spec} end @@ -88,7 +88,7 @@ def import_repos(url) do case repo.get_by(Unit, params) do nil -> params - |> Unit.chgset() + |> Unit.changeset() |> repo.insert() unit -> {:ok, unit} end @@ -99,7 +99,7 @@ def import_repos(url) do case repo.get_by(Process, params) do nil -> params - |> Process.chgset() + |> Process.changeset() |> repo.insert() proc -> {:ok, proc} end @@ -112,7 +112,7 @@ def import_repos(url) do case repo.get_by(Person, params) do nil -> params - |> Person.chgset() + |> Person.changeset() |> repo.insert() per -> {:ok, per} end @@ -132,7 +132,7 @@ def import_repos(url) do nil -> params |> Map.put(:has_point_in_time, now) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() |> repo.insert() evt -> {:ok, evt} end @@ -165,7 +165,7 @@ def import_repos(url) do has_numerical_value: 1, }) |> Map.put(:has_point_in_time, now) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() |> repo.insert() evt -> {:ok, evt} end @@ -185,7 +185,7 @@ def import_repos(url) do case repo.get_by(EconomicResource, params) do nil -> params - |> EconomicResource.chgset() + |> EconomicResource.changeset() |> repo.insert() res -> {:ok, res} end diff --git a/src/zenflows/validate.ex b/src/zenflows/validate.ex @@ -0,0 +1,326 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.Validate do +@moduledoc "Ecto.Changeset validation helpers." + +alias Ecto.Changeset + +require Logger + +@typedoc """ +Used to specify whether to fetch the field's value from the `:changes`, +`:data`, or `:both` of an `Ecto.Changeset`, respectively. + +Basically, uses `Ecto.Changeset.fetch_field/2` behind the scenes, +but `nil` values will mean empty/not-provided. +""" +@type fetch_method() :: :change | :data | :both + +@doc """ +Escape possible characters that could represent expressions in SQL's +`LIKE` keyword of a field. The value is changed if it is present. +""" +@spec escape_like(Changeset.t(), atom()) :: Changeset.t() +def escape_like(cset, field) do + case Changeset.fetch_change(cset, :field) do + {:ok, v} -> + v = Regex.replace(~r/\\|%|_/, v, &"\\#{&1}") + Changeset.put_change(cset, field, v) + :error -> cset + end +end + +@doc """ +Use the OR logic on the existance of `fields`, and error if the +result is false. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. +""" +@spec exist_or(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def exist_or(cset, fields, opts \\ []) do + meth = Keyword.get(opts, :method, :change) + if Enum.any?(fields, &field_exists?(meth, cset, &1)) do + cset + else + Enum.reduce(fields, cset, + &Changeset.add_error(&2, &1, "at least one of them must be provided")) + end +end + +@doc """ +Use the XOR logic on the existance of `fields`, and error if the +result is false. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. +""" +@spec exist_xor(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def exist_xor(cset, [h | t] = fields, opts \\ []) do + meth = Keyword.get(opts, :method, :change) + h_exists? = field_exists?(meth, cset, h) + + if Enum.all?(t, &(h_exists? != field_exists?(meth, cset, &1))) do + cset + else + Enum.reduce(fields, cset, + &Changeset.add_error(&2, &1, "exactly one of them must be provided")) + end +end + +@doc """ +Use the NAND logic on the existance of `fields`, and error if the +result is false. + +Note: `fields` must contain at least 2 items, and make sure to +read `t:fetch_method()`'s doc. +""" +@spec exist_nand(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def exist_nand(cset, fields, opts \\ []) do + meth = Keyword.get(opts, :method, :change) + if Enum.all?(fields, &field_exists?(meth, cset, &1)) do + Enum.reduce(fields, cset, + &Changeset.add_error(&2, &1, "one or none of them must be provided")) + else + cset + end +end + +@doc """ +Compare the values of `fields`, and error if they aren't equal to +each other. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. Also, empty fields will be skipped to +allow more flexibility. Use `Ecto.Changeset.validate_required/3` +to achieve otherwise. +""" +@spec value_eq(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def value_eq(cset, [a, b], opts \\ []) do + meth = Keyword.get(opts, :method, :change) + with {:ok, x} <- field_fetch(meth, cset, a), + {:ok, y} <- field_fetch(meth, cset, b) do + if x == y do + cset + else + msg = "all of them must be the same" + cset + |> Changeset.add_error(a, msg) + |> Changeset.add_error(b, msg) + end + else _ -> + cset + end +end + +@doc """ +Compare the values of `fields`, and error if they aren't all +different. + +Note: `fields` must contain at least 2 items, and make sure to read +`t:fetch_method()`'s doc. Also, empty fields will be skipped to +allow more flexibility. Use `Ecto.Changeset.validate_required/3` +to achieve otherwise. +""" +@spec value_ne(Changeset.t(), [atom(), ...], Keyword.t()) :: Changeset.t() +def value_ne(cset, [a, b], opts \\ []) do + meth = Keyword.get(opts, :method, :change) + with {:ok, x} <- field_fetch(meth, cset, a), + {:ok, y} <- field_fetch(meth, cset, b) do + if x != y do + cset + else + msg = "all of them must be different" + cset + |> Changeset.add_error(a, msg) + |> Changeset.add_error(b, msg) + end + else _ -> + cset + end +end + +@doc "Validate that given `field` is a valid email address." +@spec email(Changeset.t(), atom()) :: Changeset.t() +def email(cset, field) do + # works good enough for now + Changeset.validate_format(cset, field, ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/) +end + +@doc """ +Validate that given `field` is [1, 256] bytes long. + +The name "name" is a reference to short texts, as in email subject, +usernames, full names, and so on. +""" +@spec name(Changeset.t(), atom()) :: Changeset.t() +def name(cset, field), do: byte_range(cset, field, 1, 256) + +@doc """ +Validate that given `field` is [1, 2048] bytes long. + +The name "note" is a reference to long texts, as in email bodies, +descriptions, notes, and so on. +""" +@spec note(Changeset.t(), atom()) :: Changeset.t() +def note(cset, field), do: byte_range(cset, field, 1, 2048) + +@doc """ +Validate that given `field` is [16, 2048] bytes long. + +The name "key" is a reference to encoded cryptographic keys (so the +size is not the size of the binary key). +""" +@spec key(Changeset.t(), atom()) :: Changeset.t() +def key(cset, field), do: byte_range(cset, field, 16, 2048) + +@doc """ +Validate that given `field` is [1, 512] bytes long. + +The name "uri" is a reference to not-literal URIs, but a size used +for mostly URIs. +""" +@spec uri(Changeset.t(), atom()) :: Changeset.t() +def uri(cset, field), do: byte_range(cset, field, 1, 512) + +@doc """ +Validate that given `field` is [1B, 25MiB] long, and log a warning +if it is longer than 4MiB. + +The name "img" is a reference to Base64-encoded image binary data. +""" +@spec img(Changeset.t(), atom()) :: Changeset.t() +def img(cset, field) do + Changeset.validate_change(cset, field, __MODULE__, fn + _, str when byte_size(str) < 1 -> + [{field, "should be at least 1B long"}] + _, str when byte_size(str) > 25 * 1024 * 1024 -> + [{field, "should be at most 25MiB long"}] + _, str when byte_size(str) > 4 * 1024 * 1024 -> + Logger.warning("file exceeds 4MiB") + [] + _, _ -> + [] + end) +end + +@doc """ +Validate that given `field` is a list of binaries, which are [1, +512] bytes long in size, and the list itself contains [1, 128] +items. + +The name "class" is a reference to list of strings used for tagging, +categorization and so on. +""" +@spec class(Changeset.t(), atom()) :: Changeset.t() +def class(cset, field) do + Changeset.validate_change(cset, field, __MODULE__, fn + _, [] -> + [{field, "must contain at least 1 item"}] + _, list -> + case do_class(list, 0, 128) do + {:exceeds, _ind} -> + [{field, "must contain at most 128 items"}] + {:short, ind} -> + [{field, "the item at #{ind + 1} cannot be shorter than 1 byte"}] + {:long, ind} -> + [{field, "the item at #{ind + 1} cannot be longer than 512 bytes"}] + {:valid, _ind} -> + [] + end + end) +end + +# The rationale of this function is to loop over the list while +# decreasing `rem` (reminder) and increasing `ind` (index) until +# either one of these happen (in that order): +# +# 1. `rem` is equal to 0 +# 2. one of the items in the list is shorter than 1 byte long +# 3. one of the items in the list is longer than 512 bytes long +# +@spec do_class([String.t()], non_neg_integer(), non_neg_integer()) + :: {:exceeds | :short | :long | :valid, non_neg_integer()} +defp do_class([], ind, _), do: {:valid, ind - 1} +defp do_class([h | t], ind, rem) do + cond do + rem == 0 -> {:exceeds, ind} + byte_size(h) < 1 -> {:short, ind} + byte_size(h) > 512 -> {:long, ind} + true -> do_class(t, ind + 1, rem - 1) + end +end + +@doc """ +Validate that the given binary (in Elixir terms) is in this inclusive +octects/bytes range. +""" +@spec byte_range(Changeset.t(), atom(), non_neg_integer(), non_neg_integer()) + :: Changeset.t() +def byte_range(cset, field, min, max) do + Changeset.validate_change(cset, field, __MODULE__, fn + _, bin when byte_size(bin) < min -> + [{field, "should be at least #{min} byte(s) long"}] + _, bin when byte_size(bin) > max -> + [{field, "should be at most #{max} byte(s) long"}] + _, _ -> + [] + end) +end + +@spec field_exists?(fetch_method(), Changeset.t(), atom()) :: boolean() +defp field_exists?(:change, cset, field) do + case Changeset.fetch_field(cset, field) do + {:changes, x} when not is_nil(x) -> true + _ -> false + end +end +defp field_exists?(:data, cset, field) do + case Changeset.fetch_field(cset, field) do + {:data, x} when not is_nil(x) -> true + _ -> false + end +end +defp field_exists?(:both, cset, field) do + case Changeset.fetch_field(cset, field) do + {:changes, x} when not is_nil(x) -> true + {:data, x} when not is_nil(x) -> true + _ -> false + end +end + +@spec field_fetch(fetch_method(), Changeset.t(), atom()) :: {:ok, term()} | :error +defp field_fetch(:change, cset, field) do + case Changeset.fetch_field(cset, field) do + {:changes, v} -> {:ok, v} + _ -> :error + end +end +defp field_fetch(:data, cset, field) do + case Changeset.fetch_field(cset, field) do + {:data, v} -> {:ok, v} + _ -> :error + end +end +defp field_fetch(:both, cset, field) do + case Changeset.fetch_field(cset, field) do + :error -> :error + {_, v} -> {:ok, v} + end +end +end diff --git a/src/zenflows/vf/action/type.ex b/src/zenflows/vf/action/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Action.Type do -@moduledoc "GraphQL types of Actions." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/agent.ex b/src/zenflows/vf/agent.ex @@ -20,7 +20,7 @@ defmodule Zenflows.VF.Agent do A person or group or organization with economic agency. """ -use Zenflows.DB.Schema, types?: false +use Zenflows.DB.Schema alias Zenflows.File alias Zenflows.VF.SpatialThing diff --git a/src/zenflows/vf/agent/domain.ex b/src/zenflows/vf/agent/domain.ex @@ -18,13 +18,12 @@ defmodule Zenflows.VF.Agent.Domain do @moduledoc "Domain logic of Agents." -alias Zenflows.DB.{Paging, Repo} +alias Ecto.Changeset +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{Agent, Agent.Filter} -@typep repo() :: Ecto.Repo.t() -@typep id() :: Zenflows.DB.Schema.id() - -@spec one(repo(), id() | map() | Keyword.t()) :: {:ok, Agent.t()} | {:error, String.t()} +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: {:ok, Agent.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) def one(repo, clauses) do @@ -34,19 +33,27 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Filter.error() | Paging.result() -def all(params \\ %{}) do - with {:ok, q} <- Filter.filter(params[:filter] || %{}) do - Paging.page(q, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Agent.t() +def one!(repo \\ Repo, x) do + {:ok, found} = one(repo, x) + found +end + +@spec all(Page.t()) :: {:ok, [Agent.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + with {:ok, q} <- Filter.all(page) do + {:ok, Page.all(q, page)} end end -@spec preload(Agent.t(), :images | :primary_location) :: Agent.t() -def preload(agent, :images) do - Repo.preload(agent, :images) +@spec all!(Page.t()) :: [Agent.t()] +def all!(page \\ Page.new()) do + {:ok, q} = Filter.all(page) + Page.all(q, page) end -def preload(agent, :primary_location) do - Repo.preload(agent, :primary_location) +@spec preload(Agent.t(), :images | :primary_location) :: Agent.t() +def preload(agent, x) when x in ~w[images primary_location]a do + Repo.preload(agent, x) end end diff --git a/src/zenflows/vf/agent/filter.ex b/src/zenflows/vf/agent/filter.ex @@ -16,40 +16,33 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Agent.Filter do -@moduledoc "Filtering logic of Agents." - -use Zenflows.DB.Schema +@moduledoc false import Ecto.Query -alias Ecto.Query -alias Zenflows.DB.Filter -alias Zenflows.VF.{Agent, Validate} - -@type error() :: Filter.error() +alias Ecto.{Changeset, Queryable} +alias Zenflows.DB.{Page, Schema, Validate} +alias Zenflows.VF.Agent -@spec filter(Filter.params()) :: Filter.result() -def filter(params) do - case chgset(params) do - %{valid?: true, changes: c} -> - {:ok, Enum.reduce(c, Agent, &f(&2, &1))} - %{valid?: false} = cset -> - {:error, cset} +@spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def all(%{filter: nil}), do: {:ok, Agent} +def all(%{filter: params}) do + with {:ok, filters} <- all_validate(params) do + Enum.reduce(filters, Agent, &all_f(&2, &1)) end end -@spec f(Query.t(), {atom(), term()}) :: Query.t() -defp f(q, {:name, v}), - do: where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) - -embedded_schema do - field :name, :string -end +@spec all_f(Queryable.t(), {atom(), term()}) :: Queryable.t() +defp all_f(q, {:name, v}), + do: where(q, [x], ilike(x.name, ^"%#{v}%")) -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do - %__MODULE__{} +@spec all_validate(Schema.params()) + :: {:ok, Changeset.data()} | {:error, Changeset.t()} +defp all_validate(params) do + {%{}, %{name: :string}} |> Changeset.cast(params, [:name]) |> Validate.name(:name) + |> Validate.escape_like(:name) + |> Changeset.apply_action(nil) end end diff --git a/src/zenflows/vf/agent/resolv.ex b/src/zenflows/vf/agent/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Agent.Resolv do -@moduledoc "Resolvers of Agents." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.Agent.Domain def my_agent(_, %{context: %{req_user: user}}) do @@ -29,7 +30,10 @@ def agent(params, _) do end def agents(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def images(agent, _, _) do diff --git a/src/zenflows/vf/agent/type.ex b/src/zenflows/vf/agent/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Agent.Type do -@moduledoc "GraphQL types of Agents." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/agent_relationship.ex b/src/zenflows/vf/agent_relationship.ex @@ -23,10 +23,11 @@ with another. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Agent, AgentRelationshipRole, - Validate, } @type t() :: %__MODULE__{ @@ -50,8 +51,8 @@ end @cast @reqr ++ [:note] # in_scope_of @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/agent_relationship/domain.ex b/src/zenflows/vf/agent_relationship/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.AgentRelationship.Domain do @moduledoc "Domain logic of AgentRelationships." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.AgentRelationship -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, AgentRelationship.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,61 +33,112 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(AgentRelationship, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: AgentRelationship.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value end -@spec create(params()) :: {:ok, AgentRelationship.t()} | {:error, chgset()} +@spec all(Page.t()) :: {:ok, [AgentRelationship.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(AgentRelationship, page)} +end + +@spec all!(Page.t()) :: [AgentRelationship.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) + :: {:ok, AgentRelationship.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, AgentRelationship.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: ar}} -> {:ok, ar} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, AgentRelationship.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: AgentRelationship.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, AgentRelationship.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &AgentRelationship.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: ar}} -> {:ok, ar} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, AgentRelationship.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: AgentRelationship.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, AgentRelationship.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, &(&1.one)) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: ar}} -> {:ok, ar} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id) :: AgentRelationship.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(AgentRelationship.t(), :subject | :object | :relationship) :: AgentRelationship.t() -def preload(rel, :subject) do - Repo.preload(rel, :subject) +def preload(rel, x) when x in ~w[subject object relationship]a do + Repo.preload(rel, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :agent_relationship + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, AgentRelationship.changeset(params)) end -def preload(rel, :object) do - Repo.preload(rel, :object) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &AgentRelationship.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(rel, :relationship) do - Repo.preload(rel, :relationship) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/agent_relationship/resolv.ex b/src/zenflows/vf/agent_relationship/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.AgentRelationship.Resolv do -@moduledoc "Resolvers of AgentRelationships." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.{AgentRelationship, AgentRelationship.Domain} def agent_relationship(params, _) do @@ -25,7 +26,10 @@ def agent_relationship(params, _) do end def agent_relationships(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_agent_relationship(%{relationship: params}, _) do diff --git a/src/zenflows/vf/agent_relationship/type.ex b/src/zenflows/vf/agent_relationship/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.AgentRelationship.Type do -@moduledoc "GraphQL types of AgentRelationships." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/agent_relationship_role.ex b/src/zenflows/vf/agent_relationship_role.ex @@ -23,7 +23,9 @@ such as member, trading partner. use Zenflows.DB.Schema -alias Zenflows.VF.{RoleBehavior, Validate} +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} +alias Zenflows.VF.RoleBehavior @type t() :: %__MODULE__{ role_behavior: RoleBehavior.t() | nil, @@ -44,8 +46,8 @@ end @cast @reqr ++ ~w[role_behavior_id inverse_role_label note]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/agent_relationship_role/domain.ex b/src/zenflows/vf/agent_relationship_role/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.AgentRelationshipRole.Domain do @moduledoc "Domain logic of AgentRelationshipRoles." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.AgentRelationshipRole -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, AgentRelationshipRole.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,52 +33,109 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(AgentRelationshipRole, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: AgentRelationshipRole.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [AgentRelationshipRole.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(AgentRelationshipRole, page)} +end + +@spec all!(Page.t()) :: [AgentRelationshipRole.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, AgentRelationshipRole.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, AgentRelationshipRole.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, AgentRelationshipRole.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: rr}} -> {:ok, rr} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, AgentRelationshipRole.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: AgentRelationshipRole.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, AgentRelationshipRole.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &AgentRelationshipRole.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: rr}} -> {:ok, rr} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, AgentRelationshipRole.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: AgentRelationshipRole.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, AgentRelationshipRole.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, &(&1.one)) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: rr}} -> {:ok, rr} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id) :: AgentRelationshipRole.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(AgentRelationshipRole.t(), :role_behavior) :: AgentRelationshipRole.t() def preload(rel_role, :role_behavior) do Repo.preload(rel_role, :role_behavior) end + +@spec multi_key() :: atom() +def multi_key(), do: :agent_relationship_role + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, AgentRelationshipRole.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &AgentRelationshipRole.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/agent_relationship_role/resolv.ex b/src/zenflows/vf/agent_relationship_role/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.AgentRelationshipRole.Resolv do -@moduledoc "Resolvers of AgentRelationshipRoles." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.{AgentRelationshipRole, AgentRelationshipRole.Domain} def agent_relationship_role(params, _) do @@ -25,7 +26,10 @@ def agent_relationship_role(params, _) do end def agent_relationship_roles(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_agent_relationship_role(%{agent_relationship_role: params}, _) do diff --git a/src/zenflows/vf/agent_relationship_role/type.ex b/src/zenflows/vf/agent_relationship_role/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.AgentRelationshipRole.Type do -@moduledoc "GraphQL types of AgentRelationshipRoles." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/agreement.ex b/src/zenflows/vf/agreement.ex @@ -20,7 +20,8 @@ defmodule Zenflows.VF.Agreement do use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ id: String.t(), @@ -38,8 +39,8 @@ schema "vf_agreement" do end @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/agreement/domain.ex b/src/zenflows/vf/agreement/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.Agreement.Domain do @moduledoc "Domain logic of Agreements." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.Agreement -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Agreement.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,46 +33,103 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Agreement, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Agreement.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Agreement.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(Agreement, page)} +end + +@spec all!(Page.t()) :: [Agreement.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, Agreement.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, Agreement.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Agreement.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: a}} -> {:ok, a} + {:ok, %{^key => value}} -> {:ok, value} {:error, _, cset, _} -> {:error, cset} end end -@spec update(id(), params()) - :: {:ok, Agreement.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Agreement.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Agreement.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Agreement.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: a}} -> {:ok, a} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Agreement.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Agreement.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, Agreement.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, &(&1.one)) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: a}} -> {:ok, a} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id) :: Agreement.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :agreement + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Agreement.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Agreement.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/agreement/resolv.ex b/src/zenflows/vf/agreement/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Agreement.Resolv do -@moduledoc "Resolvers of Agreements." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.Agreement.Domain def agreement(params, _info) do @@ -25,7 +26,10 @@ def agreement(params, _info) do end def agreements(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_agreement(%{agreement: params}, _info) do diff --git a/src/zenflows/vf/agreement/type.ex b/src/zenflows/vf/agreement/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Agreement.Type do -@moduledoc "GraphQL types of Agreements." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/appreciation.ex b/src/zenflows/vf/appreciation.ex @@ -24,7 +24,9 @@ gift economy. use Zenflows.DB.Schema -alias Zenflows.VF.{EconomicEvent, Validate} +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} +alias Zenflows.VF.EconomicEvent @type t() :: %__MODULE__{ appreciation_of: EconomicEvent.t(), @@ -43,8 +45,8 @@ end @cast @reqr ++ [:note] @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/claim.ex b/src/zenflows/vf/claim.ex @@ -23,6 +23,8 @@ received. """ use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Action, Agent, @@ -30,7 +32,6 @@ alias Zenflows.VF.{ Measure, ResourceSpecification, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -79,8 +80,8 @@ end ]a # in_scope_of @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/commitment.ex b/src/zenflows/vf/commitment.ex @@ -23,6 +23,8 @@ agent. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Action, Agent, @@ -34,7 +36,6 @@ alias Zenflows.VF.{ ResourceSpecification, SpatialThing, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -102,13 +103,15 @@ end ]a # in_scope_of_id @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) - |> datetime_check() - |> resource_check() + |> Validate.exist_or([:has_point_in_time, :has_beginning, :has_end, :due]) + |> Validate.exist_nand([:has_point_in_time, :has_beginning]) + |> Validate.exist_nand([:has_point_in_time, :has_end]) + |> Validate.exist_xor([:resource_conforms_to_id, :resource_inventoried_as_id], method: :both) |> Validate.note(:note) |> Validate.class(:resource_classified_as) |> Measure.cast(:effort_quantity) @@ -123,109 +126,4 @@ def chgset(schema \\ %__MODULE__{}, params) do |> Changeset.assoc_constraint(:at_location) |> Changeset.assoc_constraint(:clause_of) end - -# Validate that either :has_point_in_time, :has_beginning, :has_end, -# or :due is provided and that :has_point_in_time and (:has_beginning -# and/or :has_end) are mutually exclusive. -@spec datetime_check(Changeset.t()) :: Changeset.t() -defp datetime_check(cset) do - # credo:disable-for-previous-line Credo.Check.Refactor.CyclomaticComplexity - - {data_point, chng_point, field_point} = - case Changeset.fetch_field(cset, :has_point_in_time) do - {:data, x} -> {x, nil, x} - {:changes, x} -> {nil, x, x} - end - {data_begin, chng_begin, field_begin} = - case Changeset.fetch_field(cset, :has_beginning) do - {:data, x} -> {x, nil, x} - {:changes, x} -> {nil, x, x} - end - {data_end, chng_end, field_end} = - case Changeset.fetch_field(cset, :has_end) do - {:data, x} -> {x, nil, x} - {:changes, x} -> {nil, x, x} - end - field_due = Changeset.get_field(cset, :due) - - cond do - data_point && chng_begin -> - msg = "has_beginning is not allowed in this record" - Changeset.add_error(cset, :has_beginning, msg) - - data_point && chng_end -> - msg = "has_end is not allowed in this record" - Changeset.add_error(cset, :has_end, msg) - - (data_begin || data_end) && chng_point -> - msg = "has_point_in_time is not allowed in this record" - Changeset.add_error(cset, :has_point_in_time, msg) - - chng_point && chng_begin -> - msg = "has_point_in_time and has_beginning are mutually exclusive" - - cset - |> Changeset.add_error(:has_point_in_time, msg) - |> Changeset.add_error(:has_beginning, msg) - - chng_point && chng_end -> - msg = "has_point_in_time and has_end are mutually exclusive" - - cset - |> Changeset.add_error(:has_point_in_time, msg) - |> Changeset.add_error(:has_end, msg) - - field_point || field_begin || field_end || field_due -> - cset - - true -> - msg = "hasBeginning or hasEnd or hasPointInTime or due is required" - - cset - |> Changeset.add_error(:has_point_in_time, msg) - |> Changeset.add_error(:has_beginning, msg) - |> Changeset.add_error(:has_end, msg) - |> Changeset.add_error(:due, msg) - end -end - -# Validate mutual exclusivity of having an actual resource or its -# specification. -# In other words, forbid :resource_conforms_to and -# :resource_inventoried_as to be provided at the same time. -@spec resource_check(Changeset.t()) :: Changeset.t() -defp resource_check(cset) do - # credo:disable-for-previous-line Credo.Check.Refactor.CyclomaticComplexity - - {data_res_con, chng_res_con} = - case Changeset.fetch_field(cset, :resource_conforms_to_id) do - {:data, x} -> {x, nil} - {:changes, x} -> {nil, x} - end - {data_res_inv, chng_res_inv} = - case Changeset.fetch_field(cset, :resource_inventoried_as_id) do - {:data, x} -> {x, nil} - {:changes, x} -> {nil, x} - end - - cond do - data_res_con && chng_res_inv -> - msg = "resource_inventoried_as is not allowed in this record" - Changeset.add_error(cset, :resource_inventoried_as_id, msg) - - data_res_inv && chng_res_con -> - msg = "resource_conforms_to is not allowed in this record" - Changeset.add_error(cset, :resource_conforms_to_id, msg) - - chng_res_con && chng_res_inv -> - msg = "resource_conforms_to and resource_inventoried_as are mutually exclusive" - - cset - |> Changeset.add_error(:resource_conforms_to_id, msg) - |> Changeset.add_error(:resource_inventoried_as_id, msg) - - true -> - cset - end -end end diff --git a/src/zenflows/vf/duration.ex b/src/zenflows/vf/duration.ex @@ -22,6 +22,8 @@ Represents an interval between two DateTime values. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.Schema alias Zenflows.VF.TimeUnitEnum @type t() :: %__MODULE__{ @@ -44,25 +46,23 @@ and `Zenflows.VF.ScenarioDefinition` modules. def cast(cset, key) do case Changeset.fetch_change(cset, key) do {:ok, params} -> - case chgset(params) do + case changeset(params) do %{valid?: true} = cset_dur -> cset - |> Changeset.put_change(field_unit_type(key), + |> Changeset.put_change(:"#{key}_unit_type", Changeset.fetch_change!(cset_dur, :unit_type)) - |> Changeset.put_change(field_numeric_duration(key), + |> Changeset.put_change(:"#{key}_numeric_duration", Changeset.fetch_change!(cset_dur, :numeric_duration)) - cset_dur -> cset_dur.errors |> Enum.reduce(cset, fn {field, {msg, _opts}}, acc -> Changeset.add_error(acc, key, "#{field}: #{msg}") end) end - :error -> # Ecto seems to convert the params' keys to string # whether they were originally string or atom. - strkey = Atom.to_string(key) + strkey = "#{key}" case cset.params do # If, for example, `key` is # `:has_duration`, and it's set to `nil`, @@ -70,9 +70,8 @@ def cast(cset, key) do # `nil` as well. %{^strkey => nil} -> cset - |> Changeset.force_change(field_unit_type(key), nil) - |> Changeset.force_change(field_numeric_duration(key), nil) - + |> Changeset.force_change(:"#{key}_unit_type", nil) + |> Changeset.force_change(:"#{key}_numeric_duration", nil) _ -> cset end @@ -87,29 +86,19 @@ as a %Duration{} struct. Useful for GraphQL types as can be seen in @spec preload(Schema.t(), atom()) :: Schema.t() def preload(schema, key) do %{schema | key => %__MODULE__{ - unit_type: Map.get(schema, field_unit_type(key)), - numeric_duration: Map.get(schema, field_numeric_duration(key)), + unit_type: Map.get(schema, :"#{key}_unit_type"), + numeric_duration: Map.get(schema, :"#{key}_numeric_duration"), }} end @cast ~w[unit_type numeric_duration]a @reqr @cast -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do +@spec changeset(Schema.params()) :: Changeset.t() +defp changeset(params) do %__MODULE__{} |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) |> Changeset.validate_number(:numeric_duration, greater_than_or_equal_to: 0) end - -@spec field_unit_type(atom()) :: atom() -defp field_unit_type(key) do - String.to_existing_atom("#{key}_unit_type") -end - -@spec field_numeric_duration(atom()) :: atom() -defp field_numeric_duration(key) do - String.to_existing_atom("#{key}_numeric_duration") -end end diff --git a/src/zenflows/vf/duration/type.ex b/src/zenflows/vf/duration/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Duration.Type do -@moduledoc "GraphQL types of Durations." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/economic_event.ex b/src/zenflows/vf/economic_event.ex @@ -24,6 +24,8 @@ resource. """ use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Action, Agent, @@ -35,7 +37,6 @@ alias Zenflows.VF.{ ResourceSpecification, SpatialThing, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -100,18 +101,15 @@ end # insert changeset @doc false -@spec chgset(params()) :: Changeset.t() -def chgset(params) do +@spec changeset(Schema.params()) :: Changeset.t() +def changeset(params) do %__MODULE__{} |> Changeset.cast(params, @insert_cast) |> Changeset.validate_required(@insert_reqr) - |> datetime_check() - |> case do - %{valid?: true, changes: %{action_id: action}} = cset -> - do_chgset(action, Changeset.apply_changes(cset), params) - cset -> - cset - end + |> Validate.exist_or([:has_point_in_time, :has_beginning, :has_end]) + |> Validate.exist_nand([:has_point_in_time, :has_beginning]) + |> Validate.exist_nand([:has_point_in_time, :has_end]) + |> do_changeset() |> Validate.uri(:agreed_in) |> Validate.note(:note) |> Validate.class(:resource_classified_as) @@ -128,50 +126,47 @@ def chgset(params) do |> Changeset.assoc_constraint(:triggered_by) end -@spec do_chgset(Action.ID.t(), Schema.t(), params()) :: Changeset.t() -defp do_chgset("raise", schema, params) do - schema - |> Changeset.cast(params, ~w[ +@spec do_changeset(Changeset.t()) :: Changeset.t() +defp do_changeset(%{valid?: false} = cset), do: cset +defp do_changeset(%{changes: %{action_id: "raise"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[ resource_conforms_to_id resource_inventoried_as_id resource_classified_as resource_quantity to_location_id ]a) |> Changeset.validate_required([:resource_quantity]) |> Measure.cast(:resource_quantity) - |> require_agents_same() - |> xor_required(:resource_conforms_to_id, :resource_inventoried_as_id) + |> Validate.value_eq([:provider_id, :receiver_id]) + |> Validate.exist_xor([:resource_conforms_to_id, :resource_inventoried_as_id]) end - -defp do_chgset("produce", schema, params) do - schema - |> Changeset.cast(params, ~w[ +defp do_changeset(%{changes: %{action_id: "produce"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[ output_of_id resource_conforms_to_id resource_inventoried_as_id resource_classified_as resource_quantity to_location_id ]a) |> Changeset.validate_required(~w[output_of_id resource_quantity]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() - |> xor_required(:resource_conforms_to_id, :resource_inventoried_as_id) + |> Validate.value_eq([:provider_id, :receiver_id]) + |> Validate.exist_xor([:resource_conforms_to_id, :resource_inventoried_as_id]) end - -defp do_chgset("lower", schema, params) do - schema - |> Changeset.cast(params, ~w[resource_inventoried_as_id resource_quantity]a) +defp do_changeset(%{changes: %{action_id: "lower"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[resource_inventoried_as_id resource_quantity]a) |> Changeset.validate_required(~w[resource_inventoried_as_id resource_quantity]a) - |> require_agents_same() + |> Validate.value_eq([:provider_id, :receiver_id]) |> Measure.cast(:resource_quantity) end - -defp do_chgset("consume", schema, params) do - schema - |> Changeset.cast(params, ~w[input_of_id resource_inventoried_as_id resource_quantity]a) +defp do_changeset(%{changes: %{action_id: "consume"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[input_of_id resource_inventoried_as_id resource_quantity]a) |> Changeset.validate_required(~w[input_of_id resource_inventoried_as_id resource_quantity]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() + |> Validate.value_eq([:provider_id, :receiver_id]) end - -defp do_chgset("use", schema, params) do - schema - |> Changeset.cast(params, ~w[ +defp do_changeset(%{changes: %{action_id: "use"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[ input_of_id effort_quantity resource_inventoried_as_id resource_conforms_to_id resource_quantity @@ -179,220 +174,108 @@ defp do_chgset("use", schema, params) do |> Changeset.validate_required(~w[input_of_id effort_quantity]a) |> Measure.cast(:effort_quantity) |> Measure.cast(:resource_quantity) - |> xor_required(:resource_inventoried_as_id, :resource_conforms_to_id) + |> Validate.exist_xor([:resource_inventoried_as_id, :resource_conforms_to_id]) end - -defp do_chgset("work", schema, params) do - schema - |> Changeset.cast(params, ~w[input_of_id effort_quantity resource_conforms_to_id]a) +defp do_changeset(%{changes: %{action_id: "work"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[input_of_id effort_quantity resource_conforms_to_id]a) |> Changeset.validate_required(~w[input_of_id effort_quantity resource_conforms_to_id]a) |> Measure.cast(:effort_quantity) end - -defp do_chgset("cite", schema, params) do - schema - |> Changeset.cast(params, ~w[ +defp do_changeset(%{changes: %{action_id: "cite"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[ input_of_id resource_quantity resource_inventoried_as_id resource_conforms_to_id ]a) |> Changeset.validate_required(~w[input_of_id resource_quantity]a) |> Measure.cast(:resource_quantity) - |> xor_required(:resource_inventoried_as_id, :resource_conforms_to_id) + |> Validate.exist_xor([:resource_inventoried_as_id, :resource_conforms_to_id]) end - -defp do_chgset("deliverService", schema, params) do - schema - |> Changeset.cast(params, ~w[input_of_id output_of_id resource_conforms_to_id]a) +defp do_changeset(%{changes: %{action_id: "deliverService"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[input_of_id output_of_id resource_conforms_to_id]a) |> Changeset.validate_required(~w[resource_conforms_to_id]a) - |> require_different_procs() + |> Validate.value_ne([:input_of_id, :output_of_id]) end - -defp do_chgset("pickup", schema, params) do - schema - |> Changeset.cast(params, ~w[input_of_id resource_quantity resource_inventoried_as_id]a) +defp do_changeset(%{changes: %{action_id: "pickup"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[input_of_id resource_quantity resource_inventoried_as_id]a) |> Changeset.validate_required(~w[input_of_id resource_quantity resource_inventoried_as_id]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() + |> Validate.value_eq([:provider_id, :receiver_id]) end - -defp do_chgset("dropoff", schema, params) do - schema - |> Changeset.cast(params, ~w[output_of_id resource_quantity resource_inventoried_as_id to_location_id]a) +defp do_changeset(%{changes: %{action_id: "dropoff"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[output_of_id resource_quantity resource_inventoried_as_id to_location_id]a) |> Changeset.validate_required(~w[output_of_id resource_quantity resource_inventoried_as_id]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() + |> Validate.value_eq([:provider_id, :receiver_id]) end - -defp do_chgset("accept", schema, params) do - schema - |> Changeset.cast(params, ~w[input_of_id resource_quantity resource_inventoried_as_id]a) +defp do_changeset(%{changes: %{action_id: "accept"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[input_of_id resource_quantity resource_inventoried_as_id]a) |> Changeset.validate_required(~w[input_of_id resource_quantity resource_inventoried_as_id]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() + |> Validate.value_eq([:provider_id, :receiver_id]) end - -defp do_chgset("modify", schema, params) do - schema - |> Changeset.cast(params, ~w[output_of_id resource_quantity resource_inventoried_as_id]a) +defp do_changeset(%{changes: %{action_id: "modify"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[output_of_id resource_quantity resource_inventoried_as_id]a) |> Changeset.validate_required(~w[output_of_id resource_quantity resource_inventoried_as_id]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() + |> Validate.value_eq([:provider_id, :receiver_id]) end - -defp do_chgset("combine", schema, params) do - schema - |> Changeset.cast(params, ~w[]a) +defp do_changeset(%{changes: %{action_id: "combine"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[]a) |> Changeset.validate_required(~w[]a) end - -defp do_chgset("separate", schema, params) do - schema - |> Changeset.cast(params, ~w[]a) +defp do_changeset(%{changes: %{action_id: "separate"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[]a) |> Changeset.validate_required(~w[]a) end - -defp do_chgset("transferAllRights", schema, params) do - schema - |> Changeset.cast(params, ~w[ +defp do_changeset(%{changes: %{action_id: "transferAllRights"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[ resource_inventoried_as_id to_resource_inventoried_as_id resource_quantity resource_classified_as ]a) |> Changeset.validate_required(~w[resource_inventoried_as_id resource_quantity]a) |> Measure.cast(:resource_quantity) end - -defp do_chgset(action, schema, params) when action in ~w[transferCustody transfer] do - schema - |> Changeset.cast(params, ~w[ +defp do_changeset(%{changes: %{action_id: id}} = cset) + when id in ~w[transferCustody transfer] do + cset + |> Changeset.cast(cset.params, ~w[ resource_inventoried_as_id to_resource_inventoried_as_id resource_quantity to_location_id resource_classified_as ]a) |> Changeset.validate_required(~w[resource_inventoried_as_id resource_quantity]a) |> Measure.cast(:resource_quantity) end - -defp do_chgset("move", schema, params) do - schema - |> Changeset.cast(params, ~w[ +defp do_changeset(%{changes: %{action_id: "move"}} = cset) do + cset + |> Changeset.cast(cset.params, ~w[ resource_inventoried_as_id to_resource_inventoried_as_id resource_quantity to_location_id resource_classified_as ]a) |> Changeset.validate_required(~w[resource_inventoried_as_id resource_quantity]a) |> Measure.cast(:resource_quantity) - |> require_agents_same() -end - -# Require that `:provider` and `:receiver` be the same agents. -@spec require_agents_same(Changeset.t()) :: Changeset.t() -defp require_agents_same(cset) do - prov = cset.data.provider_id - recv = cset.data.receiver_id - - # since provider and receiver is required, there's no nil case to care - if prov != recv do - msg = "agents must be the same" - cset - |> Changeset.add_error(:provider_id, msg) - |> Changeset.add_error(:receiver_id, msg) - else - cset - end -end - -# Require either of the given fields `a` xor `b`. -@spec xor_required(Changeset.t(), atom(), atom()) :: Changeset.t() -defp xor_required(cset, a, b) do - x = Changeset.get_change(cset, a) - y = Changeset.get_change(cset, b) - - if (x && !y) || (!x && y) do - cset - else - msg = "these are mutually exclusive and exactly one must be provided" - - cset - |> Changeset.add_error(a, msg) - |> Changeset.add_error(b, msg) - end -end - -# Require that `:input_of` and/or `:output_of` is required, and -# that they are not the same. -@spec require_different_procs(Changeset.t()) :: Changeset.t() -defp require_different_procs(cset) do - input = Changeset.get_change(cset, :input_of_id) - output = Changeset.get_change(cset, :output_of_id) - - cond do - input != nil and output != nil and input == output -> - msg = "must have different processes" - - cset - |> Changeset.add_error(:input_of_id, msg) - |> Changeset.add_error(:output_of_id, msg) - - input == nil and output == nil -> - msg = "either of these must not be blank" - - cset - |> Changeset.add_error(:input_of_id, msg) - |> Changeset.add_error(:output_of_id, msg) - - true -> - cset - end + |> Validate.value_eq([:provider_id, :receiver_id]) end @update_cast ~w[note agreed_in realization_of_id triggered_by_id]a # update changeset @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema, params) do schema |> Changeset.cast(params, @update_cast) |> Validate.note(:note) |> Changeset.assoc_constraint(:realization_of) |> Changeset.assoc_constraint(:triggered_by) end - -# Validate datetime mutual exclusivity and requirements. -# In other words, require one of these combinations to be provided: -# * only :has_point_in_time -# * only :has_beginning and/or :has_end -# -# This is only for inserting changeset. -@spec datetime_check(Changeset.t()) :: Changeset.t() -defp datetime_check(cset) do - point = Changeset.get_change(cset, :has_point_in_time) - begin = Changeset.get_change(cset, :has_beginning) - endd = Changeset.get_change(cset, :has_end) - - cond do - point && begin -> - msg = "'has point in time' and 'has beginning' are mutually exclusive" - - cset - |> Changeset.add_error(:has_point_in_time, msg) - |> Changeset.add_error(:has_beginning, msg) - - point && endd -> - msg = "'has point in time' and 'has end' are mutually exclusive" - - cset - |> Changeset.add_error(:has_point_in_time, msg) - |> Changeset.add_error(:has_end, msg) - - point || begin || endd -> - cset - - true -> - msg = "'has point in time', 'has beginning', or 'has end' is requried" - - cset - |> Changeset.add_error(:has_beginning, msg) - |> Changeset.add_error(:has_end, msg) - |> Changeset.add_error(:has_point_in_time, msg) - end -end end diff --git a/src/zenflows/vf/economic_event/domain.ex b/src/zenflows/vf/economic_event/domain.ex @@ -21,7 +21,7 @@ defmodule Zenflows.VF.EconomicEvent.Domain do import Ecto.Query alias Ecto.{Changeset, Multi} -alias Zenflows.DB.{Paging, Repo} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{ Action, EconomicEvent, @@ -29,12 +29,7 @@ alias Zenflows.VF.{ Measure, } -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, EconomicEvent.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -45,52 +40,110 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(EconomicEvent, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: EconomicEvent.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [EconomicEvent.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(EconomicEvent, page)} +end + +@spec all!(Page.t()) :: [EconomicEvent.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params(), params()) :: {:ok, EconomicEvent.t(), EconomicResource.t(), nil} - | {:ok, EconomicEvent.t(), nil, EconomicResource.t()} - | {:ok, EconomicEvent.t()} | {:error, String.t() | chgset()} -def create(evt_params, res_params) do +@spec create(Schema.params(), nil | Schema.params()) + :: {:ok, EconomicEvent.t()} | {:error, String.t() | Changeset.t()} +def create(evt_params, res_params \\ nil) do + key = multi_key() Multi.new() - |> Multi.insert(:created_evt, EconomicEvent.chgset(evt_params)) - |> Multi.merge(fn %{created_evt: evt} -> - handle_multi(evt.action_id, evt, res_params || %{}) # since it can be empty - end) + |> multi_insert(evt_params, res_params) |> Repo.transaction() |> case do - {:ok, %{updated_evt: evt} = map} -> - if map[:eco_res] || map[:to_eco_res] do - {:ok, evt, map[:eco_res], map[:to_eco_res]} - else - {:ok, evt} - end + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} + end +end - {:ok, %{created_evt: evt}} -> - {:ok, evt} +@spec create!(Schema.params(), nil | Schema.params()) :: EconomicEvent.t() +def create!(evt_params, res_params \\ nil) do + {:ok, value} = create(evt_params, res_params) + value +end - {:error, _, msg_or_cset, _} -> - {:error, msg_or_cset} +@spec update(Schema.id(), Schema.params()) + :: {:ok, EconomicEvent.t()} | {:error, String.t() | Changeset.t()} +def update(id, params) do + key = multi_key() + Multi.new() + |> multi_update(id, params) + |> Repo.transaction() + |> case do + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec update!(Schema.id(), Schema.params()) :: EconomicEvent.t() +def update!(id, params) do + # `__MODULE__` because it confilicts with `import Ecto.Query` + {:ok, value} = __MODULE__.update(id, params) + value +end + +@spec preload(EconomicEvent.t(), :action | :input_of | :output_of + | :provider | :receiver + | :resource_inventoried_as | :to_resource_inventoried_as + | :resource_conforms_to | :resource_quantity | :effort_quantity + | :to_location | :at_location | :realization_of | :triggered_by) + :: EconomicEvent.t() +def preload(eco_evt, x) when x in ~w[ + input_of output_of provider receiver + resource_inventoried_as to_resource_inventoried_as + resource_conforms_to to_location at_location realization_of + triggered_by +]a do + Repo.preload(eco_evt, x) +end +def preload(eco_evt, :action), + do: Action.preload(eco_evt, :action) +def preload(eco_evt, x) when x in ~w[resource_quantity effort_quantity]a, + do: Measure.preload(eco_evt, x) + +@spec multi_key() :: atom() +def multi_key(), do: :economic_event + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params(), nil | Schema.params()) + :: Multi.t() +def multi_insert(m, key \\ multi_key(), evt_params, res_params) do + m + |> Multi.insert("#{key}.created", EconomicEvent.changeset(evt_params)) + |> Multi.merge(&handle_insert(key, Map.fetch!(&1, "#{key}.created"), res_params)) +end + # Handle the part after the event is created. These clauses deal with # validations, creation of resources, and any other side-effects. # -# If they return a multi named `:updated_evt`, the value (assuming -# it is a event struct) of it will be passed back as `{:ok, value}`. -# -# They can optionally return a multi named `:eco_res` (assuming -# it is a resource struct) that can be used for a tiny optimization -# on the resolver (when create a resource, fetch it, etc.). -@spec handle_multi(Action.ID.t(), EconomicEvent.t(), params() | nil) :: Multi.t() -defp handle_multi(action_id, evt, res_params) when action_id in ["raise", "produce"] do +# It either returns the given `evt` as it is under the name `key`, +# or updates `evt` and returns it under the name `key`. +@spec handle_insert(term(), EconomicEvent.t(), nil | Schema.params()) :: Multi.t() +defp handle_insert(key, %{action_id: action_id} = evt, res_params) + when action_id in ["raise", "produce"] do cond do evt.resource_conforms_to_id != nil -> res_params = - res_params + (res_params || %{}) |> Map.put(:primary_accountable_id, evt.receiver_id) |> Map.put(:custodian_id, evt.receiver_id) |> Map.put(:conforms_to_id, evt.resource_conforms_to_id) @@ -102,14 +155,12 @@ defp handle_multi(action_id, evt, res_params) when action_id in ["raise", "produ |> Map.put(:classified_as, evt.resource_classified_as) Multi.new() - |> Multi.insert(:eco_res, EconomicResource.chgset(res_params)) - |> Multi.update(:updated_evt, fn %{eco_res: res} -> - Changeset.change(evt, resource_inventoried_as_id: res.id) - end) - + |> EconomicResource.Domain.multi_insert("#{key}.eco_res", res_params) + |> Multi.update(key, &Changeset.change(evt, + resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.eco_res") |> Map.fetch!(:id))) evt.resource_inventoried_as_id != nil -> Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> fields = ~w[ primary_accountable_id custodian_id accounting_quantity_has_unit_id @@ -129,30 +180,27 @@ defp handle_multi(action_id, evt, res_params) when action_id in ["raise", "produ cond do evt.provider_id != res.primary_accountable_id or evt.provider_id != res.custodian_id -> {:error, "you don't have ownership over this resource"} - evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id -> {:error, "the unit of resource quantity must match with the unit of this resource"} - res.contained? -> {:error, "you can't #{action_id} into a contained resource"} - res.container? -> {:error, "you can't #{action_id} into a container resource"} - true -> {:ok, nil} end end) - |> Multi.update_all(:inc, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) end end - -defp handle_multi(action_id, evt, _) when action_id in ["lower", "consume"] do +defp handle_insert(key, %{action_id: action_id} = evt, _) + when action_id in ["lower", "consume"] do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> fields = ~w[ primary_accountable_id custodian_id accounting_quantity_has_unit_id @@ -172,38 +220,33 @@ defp handle_multi(action_id, evt, _) when action_id in ["lower", "consume"] do cond do evt.provider_id != res.primary_accountable_id or evt.provider_id != res.custodian_id -> {:error, "you don't have ownership over this resource"} - evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id -> {:error, "the unit of resource quantity must match with the unit of this resource"} - res.contained? -> # TODO: study combine-separate {:error, "you can't #{action_id} a contained resource"} - res.container? -> {:error, "you can't #{action_id} a container resource"} - true -> {:ok, nil} end end) - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) end - -defp handle_multi(action_id, _evt, _) when action_id in ~w[work deliverService] do - Multi.new() +defp handle_insert(key, %{action_id: action_id} = evt, _) + when action_id in ~w[work deliverService] do + Multi.put(Multi.new(), key, evt) end - -defp handle_multi("use", evt, _) do +defp handle_insert(key, %{action_id: "use"} = evt, _) do cond do evt.resource_conforms_to_id != nil -> - Multi.new() - + Multi.put(Multi.new(), key, evt) evt.resource_inventoried_as_id != nil -> Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> fields = if evt.resource_quantity != nil, do: [:accounting_quantity_has_unit_id], else: [] @@ -223,28 +266,24 @@ defp handle_multi("use", evt, _) do cond do evt.resource_quantity && (evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id) -> {:error, "the unit of resource quantity must match with the unit of this resource"} - res.contained? -> # TODO: study combine-separate {:error, "you can't use a contained resource"} - res.container? -> {:error, "you can't use a container resource"} - true -> {:ok, nil} end end) + |> Multi.put(key, evt) end end - -defp handle_multi("cite", evt, _) do +defp handle_insert(key, %{action_id: "cite"} = evt, _) do cond do evt.resource_conforms_to_id != nil -> - Multi.new() - + Multi.put(Multi.new(), key, evt) evt.resource_inventoried_as_id != nil -> Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> res = from( r in EconomicResource, where: [id: ^evt.resource_inventoried_as_id], @@ -259,23 +298,20 @@ defp handle_multi("cite", evt, _) do cond do evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id -> {:error, "the unit of resource quantity must match with the unit of this resource"} - res.contained? -> # TODO: study combine-separate {:error, "you can't cite a contained resource"} - res.container? -> {:error, "you can't cite a container resource"} - true -> {:ok, nil} end end) + |> Multi.put(key, evt) end end - -defp handle_multi("pickup", evt, _) do +defp handle_insert(key, %{action_id: "pickup"} = evt, _) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> fields = ~w[ custodian_id onhand_quantity_has_numerical_value onhand_quantity_has_unit_id @@ -289,26 +325,22 @@ defp handle_multi("pickup", evt, _) do ) |> repo.one!() - single_ref? = fn -> + not_single_ref? = fn -> where(EconomicEvent, [e], e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id and e.id != ^evt.id and e.action_id == "pickup" and e.input_of_id == ^evt.input_of_id) |> repo.exists?() - |> Kernel.not() end cond do evt.provider_id != res.custodian_id -> {:error, "you don't have custody over this resource"} - res.contained? -> # TODO: study combine-separate {:error, "you can't pickup a contained resource"} - evt.resource_quantity_has_unit_id != res.onhand_quantity_has_unit_id -> {:error, "the unit of resource quantity must match with the unit of this resource"} - # This also handles the requirement that the # resource's onhand quantity must be positive. # This is guranteed because of the check below @@ -316,19 +348,18 @@ defp handle_multi("pickup", evt, _) do # of events must be positive. evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value -> {:error, "the pickup events need to fully pickup the resource"} - - not single_ref?.() -> - {:error, "no more than one pickup event in the same process, referring to the same resource is allowed"} - + not_single_ref?.() -> + {:error, + "no more than one pickup event in the same process, referring to the same resource is allowed"} true -> {:ok, nil} end end) + |> Multi.put(key, evt) end - -defp handle_multi("dropoff", evt, _) do +defp handle_insert(key, %{action_id: "dropoff"} = evt, _) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> pair_evt = from(e in EconomicEvent, where: e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id @@ -340,14 +371,13 @@ defp handle_multi("dropoff", evt, _) do ]a)) |> repo.one!() - single_ref? = fn -> + not_single_ref? = fn -> where(EconomicEvent, [e], e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id and e.id != ^evt.id and e.action_id == "dropoff" and e.output_of_id == ^evt.output_of_id) |> repo.exists?() - |> Kernel.not() end container? = fn -> @@ -358,35 +388,32 @@ defp handle_multi("dropoff", evt, _) do cond do evt.provider_id != pair_evt.provider_id -> {:error, "you don't have custody over this resource"} - evt.resource_quantity_has_unit_id != pair_evt.resource_quantity_has_unit_id -> {:error, "the unit of resource quantity must match with the unit of the paired event"} - container?.() && evt.resource_quantity_has_numerical_value != pair_evt.resource_quantity_has_numerical_value -> {:error, "the dropoff events need to fully dropoff the resource"} - - not single_ref?.() -> - {:error, "no more than one dropoff event in the same process, referring to the same resource is allowed"} - + not_single_ref?.() -> + {:error, + "no more than one dropoff event in the same process, referring to the same resource is allowed"} true -> {:ok, nil} end end) - |> Multi.run(:set, fn repo, _ -> + |> Multi.run("#{key}.set", fn repo, _ -> if evt.to_location_id do q = where(EconomicResource, [r], - r.id == ^evt.resource_inventoried_as_id - or r.contained_in_id == ^evt.resource_inventoried_as_id) + r.id == ^evt.resource_inventoried_as_id + or r.contained_in_id == ^evt.resource_inventoried_as_id) {:ok, repo.update_all(q, set: [current_location_id: evt.to_location_id])} else {:ok, nil} end end) + |> Multi.put(key, evt) end - -defp handle_multi("accept", evt, _) do +defp handle_insert(key, %{action_id: "accept"} = evt, _) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> fields = ~w[ custodian_id onhand_quantity_has_numerical_value onhand_quantity_has_unit_id @@ -403,14 +430,13 @@ defp handle_multi("accept", evt, _) do where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id) |> repo.exists?()) - single_ref? = fn -> + not_single_ref? = fn -> where(EconomicEvent, [e], e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id and e.id != ^evt.id and e.action_id == "accept" and e.input_of_id == ^evt.input_of_id) |> repo.exists?() - |> Kernel.not() end any_combine_separate? = fn -> @@ -440,21 +466,21 @@ defp handle_multi("accept", evt, _) do any_combine_separate?.() -> {:error, "you can't add another accept event to the same process where there are at least one combine or separate events"} - not single_ref?.() -> + not_single_ref?.() -> {:error, "no more than one accept event in the same process, referring to the same resource is allowed"} true -> {:ok, nil} end end) - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) end - -defp handle_multi("modify", evt, _) do +defp handle_insert(key, %{action_id: "modify"} = evt, _) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> pair_evt = from(e in EconomicEvent, where: e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id @@ -466,48 +492,43 @@ defp handle_multi("modify", evt, _) do ]a)) |> repo.one!() - single_ref? = fn -> + not_single_ref? = fn -> where(EconomicEvent, [e], e.resource_inventoried_as_id == ^evt.resource_inventoried_as_id and e.id != ^evt.id and e.action_id == "modify" and e.output_of_id == ^evt.output_of_id) |> repo.exists?() - |> Kernel.not() end cond do evt.provider_id != pair_evt.provider_id -> {:error, "you don't have custody over this resource"} - evt.resource_quantity_has_unit_id != pair_evt.resource_quantity_has_unit_id -> {:error, "the unit of resource quantity must match with the unit of the paired event"} - evt.resource_quantity_has_numerical_value != pair_evt.resource_quantity_has_numerical_value -> {:error, "the modify events need to fully modify the resource"} - - not single_ref?.() -> + not_single_ref?.() -> {:error, "no more than one modify event in the same process, referring to the same resource is allowed"} - true -> {:ok, nil} end end) - |> Multi.update_all(:inc, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, ]) - |> Multi.update_all(:stage, fn _ -> + |> Multi.update_all("#{key}.stage", fn _ -> from(r in EconomicResource, join: e in EconomicEvent, on: e.id == ^evt.id, join: p in assoc(e, :output_of), where: r.id == e.resource_inventoried_as_id, update: [set: [stage_id: p.based_on_id]]) end, []) + |> Multi.put(key, evt) end - -defp handle_multi("transferCustody", evt, res_params) do +defp handle_insert(key, %{action_id: "transferCustody"} = evt, res_params) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> %{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt res_ids = if to_res_id do @@ -543,51 +564,43 @@ defp handle_multi("transferCustody", evt, res_params) do cond do evt.provider_id != res.custodian_id -> {:error, "you don't have custody over this resource"} - res.contained? -> {:error, "you can't transfer-custody a contained resource"} - evt.resource_quantity_has_unit_id != res.onhand_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} - res.container? and res.onhand_quantity_has_numerical_value <= 0 -> {:error, "the transfer-custody events need container resources to have positive onhand-quantity"} - res.container? && evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value -> {:error, "the transfer-custody events need to fully transfer the resource"} - res.container? && to_res -> {:error, "you can't transfer-custody a container resource into another resource"} - to_res && to_res.contained? -> {:error, "you can't transfer-custody into a contained resource"} - to_res && to_res.container? -> {:error, "you can't transfer-custody into a container resource"} - to_res && evt.resource_quantity_has_unit_id != to_res.onhand_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} - to_res && res.conforms_to_id != to_res.conforms_to_id -> {:error, "the resources must conform to the same specification"} - true -> # some fields of the resource is required for the following multis {:ok, res} end end) - |> Multi.merge(fn %{checks: res} -> + |> Multi.merge(fn changes -> + res = Map.fetch!(changes, "#{key}.checks") if evt.to_resource_inventoried_as_id do Multi.new() - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.update_all(:inc, where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) else res_params = - res_params + (res_params || %{}) |> Map.put(:primary_accountable_id, evt.receiver_id) |> Map.put(:custodian_id, evt.receiver_id) |> Map.put(:conforms_to_id, res.conforms_to_id) @@ -608,19 +621,19 @@ defp handle_multi("transferCustody", evt, res_params) do |> Map.put_new(:metadata, res.metadata) Multi.new() - |> Multi.insert(:to_eco_res, EconomicResource.chgset(res_params)) - |> Multi.update(:updated_evt, fn %{to_eco_res: res} -> - Changeset.change(evt, to_resource_inventoried_as_id: res.id) - end) - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params) + |> Multi.update(key, &Changeset.change(evt, + to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id))) + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.merge(fn %{updated_evt: evt} -> + |> Multi.merge(fn %{^key => evt} -> if res.container? do - Multi.update_all(Multi.new(), :set, where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ - contained_in_id: evt.to_resource_inventoried_as_id, - custodian_id: evt.receiver_id, - ]) + Multi.update_all(Multi.new(), "#{key}.set", + where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ + contained_in_id: evt.to_resource_inventoried_as_id, + custodian_id: evt.receiver_id, + ]) else Multi.new() end @@ -628,10 +641,9 @@ defp handle_multi("transferCustody", evt, res_params) do end end) end - -defp handle_multi("transferAllRights", evt, res_params) do +defp handle_insert(key, %{action_id: "transferAllRights"} = evt, res_params) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> %{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt res_ids = if to_res_id do @@ -667,40 +679,31 @@ defp handle_multi("transferAllRights", evt, res_params) do cond do evt.provider_id != res.primary_accountable_id -> {:error, "you don't have accountability over this resource"} - res.contained? -> {:error, "you can't transfer-all-rights a contained resource"} - evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} - res.container? and res.accounting_quantity_has_numerical_value <= 0 -> {:error, "the transfer-all-rights events need container resources to have positive accounting-quantity"} - res.container? && evt.resource_quantity_has_numerical_value != res.accounting_quantity_has_numerical_value -> {:error, "the transfer-all-rights events need to fully transfer the resource"} - res.container? && to_res -> {:error, "you can't transfer-all-rights a container resource into another resource"} - to_res && to_res.contained? -> {:error, "you can't transfer-all-rights into a contained resource"} - to_res && to_res.container? -> {:error, "you can't transfer-all-rights into a container resource"} - to_res && evt.resource_quantity_has_unit_id != to_res.accounting_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} - to_res && res.conforms_to_id != to_res.conforms_to_id -> {:error, "the resources must conform to the same specification"} - true -> # some fields of the resource is required for the following multis {:ok, res} end end) - |> Multi.merge(fn %{checks: res} -> + |> Multi.merge(fn changes -> + res = Map.fetch!(changes, "#{key}.checks") if evt.to_resource_inventoried_as_id do Multi.new() |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ @@ -709,9 +712,10 @@ defp handle_multi("transferAllRights", evt, res_params) do |> Multi.update_all(:inc, where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) else res_params = - res_params + (res_params || %{}) |> Map.put(:primary_accountable_id, evt.receiver_id) |> Map.put(:custodian_id, evt.receiver_id) |> Map.put(:conforms_to_id, res.conforms_to_id) @@ -731,16 +735,15 @@ defp handle_multi("transferAllRights", evt, res_params) do |> Map.put_new(:metadata, res.metadata) Multi.new() - |> Multi.insert(:to_eco_res, EconomicResource.chgset(res_params)) - |> Multi.update(:updated_evt, fn %{to_eco_res: res} -> - Changeset.change(evt, to_resource_inventoried_as_id: res.id) - end) - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params) + |> Multi.update(key, &Changeset.change(evt, + to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id))) + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.merge(fn %{updated_evt: evt} -> + |> Multi.merge(fn %{^key => evt} -> if res.container? do - Multi.update_all(Multi.new(), :set, where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ + Multi.update_all(Multi.new(), "#{key}.set", where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ contained_in_id: evt.to_resource_inventoried_as_id, primary_accountable_id: evt.receiver_id, ]) @@ -751,10 +754,9 @@ defp handle_multi("transferAllRights", evt, res_params) do end end) end - -defp handle_multi("transfer", evt, res_params) do +defp handle_insert(key, %{action_id: "transfer"} = evt, res_params) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> %{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt res_ids = if to_res_id do @@ -791,62 +793,51 @@ defp handle_multi("transfer", evt, res_params) do cond do evt.provider_id != res.primary_accountable_id -> {:error, "you don't have accountability over this resource"} - evt.provider_id != res.custodian_id -> {:error, "you don't have custody over this resource"} - res.contained? -> {:error, "you can't transfer a contained resource"} - evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} - res.container? and res.accounting_quantity_has_numerical_value <= 0 -> {:error, "the transfer events need container resources to have positive accounting-quantity"} - res.container? and res.onhand_quantity_has_numerical_value <= 0 -> {:error, "the transfer events need container resources to have positive onhand-quantity"} - res.container? && evt.resource_quantity_has_numerical_value != res.accounting_quantity_has_numerical_value -> {:error, "the transfer events need to fully transfer the resource"} - res.container? && evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value -> {:error, "the transfer events need to fully transfer the resource"} - res.container? && to_res -> {:error, "you can't transfer a container resource into another resource"} - to_res && to_res.contained? -> {:error, "you can't transfer into a contained resource"} - to_res && to_res.container? -> {:error, "you can't transfer into a container resource"} - to_res && evt.resource_quantity_has_unit_id != to_res.accounting_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} - to_res && res.conforms_to_id != to_res.conforms_to_id -> {:error, "the resources must conform to the same specification"} - true -> # some fields of the resource is required for the following multis {:ok, res} end end) - |> Multi.merge(fn %{checks: res} -> + |> Multi.merge(fn changes -> + res = Map.fetch!(changes, "#{key}.checks") if evt.to_resource_inventoried_as_id do Multi.new() - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.update_all(:inc, where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) else res_params = - res_params + (res_params || %{}) |> Map.put(:primary_accountable_id, evt.receiver_id) |> Map.put(:custodian_id, evt.receiver_id) |> Map.put(:conforms_to_id, res.conforms_to_id) @@ -867,17 +858,16 @@ defp handle_multi("transfer", evt, res_params) do |> Map.put_new(:metadata, res.metadata) Multi.new() - |> Multi.insert(:to_eco_res, EconomicResource.chgset(res_params)) - |> Multi.update(:updated_evt, fn %{to_eco_res: res} -> - Changeset.change(evt, to_resource_inventoried_as_id: res.id) - end) - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params) + |> Multi.update(key, &Changeset.change(evt, + to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id))) + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.merge(fn %{updated_evt: evt} -> + |> Multi.merge(fn %{^key => evt} -> if res.container? do - Multi.update_all(Multi.new(), :set, where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ + Multi.update_all(Multi.new(), "#{key}.set", where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ contained_in_id: evt.to_resource_inventoried_as_id, primary_accountable_id: evt.receiver_id, custodian_id: evt.receiver_id, @@ -889,10 +879,9 @@ defp handle_multi("transfer", evt, res_params) do end end) end - -defp handle_multi("move", evt, res_params) do +defp handle_insert(key, %{action_id: "move"} = evt, res_params) do Multi.new() - |> Multi.run(:checks, fn repo, _ -> + |> Multi.run("#{key}.checks", fn repo, _ -> %{resource_inventoried_as_id: res_id, to_resource_inventoried_as_id: to_res_id} = evt res_ids = if to_res_id do @@ -929,68 +918,55 @@ defp handle_multi("move", evt, res_params) do cond do evt.provider_id != res.primary_accountable_id -> {:error, "you don't have accountability over resource-inventoried-as"} - evt.provider_id != res.custodian_id -> {:error, "you don't have custody over resource-inventoried-as"} - res.contained? -> {:error, "you can't move a contained resource"} - evt.resource_quantity_has_unit_id != res.accounting_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} - res.container? and res.accounting_quantity_has_numerical_value <= 0 -> {:error, "the move events need container resources to have positive accounting-quantity"} - res.container? and res.onhand_quantity_has_numerical_value <= 0 -> {:error, "the move events need container resources to have positive onhand-quantity"} - res.container? && evt.resource_quantity_has_numerical_value != res.accounting_quantity_has_numerical_value -> {:error, "the move events need to fully move the resource"} - res.container? && evt.resource_quantity_has_numerical_value != res.onhand_quantity_has_numerical_value -> {:error, "the move events need to fully move the resource"} - res.container? && to_res -> {:error, "you can't move a container resource into another resource"} - to_res && evt.provider_id != to_res.primary_accountable_id -> {:error, "you don't have accountability over to-resource-inventoried-as"} - to_res && evt.provider_id != to_res.custodian_id -> {:error, "you don't have custody over to-resource-inventoried-as"} - to_res && to_res.contained? -> {:error, "you can't move into a contained resource"} - to_res && to_res.container? -> {:error, "you can't move into a container resource"} - to_res && evt.resource_quantity_has_unit_id != to_res.accounting_quantity_has_unit_id -> {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} - to_res && res.conforms_to_id != to_res.conforms_to_id -> {:error, "the resources must conform to the same specification"} - true -> # some fields of the resource is required for the following multis {:ok, res} end end) - |> Multi.merge(fn %{checks: res} -> + |> Multi.merge(fn changes -> + res = Map.fetch!(changes, "#{key}.checks") if evt.to_resource_inventoried_as_id do Multi.new() - |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.dec", where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.update_all(:inc, where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ + |> Multi.update_all("#{key}.inc", where(EconomicResource, id: ^evt.to_resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: evt.resource_quantity_has_numerical_value, ]) + |> Multi.put(key, evt) else res_params = - res_params + (res_params || %{}) |> Map.put(:primary_accountable_id, evt.receiver_id) |> Map.put(:custodian_id, evt.receiver_id) |> Map.put(:conforms_to_id, res.conforms_to_id) @@ -1011,17 +987,16 @@ defp handle_multi("move", evt, res_params) do |> Map.put_new(:metadata, res.metadata) Multi.new() - |> Multi.insert(:to_eco_res, EconomicResource.chgset(res_params)) - |> Multi.update(:updated_evt, fn %{to_eco_res: res} -> - Changeset.change(evt, to_resource_inventoried_as_id: res.id) - end) + |> EconomicResource.Domain.multi_insert("#{key}.to_eco_res", res_params) + |> Multi.update(key, &Changeset.change(evt, + to_resource_inventoried_as_id: &1 |> Map.fetch!("#{key}.to_eco_res") |> Map.fetch!(:id))) |> Multi.update_all(:dec, where(EconomicResource, id: ^evt.resource_inventoried_as_id), inc: [ accounting_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, onhand_quantity_has_numerical_value: -evt.resource_quantity_has_numerical_value, ]) - |> Multi.merge(fn %{updated_evt: evt} -> + |> Multi.merge(fn %{^key => evt} -> if res.container? do - Multi.update_all(Multi.new(), :set, where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ + Multi.update_all(Multi.new(), "#{key}.set", where(EconomicResource, contained_in_id: ^evt.resource_inventoried_as_id), set: [ contained_in_id: evt.to_resource_inventoried_as_id, primary_accountable_id: evt.receiver_id, custodian_id: evt.receiver_id, @@ -1034,84 +1009,18 @@ defp handle_multi("move", evt, res_params) do end) end -@spec update(id(), params()) - :: {:ok, EconomicEvent.t()} | {:error, String.t() | chgset()} -def update(id, params) do - Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &EconomicEvent.chgset(&1.one, params)) - |> Repo.transaction() - |> case do - {:ok, %{update: ee}} -> {:ok, ee} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} - end -end - -@spec preload(EconomicEvent.t(), :action | :input_of | :output_of - | :provider | :receiver - | :resource_inventoried_as | :to_resource_inventoried_as - | :resource_conforms_to | :resource_quantity | :effort_quantity - | :to_location | :at_location | :realization_of | :triggered_by - | :previous_event) - :: EconomicEvent.t() -def preload(eco_evt, :action) do - Action.preload(eco_evt, :action) -end - -def preload(eco_evt, :input_of) do - Repo.preload(eco_evt, :input_of) -end - -def preload(eco_evt, :output_of) do - Repo.preload(eco_evt, :output_of) -end - -def preload(eco_evt, :provider) do - Repo.preload(eco_evt, :provider) -end - -def preload(eco_evt, :receiver) do - Repo.preload(eco_evt, :receiver) -end - -def preload(eco_evt, :resource_inventoried_as) do - Repo.preload(eco_evt, :resource_inventoried_as) -end - -def preload(eco_evt, :to_resource_inventoried_as) do - Repo.preload(eco_evt, :to_resource_inventoried_as) -end - -def preload(eco_evt, :resource_conforms_to) do - Repo.preload(eco_evt, :resource_conforms_to) -end - -def preload(eco_evt, :resource_quantity) do - Measure.preload(eco_evt, :resource_quantity) -end - -def preload(eco_evt, :effort_quantity) do - Measure.preload(eco_evt, :effort_quantity) -end - -def preload(eco_evt, :to_location) do - Repo.preload(eco_evt, :to_location) -end - -def preload(eco_evt, :at_location) do - Repo.preload(eco_evt, :at_location) -end - -def preload(eco_evt, :realization_of) do - Repo.preload(eco_evt, :realization_of) -end - -def preload(eco_evt, :triggered_by) do - Repo.preload(eco_evt, :triggered_by) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &EconomicEvent.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(eco_evt, :previous_event) do - Repo.preload(eco_evt, :previous_event) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/economic_event/resolv.ex b/src/zenflows/vf/economic_event/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.EconomicEvent.Resolv do -@moduledoc "Resolvers of EconomicEvent." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.EconomicEvent.Domain def economic_event(params, _) do @@ -27,26 +28,16 @@ def economic_event(params, _) do end def economic_events(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_economic_event(%{event: evt_params} = params, _) do res_params = params[:new_inventoried_resource] - - case Domain.create(evt_params, res_params) do - {:ok, evt, res, nil} -> - evt = Map.put(evt, :resource_inventoried_as, res) # tiny optimization - {:ok, %{economic_event: evt}} - - {:ok, evt, nil, to_res} -> - evt = Map.put(evt, :to_resource_inventoried_as, to_res) # tiny optimization - {:ok, %{economic_event: evt}} - - {:ok, evt} -> - {:ok, %{economic_event: evt}} - - {:error, err} -> - {:error, err} + with {:ok, evt} <- Domain.create(evt_params, res_params) do + {:ok, %{economic_event: evt}} end end diff --git a/src/zenflows/vf/economic_event/type.ex b/src/zenflows/vf/economic_event/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.EconomicEvent.Type do -@moduledoc "GraphQL types of EconomicEvents." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/economic_resource.ex b/src/zenflows/vf/economic_resource.ex @@ -20,6 +20,8 @@ defmodule Zenflows.VF.EconomicResource do use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.File alias Zenflows.VF.{ Action, @@ -31,7 +33,6 @@ alias Zenflows.VF.{ ResourceSpecification, SpatialThing, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -111,8 +112,8 @@ end ]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) @@ -124,8 +125,8 @@ def chgset(schema \\ %__MODULE__{}, params) do |> Validate.name(:version) |> Validate.name(:licensor) |> Validate.name(:license) - |> require_quantity_units_same() - |> Changeset.cast_assoc(:images, with: &File.chgset/2) + |> Validate.value_eq([:accounting_quantity_has_unit_id, :onhand_quantity_has_unit_id]) + |> Changeset.cast_assoc(:images) |> Changeset.assoc_constraint(:conforms_to) |> Changeset.assoc_constraint(:accounting_quantity_has_unit) |> Changeset.assoc_constraint(:onhand_quantity_has_unit) @@ -137,21 +138,4 @@ def chgset(schema \\ %__MODULE__{}, params) do |> Changeset.assoc_constraint(:contained_in) |> Changeset.assoc_constraint(:unit_of_effort) end - -# Require that `:accounting_quantity_has_unit_id` and -# `:onhand_quantity_has_unit_id` be the same. -@spec require_quantity_units_same(Changeset.t()) :: Changeset.t() -def require_quantity_units_same(cset) do - accnt_unit = Changeset.get_field(cset, :accounting_quantity_has_unit_id) - onhnd_unit = Changeset.get_field(cset, :onhand_quantity_has_unit_id) - - if accnt_unit != onhnd_unit do - msg = "has_unit: quantity units must be same" - cset - |> Changeset.add_error(:accounting_quantity, msg) - |> Changeset.add_error(:onhand_quantity, msg) - else - cset - end -end end diff --git a/src/zenflows/vf/economic_resource/domain.ex b/src/zenflows/vf/economic_resource/domain.ex @@ -18,8 +18,8 @@ defmodule Zenflows.VF.EconomicResource.Domain do @moduledoc "Domain logic of EconomicResources." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{ Action, EconomicResource, @@ -27,12 +27,7 @@ alias Zenflows.VF.{ Measure, } -@typep repo() :: Ecto.Repo.t() -@typep error() :: Ecto.Changeset.t() | String.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, EconomicResource.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -43,13 +38,26 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Filter.error() | Paging.result() -def all(params \\ %{}) do - with {:ok, q} <- Filter.filter(params[:filter] || %{}) do - Paging.page(q, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: EconomicResource.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [EconomicResource.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + with {:ok, q} <- Filter.all(page) do + {:ok, Page.all(q, page)} end end +@spec all!(Page.t()) :: [EconomicResource.t()] +def all!(page \\ Page.new()) do + {:ok, q} = Filter.all(page) + Page.all(q, page) +end + @spec classifications() :: [String.t()] def classifications() do import Ecto.Query @@ -59,83 +67,85 @@ def classifications() do |> Repo.all() end -@spec update(id(), params()) :: {:ok, EconomicResource.t()} | {:error, error()} +@spec update(Schema.id(), Schema.params()) + :: {:ok, EconomicResource.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &EconomicResource.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: er}} -> {:ok, er} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, EconomicResource.t()} | {:error, error()} +@spec update!(Schema.id(), Schema.params()) :: EconomicResource.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: + {:ok, EconomicResource.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: er}} -> {:ok, er} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id) :: EconomicResource.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(EconomicResource.t(), :images | :conforms_to | :accounting_quantity | :onhand_quantity | :primary_accountable | :custodian | :stage | :state | :current_location | :lot | :contained_in | :unit_of_effort) :: EconomicResource.t() -def preload(eco_res, :images) do - Repo.preload(eco_res, :images) -end - -def preload(eco_res, :conforms_to) do - Repo.preload(eco_res, :conforms_to) -end - -def preload(eco_res, :accounting_quantity) do - Measure.preload(eco_res, :accounting_quantity) -end - -def preload(eco_res, :onhand_quantity) do - Measure.preload(eco_res, :onhand_quantity) -end - -def preload(eco_res, :primary_accountable) do - Repo.preload(eco_res, :primary_accountable) -end - -def preload(eco_res, :custodian) do - Repo.preload(eco_res, :custodian) -end - -def preload(eco_res, :stage) do - Repo.preload(eco_res, :stage) -end - -def preload(eco_res, :state) do - Action.preload(eco_res, :state) -end - -def preload(eco_res, :current_location) do - Repo.preload(eco_res, :current_location) -end - -def preload(eco_res, :lot) do - Repo.preload(eco_res, :lot) -end - -def preload(eco_res, :contained_in) do - Repo.preload(eco_res, :contained_in) -end - -def preload(eco_res, :unit_of_effort) do - Repo.preload(eco_res, :unit_of_effort) +def preload(eco_res, x) when x in ~w[ + images conforms_to primary_accountable custodian lot + stage current_location contained_in unit_of_effort +]a, + do: Repo.preload(eco_res, x) +def preload(eco_res, x) when x in ~w[accounting_quantity onhand_quantity]a, + do: Measure.preload(eco_res, x) +def preload(eco_res, :state), + do: Action.preload(eco_res, :state) + +@spec multi_key() :: atom() +def multi_key(), do: :economic_resource + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, EconomicResource.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &EconomicResource.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/economic_resource/filter.ex b/src/zenflows/vf/economic_resource/filter.ex @@ -16,62 +16,54 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.EconomicResource.Filter do -@moduledoc "Filtering logic of EconomicResources." - -use Zenflows.DB.Schema +@moduledoc false import Ecto.Query -alias Ecto.Query -alias Zenflows.DB.{Filter, ID} -alias Zenflows.VF.{EconomicResource, Validate} - -@type error() :: Filter.error() +alias Ecto.{Changeset, Queryable} +alias Zenflows.DB.{ID, Page, Schema, Validate} +alias Zenflows.VF.EconomicResource -@spec filter(Filter.params()) :: Filter.result() -def filter(params) do - case chgset(params) do - %{valid?: true, changes: c} -> - {:ok, Enum.reduce(c, EconomicResource, &f(&2, &1))} - %{valid?: false} = cset -> - {:error, cset} +@spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def all(%{filter: nil}), do: {:ok, EconomicResource} +def all(%{filter: params}) do + with {:ok, filters} <- all_validate(params) do + Enum.reduce(filters, EconomicResource, &all_f(&2, &1)) end end -@spec f(Query.t(), {atom(), term()}) :: Query.t() -defp f(q, {:classified_as, v}), +@spec all_f(Queryable.t(), {atom(), term()}) :: Queryable.t() +defp all_f(q, {:classified_as, v}), do: where(q, [x], fragment("? @> ?", x.classified_as, ^v)) -defp f(q, {:primary_accountable, v}), +defp all_f(q, {:primary_accountable, v}), do: where(q, [x], x.primary_accountable_id in ^v) -defp f(q, {:custodian, v}), +defp all_f(q, {:custodian, v}), do: where(q, [x], x.custodian_id in ^v) -defp f(q, {:conforms_to, v}), +defp all_f(q, {:conforms_to, v}), do: where(q, [x], x.conforms_to_id in ^v) -defp f(q, {:gt_onhand_quantity_has_numerical_value, v}), +defp all_f(q, {:gt_onhand_quantity_has_numerical_value, v}), do: where(q, [x], x.onhand_quantity_has_numerical_value > ^v) -embedded_schema do - field :classified_as, {:array, :string} - field :primary_accountable, {:array, ID} - field :custodian, {:array, ID} - field :conforms_to, {:array, ID} - field :gt_onhand_quantity_has_numerical_value, :float -end - -@cast ~w[ - classified_as primary_accountable custodian conforms_to - gt_onhand_quantity_has_numerical_value -]a - -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do - %__MODULE__{} - |> Changeset.cast(params, @cast) +@spec all_validate(Schema.params()) :: + {:ok, Changeset.data()} | {:error, Changeset.t()} +defp all_validate(params) do + {%{}, %{ + classified_as: {:array, :string}, + primary_accountable: {:array, ID}, + custodian: {:array, ID}, + conforms_to: {:array, ID}, + gt_onhand_quantity_has_numerical_value: :float, + }} + |> Changeset.cast(params, ~w[ + classified_as primary_accountable custodian conforms_to + gt_onhand_quantity_has_numerical_value + ]a) |> Validate.class(:classified_as) |> Validate.class(:primary_accountable) |> Validate.class(:custodian) |> Validate.class(:conforms_to) |> Changeset.validate_number(:gt_onhand_quantity_has_numerical_value, greater_than_or_equal_to: 0) + |> Changeset.apply_action(nil) end end diff --git a/src/zenflows/vf/economic_resource/resolv.ex b/src/zenflows/vf/economic_resource/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.EconomicResource.Resolv do -@moduledoc "Resolvers of EconomicResource." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.EconomicResource.Domain def economic_resource(params, _) do @@ -27,7 +28,10 @@ def economic_resource(params, _) do end def economic_resources(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def economic_resource_classifications(_, _) do diff --git a/src/zenflows/vf/economic_resource/type.ex b/src/zenflows/vf/economic_resource/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.EconomicResource.Type do -@moduledoc "GraphQL types of EconomicResources." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/event_or_commitment.ex b/src/zenflows/vf/event_or_commitment.ex @@ -22,6 +22,8 @@ An EconomicEvent or Commitment, mutually exclusive. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{Commitment, EconomicEvent} @type t() :: %__MODULE__{ @@ -38,57 +40,12 @@ end @cast ~w[event_id commitment_id]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) - |> mutex_check() + |> Validate.exist_xor([:event_id, :commitment_id], method: :both) |> Changeset.assoc_constraint(:event) |> Changeset.assoc_constraint(:commitment) end - -# Validate mutual exclusivity of having either one of EconomicEvent -# or Commitment. -@spec mutex_check(Changeset.t()) :: Changeset.t() -defp mutex_check(cset) do - # credo:disable-for-previous-line Credo.Check.Refactor.CyclomaticComplexity - - {data_evt, chng_evt} = - case Changeset.fetch_field(cset, :event_id) do - {:data, x} -> {x, nil} - {:changes, x} -> {nil, x} - end - {data_comm, chng_comm} = - case Changeset.fetch_field(cset, :commitment_id) do - {:data, x} -> {x, nil} - {:changes, x} -> {nil, x} - end - - cond do - data_evt && chng_comm -> - msg = "commitment is not allowed in this record" - Changeset.add_error(cset, :commitment_id, msg) - - data_comm && chng_evt -> - msg = "event is not allowed in this record" - Changeset.add_error(cset, :event_id, msg) - - chng_evt && chng_comm -> - msg = "economic events and commitments are mutually exclusive" - - cset - |> Changeset.add_error(:event_id, msg) - |> Changeset.add_error(:commitment_id, msg) - - chng_evt || chng_comm -> - cset - - true -> - msg = "economic events or commitments is required" - - cset - |> Changeset.add_error(:event_id, msg) - |> Changeset.add_error(:commitment_id, msg) - end -end end diff --git a/src/zenflows/vf/fulfillment.ex b/src/zenflows/vf/fulfillment.ex @@ -23,12 +23,13 @@ events that fully or partially satisfy one or more commitments. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Commitment, EconomicEvent, Measure, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -56,8 +57,8 @@ end @cast @reqr ++ ~w[resource_quantity effort_quantity note]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/intent.ex b/src/zenflows/vf/intent.ex @@ -22,6 +22,8 @@ to economic events (sometimes through commitments). """ use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.File alias Zenflows.VF.{ Action, @@ -33,7 +35,6 @@ alias Zenflows.VF.{ ResourceSpecification, SpatialThing, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -108,15 +109,15 @@ end ]a # in_scope_of_id @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) - |> mutex_check() + |> Validate.exist_xor([:provider_id, :receiver_id], method: :both) |> Validate.name(:name) |> Validate.note(:note) - |> Changeset.cast_assoc(:images, with: &File.chgset/2) + |> Changeset.cast_assoc(:images) |> Validate.class(:resource_classified_as) |> Measure.cast(:resource_quantity) |> Measure.cast(:effort_quantity) @@ -129,48 +130,4 @@ def chgset(schema \\ %__MODULE__{}, params) do |> Changeset.assoc_constraint(:resource_inventoried_as) |> Changeset.assoc_constraint(:at_location) end - -# Validate that provider and receiver are mutually exclusive. -@spec mutex_check(Changeset.t()) :: Changeset.t() -defp mutex_check(cset) do - # credo:disable-for-previous-line Credo.Check.Refactor.CyclomaticComplexity - - {data_prov, chng_prov, field_prov} = - case Changeset.fetch_field(cset, :provider_id) do - {:data, x} -> {x, nil, x} - {:changes, x} -> {nil, x, x} - end - {data_recv, chng_recv, field_recv} = - case Changeset.fetch_field(cset, :receiver_id) do - {:data, x} -> {x, nil, x} - {:changes, x} -> {nil, x, x} - end - - cond do - data_prov && chng_recv -> - msg = "receiver is not allowed in this record" - Changeset.add_error(cset, :receiver_id, msg) - - data_recv && chng_prov -> - msg = "provider is not allowed in this record" - Changeset.add_error(cset, :provider_id, msg) - - chng_prov && chng_recv -> - msg = "receiver is mutually exclusive with provider" - - cset - |> Changeset.add_error(:provider_id, msg) - |> Changeset.add_error(:receiver_id, msg) - - field_prov || field_recv -> - cset - - true -> - msg = "either provider or receiver is required" - - cset - |> Changeset.add_error(:provider_id, msg) - |> Changeset.add_error(:receiver_id, msg) - end -end end diff --git a/src/zenflows/vf/intent/domain.ex b/src/zenflows/vf/intent/domain.ex @@ -18,20 +18,15 @@ defmodule Zenflows.VF.Intent.Domain do @moduledoc "Domain logic of Intents." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{ Action, Intent, Measure, } -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Intent.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -42,100 +37,120 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Intent, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Intent.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Intent.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(Intent, page)} +end + +@spec all!(Page.t()) :: [Intent.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, Intent.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, Intent.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Intent.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: i}} -> {:ok, i} + {:ok, %{^key => value}} -> {:ok, value} {:error, _, cset, _} -> {:error, cset} end end -@spec update(id(), params()) :: - {:ok, Intent.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Intent.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Intent.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Intent.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: i}} -> {:ok, i} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Intent.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Intent.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, Intent.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, &(&1.one)) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: i}} -> {:ok, i} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id) :: Intent.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Intent.t(), :action | :input_of | :output_of | :provider | :receiver | :resource_inventoried_as | :resource_conforms_to | :resource_quantity | :effort_quantity | :available_quantity | :at_location | :published_in) :: Intent.t() -def preload(int, :action) do - Action.preload(int, :action) -end - -def preload(int, :input_of) do - Repo.preload(int, :input_of) -end - -def preload(int, :output_of) do - Repo.preload(int, :output_of) -end - -def preload(int, :provider) do - Repo.preload(int, :provider) -end - -def preload(int, :receiver) do - Repo.preload(int, :receiver) -end - -def preload(int, :resource_inventoried_as) do - Repo.preload(int, :resource_inventoried_as) -end - -def preload(int, :resource_conforms_to) do - Repo.preload(int, :resource_conforms_to) -end - -def preload(int, :resource_quantity) do - Measure.preload(int, :resource_quantity) -end - -def preload(int, :effort_quantity) do - Measure.preload(int, :effort_quantity) -end - -def preload(int, :available_quantity) do - Measure.preload(int, :available_quantity) -end - -def preload(int, :at_location) do - Repo.preload(int, :at_location) -end - -def preload(int, :published_in) do - Repo.preload(int, :published_in) +def preload(int, x) when x in ~w[ + input_of output_of provider receiver resource_inventoried_as + resource_conforms_to at_location published_in +]a, + do: Repo.preload(int, x) +def preload(int, :action), do: Action.preload(int, :action) +def preload(int, x) when x in ~w[ + effort_quantity available_quantity resource_quantity +]a, + do: Measure.preload(int, x) + +@spec multi_key() :: atom() +def multi_key(), do: :intent + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Intent.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Intent.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/intent/resolv.ex b/src/zenflows/vf/intent/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Intent.Resolv do -@moduledoc "Resolvers of Intent." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.Intent.Domain def intent(params, _) do @@ -25,7 +26,10 @@ def intent(params, _) do end def intents(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_intent(%{intent: params}, _) do diff --git a/src/zenflows/vf/intent/type.ex b/src/zenflows/vf/intent/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Intent.Type do -@moduledoc "GraphQL types of Intents." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/measure.ex b/src/zenflows/vf/measure.ex @@ -23,6 +23,8 @@ unit. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.Schema alias Zenflows.VF.Unit @type t() :: %__MODULE__{ @@ -45,38 +47,35 @@ and `Zenflows.VF.ScenarioDefinition` modules. def cast(cset, key) do case Changeset.fetch_change(cset, key) do {:ok, params} -> - case chgset(params) do + case changeset(params) do %{valid?: true} = cset_meas -> cset - |> Changeset.put_change(field_has_unit_id(key), + |> Changeset.put_change(:"#{key}_has_unit_id", Changeset.fetch_change!(cset_meas, :has_unit_id)) - |> Changeset.put_change(field_has_numerical_value(key), + |> Changeset.put_change(:"#{key}_has_numerical_value", Changeset.fetch_change!(cset_meas, :has_numerical_value)) - cset_meas -> Changeset.traverse_errors(cset_meas, fn {msg, opts} -> Regex.replace(~r"%{(\w+)}", msg, fn _, key -> - opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() + opts |> Keyword.get(:"#{key}", key) |> to_string() end) end) |> Enum.reduce(cset, fn {field, msg}, acc -> Changeset.add_error(acc, key, "#{field}: #{msg}") end) end - :error -> # Ecto seems to convert the params' keys to string # whether they were originally string or atom. - strkey = Atom.to_string(key) + strkey = "#{key}" case cset.params do # If the field `key` is set to `nil`, # this will set the associated fields to # `nil` as well. %{^strkey => nil} -> cset - |> Changeset.force_change(field_has_unit_id(key), nil) - |> Changeset.force_change(field_has_numerical_value(key), nil) - + |> Changeset.force_change(:"#{key}_has_unit_id", nil) + |> Changeset.force_change(:"#{key}_has_numerical_value", nil) _ -> cset end @@ -84,8 +83,7 @@ def cast(cset, key) do |> case do # if not embedded schema, which doesn't allow `assoc_constraint/3`... %{data: %{__meta__: %Ecto.Schema.Metadata{}}} = cset -> - Changeset.assoc_constraint(cset, String.to_existing_atom("#{key}_has_unit")) - + Changeset.assoc_constraint(cset, :"#{key}_has_unit") # this case is most useful when testing, which you use emebedded schemas cset -> cset @@ -100,29 +98,19 @@ as a %Measure{} struct. Useful for GraphQL types as can be seen in @spec preload(Schema.t(), atom()) :: Schema.t() def preload(schema, key) do %{schema | key => %__MODULE__{ - has_unit_id: Map.get(schema, field_has_unit_id(key)), - has_numerical_value: Map.get(schema, field_has_numerical_value(key)), + has_unit_id: Map.get(schema, :"#{key}_has_unit_id"), + has_numerical_value: Map.get(schema, :"#{key}_has_numerical_value"), }} end @cast ~w[has_unit_id has_numerical_value]a @reqr @cast -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do +@spec changeset(Schema.params()) :: Changeset.t() +defp changeset(params) do %__MODULE__{} |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) |> Changeset.validate_number(:has_numerical_value, greater_than: 0) end - -@spec field_has_unit_id(atom()) :: atom() -defp field_has_unit_id(key) do - String.to_existing_atom("#{key}_has_unit_id") -end - -@spec field_has_numerical_value(atom()) :: atom() -defp field_has_numerical_value(key) do - String.to_existing_atom("#{key}_has_numerical_value") -end end diff --git a/src/zenflows/vf/measure/resolv.ex b/src/zenflows/vf/measure/resolv.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Measure.Resolv do -@moduledoc "Resolvers of Measures." +@moduledoc false alias Zenflows.VF.{Measure, Measure.Domain} diff --git a/src/zenflows/vf/measure/type.ex b/src/zenflows/vf/measure/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Measure.Type do -@moduledoc "GraphQL types of Measures." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/organization.ex b/src/zenflows/vf/organization.ex @@ -20,8 +20,10 @@ defmodule Zenflows.VF.Organization do use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.File -alias Zenflows.VF.{SpatialThing, Validate} +alias Zenflows.VF.SpatialThing @type t() :: %__MODULE__{ type: :org, @@ -47,14 +49,14 @@ end @cast @reqr ++ ~w[classified_as note primary_location_id]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) |> Validate.name(:name) |> Validate.note(:note) - |> Changeset.cast_assoc(:images, with: &File.chgset/2) + |> Changeset.cast_assoc(:images) |> Validate.class(:classified_as) |> Changeset.assoc_constraint(:primary_location) end diff --git a/src/zenflows/vf/organization/domain.ex b/src/zenflows/vf/organization/domain.ex @@ -18,80 +18,128 @@ defmodule Zenflows.VF.Organization.Domain do @moduledoc "Domain logic of Organizations." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{Organization, Organization.Filter} -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Organization.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) def one(repo, clauses) do - clauses = if(is_map(clauses), - do: Map.put(clauses, :type, :org), - else: Keyword.put(clauses, :type, :org)) - case repo.get_by(Organization, clauses) do + import Ecto.Query + + case repo.get_by(where(Organization, type: :org), clauses) do nil -> {:error, "not found"} found -> {:ok, found} end end -@spec all(Paging.params()) :: Filter.error() | Paging.result() -def all(params \\ %{}) do - with {:ok, q} <- Filter.filter(params[:filter] || %{}) do - Paging.page(q, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Organization.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Organization.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + with {:ok, q} <- Filter.all(page) do + {:ok, Page.all(q, page)} end end -@spec create(params()) :: {:ok, Organization.t()} | {:error, chgset()} +@spec all!(Page.t()) :: [Organization.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) :: {:ok, Organization.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Organization.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: o}} -> {:ok, o} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, Organization.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Organization.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Organization.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Organization.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: o}} -> {:ok, o} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Organization.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Organization.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, Organization.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, &(&1.one)) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: o}} -> {:ok, o} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: Organization.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Organization.t(), :images | :primary_location) :: Organization.t() -def preload(org, :images) do - Repo.preload(org, :images) +def preload(org, x) when x in ~w[images primary_location]a do + Repo.preload(org, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :organization + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Organization.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Organization.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(org, :primary_location) do - Repo.preload(org, :primary_location) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/organization/filter.ex b/src/zenflows/vf/organization/filter.ex @@ -16,40 +16,33 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Organization.Filter do -@moduledoc "Filtering logic of Organizations." - -use Zenflows.DB.Schema +@moduledoc false import Ecto.Query -alias Ecto.Query -alias Zenflows.DB.Filter -alias Zenflows.VF.{Organization, Validate} - -@type error() :: Filter.error() +alias Ecto.{Changeset, Queryable} +alias Zenflows.DB.{Page, Schema, Validate} +alias Zenflows.VF.Organization -@spec filter(Filter.params()) :: Filter.result() -def filter(params) do - case chgset(params) do - %{valid?: true, changes: c} -> - {:ok, Enum.reduce(c, where(Organization, type: :org), &f(&2, &1))} - %{valid?: false} = cset -> - {:error, cset} +@spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def all(%{filter: nil}), do: {:ok, where(Organization, type: :org)} +def all(%{filter: params}) do + with {:ok, filters} <- all_validate(params) do + Enum.reduce(filters, where(Organization, type: :org), &all_f(&2, &1)) end end -@spec f(Query.t(), {atom(), term()}) :: Query.t() -defp f(q, {:name, v}), - do: where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) - -embedded_schema do - field :name, :string -end +@spec all_f(Queryable.t(), {atom(), term()}) :: Queryable.t() +defp all_f(q, {:name, v}), + do: where(q, [x], ilike(x.name, ^"%#{v}%")) -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do - %__MODULE__{} +@spec all_validate(Schema.params()) + :: {:ok, Changeset.data()} | {:error, Changeset.t()} +defp all_validate(params) do + {%{}, %{name: :string}} |> Changeset.cast(params, [:name]) |> Validate.name(:name) + |> Validate.escape_like(:name) + |> Changeset.apply_action(nil) end end diff --git a/src/zenflows/vf/organization/resolv.ex b/src/zenflows/vf/organization/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Organization.Resolv do -@moduledoc "Resolvers of Organizations." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.Organization.Domain def organization(params, _) do @@ -25,7 +26,10 @@ def organization(params, _) do end def organizations(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_organization(%{organization: params}, _) do diff --git a/src/zenflows/vf/organization/type.ex b/src/zenflows/vf/organization/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Organization.Type do -@moduledoc "GraphQL types of Organizations." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/person.ex b/src/zenflows/vf/person.ex @@ -20,8 +20,10 @@ defmodule Zenflows.VF.Person do use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.File -alias Zenflows.VF.{SpatialThing, Validate} +alias Zenflows.VF.SpatialThing @type t() :: %__MODULE__{ type: :per, @@ -69,22 +71,22 @@ end # insert changeset @doc false -@spec chgset(params()) :: Changeset.t() -def chgset(params) do +@spec changeset(Schema.params()) :: Changeset.t() +def changeset(params) do %__MODULE__{} |> Changeset.cast(params, @insert_cast) |> Changeset.validate_required(@insert_reqr) |> Validate.name(:name) |> Validate.name(:user) |> Validate.name(:email) - |> Changeset.cast_assoc(:images, with: &File.chgset/2) + |> Changeset.cast_assoc(:images) |> Validate.note(:note) |> Validate.key(:ecdh_public_key) |> Validate.key(:eddsa_public_key) |> Validate.key(:ethereum_address) |> Validate.key(:reflow_public_key) |> Validate.key(:schnorr_public_key) - |> check_email() + |> Validate.email(:email) |> Changeset.unique_constraint(:user) |> Changeset.unique_constraint(:name) |> Changeset.unique_constraint(:email) @@ -93,23 +95,16 @@ end # update changeset @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema, params) do schema |> Changeset.cast(params, @update_cast) |> Validate.name(:name) |> Validate.name(:user) |> Validate.note(:note) - |> check_email() + |> Validate.email(:email) |> Changeset.unique_constraint(:user) |> Changeset.unique_constraint(:name) |> Changeset.assoc_constraint(:primary_location) end - -# Validate that :email is a valid email address. -@spec check_email(Changeset.t()) :: Changeset.t() -defp check_email(cset) do - # works good enough for now - Changeset.validate_format(cset, :email, ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/) -end end diff --git a/src/zenflows/vf/person/domain.ex b/src/zenflows/vf/person/domain.ex @@ -20,44 +20,48 @@ defmodule Zenflows.VF.Person.Domain do import Ecto.Query -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{Person, Person.Filter} -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) :: {:ok, Person.t()} | {:error, String.t()} +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: {:ok, Person.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) def one(repo, clauses) do - clauses = if(is_map(clauses), - do: Map.put(clauses, :type, :per), - else: Keyword.put(clauses, :type, :per)) - case repo.get_by(Person, clauses) do + case repo.get_by(where(Person, type: :per), clauses) do nil -> {:error, "not found"} found -> {:ok, found} end end -@spec all(Paging.params()) :: Filter.error() | Paging.result() -def all(params \\ %{}) do - with {:ok, q} <- Filter.filter(params[:filter] || %{}) do - Paging.page(q, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Person.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Person.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + with {:ok, q} <- Filter.all(page) do + {:ok, Page.all(q, page)} end end +@spec all!(Page.t()) :: [Person.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + @spec exists?(Keyword.t()) :: boolean() def exists?(conds) do where(Person, ^conds) |> where(type: :per) |> Repo.exists?() end -@spec pubkey(id()) :: {:ok, String.t()} | {:error, String.t()} +@spec pubkey(Schema.id()) :: {:ok, nil | String.t()} | {:error, String.t()} def pubkey(id) do - where(Person, id: ^id) - |> where(type: :per) + where(Person, type: :per, id: ^id) |> select([:eddsa_public_key]) |> Repo.one() |> case do nil -> {:error, "not found"} @@ -65,50 +69,92 @@ def pubkey(id) do end end -@spec create(params()) :: {:ok, Person.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, Person.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Person.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: p}} -> {:ok, p} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, Person.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Person.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Person.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Person.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Person.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Person.t() +def update!(id, params) do + {:ok, value} = __MODULE__.update(id, params) + value +end + +@spec delete(Schema.id()) :: + {:ok, Person.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, &(&1.one)) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: Person.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Person.t(), :images | :primary_location) :: Person.t() -def preload(per, :images) do - Repo.preload(per, :images) +def preload(per, x) when x in ~w[images primary_location]a do + Repo.preload(per, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :person + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Person.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Person.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(per, :primary_location) do - Repo.preload(per, :primary_location) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/person/filter.ex b/src/zenflows/vf/person/filter.ex @@ -16,56 +16,47 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Person.Filter do -@moduledoc "Filtering logic of Persons." - -use Zenflows.DB.Schema +@moduledoc false import Ecto.Query -alias Ecto.Query -alias Zenflows.DB.Filter -alias Zenflows.VF.{Person, Validate} - -@type error() :: Filter.error() +alias Ecto.{Changeset, Queryable} +alias Zenflows.DB.{Page, Schema, Validate} +alias Zenflows.VF.Person -@spec filter(Filter.params()) :: Filter.result() -def filter(params) do - case chgset(params) do - %{valid?: true, changes: c} -> - {:ok, Enum.reduce(c, where(Person, type: :per), &f(&2, &1))} - %{valid?: false} = cset -> - {:error, cset} +@spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def all(%{filter: nil}), do: {:ok, where(Person, type: :per)} +def all(%{filter: params}) do + with {:ok, filters} <- all_validate(params) do + Enum.reduce(filters, where(Person, type: :per), &all_f(&2, &1)) end end -@spec f(Query.t(), {atom(), term()}) :: Query.t() -defp f(q, {:name, v}), - do: where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) -defp f(q, {:or_name, v}), - do: or_where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) -defp f(q, {:user, v}), - do: where(q, [x], ilike(x.user, ^"%#{Filter.escape_like(v)}%")) -defp f(q, {:or_user, v}), - do: or_where(q, [x], ilike(x.user, ^"%#{Filter.escape_like(v)}%")) - -embedded_schema do - field :name, :string - field :or_name, :string - field :user, :string - field :or_user, :string -end - -@cast ~w[name or_name user or_user]a - -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do - %__MODULE__{} - |> Changeset.cast(params, @cast) +@spec all_f(Queryable.t(), {atom(), term()}) :: Queryable.t() +defp all_f(q, {:name, v}), + do: where(q, [x], ilike(x.name, ^"%#{v}%")) +defp all_f(q, {:or_name, v}), + do: or_where(q, [x], ilike(x.name, ^"%#{v}%")) +defp all_f(q, {:user, v}), + do: where(q, [x], ilike(x.user, ^"%#{v}%")) +defp all_f(q, {:or_user, v}), + do: or_where(q, [x], ilike(x.user, ^"%#{v}")) + +@spec all_validate(Schema.params()) :: + {:ok, Changeset.data()} | {:error, Changeset.t()} +defp all_validate(params) do + {%{}, %{name: :string, or_name: :string, user: :string, or_user: :string}} + |> Changeset.cast(params, ~w[name or_name user or_user]a) |> Validate.name(:name) |> Validate.name(:or_name) |> Validate.name(:user) |> Validate.name(:or_user) - |> Filter.check_xor(:name, :or_name) - |> Filter.check_xor(:user, :or_user) + |> Validate.exist_xor([:name, :or_name]) + |> Validate.exist_xor([:user, :or_user]) + |> Validate.escape_like(:name) + |> Validate.escape_like(:or_name) + |> Validate.escape_like(:user) + |> Validate.escape_like(:or_user) + |> Changeset.apply_action(nil) end end diff --git a/src/zenflows/vf/person/resolv.ex b/src/zenflows/vf/person/resolv.ex @@ -16,8 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Person.Resolv do -@moduledoc "Resolvers of Persons." +@moduledoc false +alias Ecto.Changeset +alias Zenflows.DB.Validate +alias Zenflows.GQL.Connection alias Zenflows.VF.Person.Domain def person(params, _) do @@ -25,7 +28,16 @@ def person(params, _) do end def people(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end +end + +def person_exists(params, _) do + with {:ok, parsed} <- parse_person_exists(params) do + {:ok, parsed |> Map.to_list() |> Domain.exists?()} + end end def person_check(params, _) do @@ -63,4 +75,14 @@ def primary_location(per, _, _) do per = Domain.preload(per, :primary_location) {:ok, per.primary_location} end + +@spec parse_person_exists(map()) :: {:ok, map()} | {:error, Changeset.t()} +def parse_person_exists(params) do + {%{}, %{email: :string, user: :string}} + |> Changeset.cast(params, [:email, :user]) + |> Validate.exist_xor([:email, :user]) + |> Validate.email(:email) + |> Validate.name(:user) + |> Changeset.apply_action(nil) +end end diff --git a/src/zenflows/vf/person/type.ex b/src/zenflows/vf/person/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Person.Type do -@moduledoc "GraphQL types of Persons." +@moduledoc false use Absinthe.Schema.Notation @@ -180,6 +180,14 @@ object :query_person do resolve &Resolv.people/2 end + @desc "Check if a person exists by email xor username." + field :person_exists, non_null(:boolean) do + meta only_guest?: true + arg :email, :string + arg :user, :string + resolve &Resolv.person_exists/2 + end + @desc "If exists, find a person by email and eddsa-public-key." field :person_check, non_null(:person) do meta only_guest?: true diff --git a/src/zenflows/vf/plan.ex b/src/zenflows/vf/plan.ex @@ -23,7 +23,9 @@ with defined deliverable(s). use Zenflows.DB.Schema -alias Zenflows.VF.{Scenario, Validate} +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} +alias Zenflows.VF.Scenario @type t() :: %__MODULE__{ name: String.t(), @@ -44,8 +46,8 @@ end @cast @reqr ++ ~w[due note refinement_of_id]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/plan/domain.ex b/src/zenflows/vf/plan/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.Plan.Domain do @moduledoc "Domain logic of Plans." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.Plan -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Plan.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,51 +33,108 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Plan, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Plan.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Plan.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(Plan, page)} +end + +@spec all!(Page.t()) :: [Plan.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, Plan.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, Plan.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Plan.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: p}} -> {:ok, p} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) :: - {:ok, Plan.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Plan.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Plan.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Plan.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Plan.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Plan.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, Plan.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: Plan.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Plan.t(), :refinement_of) :: Plan.t() def preload(plan, :refinement_of) do Repo.preload(plan, :refinement_of) end + +@spec multi_key() :: atom() +def multi_key(), do: :plan + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Plan.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Plan.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/plan/resolv.ex b/src/zenflows/vf/plan/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Plan.Resolv do -@moduledoc "Resolvers of Plan." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.Plan.Domain def plan(params, _) do @@ -27,7 +28,10 @@ def plan(params, _) do end def plans(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_plan(%{plan: params}, _) do diff --git a/src/zenflows/vf/plan/type.ex b/src/zenflows/vf/plan/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Plan.Type do -@moduledoc "GraphQL types of Plans." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/process.ex b/src/zenflows/vf/process.ex @@ -23,12 +23,13 @@ transport economic resource(s). use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ EconomicEvent, Plan, ProcessSpecification, Scenario, - Validate, } @type t() :: %__MODULE__{ @@ -70,8 +71,8 @@ end ]a # in_scope_of_id @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/process/domain.ex b/src/zenflows/vf/process/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.Process.Domain do @moduledoc "Domain logic of Processes." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.Process -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Process.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,60 +33,109 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Process, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Process.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Process.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(Process, page)} +end + +@spec all!(Page.t()) :: [Process.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, Process.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, Process.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Process.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: p}} -> {:ok, p} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, Process.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Process.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Process.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Process.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Process.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Process.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, Process.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: Process.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Process.t(), :based_on | :planned_within | :nested_in) :: Process.t() -def preload(proc, :based_on) do - Repo.preload(proc, :based_on) +def preload(proc, x) when x in ~w[based_on planned_within nested_in]a do + Repo.preload(proc, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :process + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Process.changeset(params)) end -def preload(proc, :planned_within) do - Repo.preload(proc, :planned_within) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Process.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(proc, :nested_in) do - Repo.preload(proc, :nested_in) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/process/resolv.ex b/src/zenflows/vf/process/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Process.Resolv do -@moduledoc "Resolvers of Process." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.Process.Domain def process(params, _) do @@ -27,7 +28,10 @@ def process(params, _) do end def processes(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_process(%{process: params}, _) do diff --git a/src/zenflows/vf/process/type.ex b/src/zenflows/vf/process/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Process.Type do -@moduledoc "GraphQL types of Processs." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/process_specification.ex b/src/zenflows/vf/process_specification.ex @@ -22,7 +22,8 @@ Specifies the kind of process. use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ name: String.t(), @@ -39,8 +40,8 @@ end @cast @reqr ++ [:note] @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/process_specification/domain.ex b/src/zenflows/vf/process_specification/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.ProcessSpecification.Domain do @moduledoc "Domain logic of ProcessSpecifications." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.ProcessSpecification -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, ProcessSpecification.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,47 +33,106 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(ProcessSpecification, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: ProcessSpecification.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [ProcessSpecification.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(ProcessSpecification, page)} +end + +@spec all!(Page.t()) :: [ProcessSpecification.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, ProcessSpecification.t()} | {:error, chgset()} +@spec create(Schema.params()) + :: {:ok, ProcessSpecification.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, ProcessSpecification.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: p}} -> {:ok, p} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, ProcessSpecification.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: ProcessSpecification.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, ProcessSpecification.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &ProcessSpecification.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, ProcessSpecification.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: ProcessSpecification.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, ProcessSpecification.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id()) :: ProcessSpecification.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :process_specification + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, ProcessSpecification.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &ProcessSpecification.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/process_specification/resolv.ex b/src/zenflows/vf/process_specification/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ProcessSpecification.Resolv do -@moduledoc "Resolvers of ProcessSpecifications." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.ProcessSpecification.Domain def process_specification(params, _) do @@ -25,7 +26,10 @@ def process_specification(params, _) do end def process_specifications(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_process_specification(%{process_specification: params}, _) do diff --git a/src/zenflows/vf/process_specification/type.ex b/src/zenflows/vf/process_specification/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ProcessSpecification.Type do -@moduledoc "GraphQL types of ProcessSpecifications." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/product_batch.ex b/src/zenflows/vf/product_batch.ex @@ -23,7 +23,8 @@ same way. use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ batch_number: String.t(), @@ -42,8 +43,8 @@ end @cast @reqr ++ ~w[expiry_date production_date]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/product_batch/domain.ex b/src/zenflows/vf/product_batch/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.ProductBatch.Domain do @moduledoc "Domain logic of ProductBatches." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.ProductBatch -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, ProductBatch.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,46 +33,103 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(ProductBatch, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: ProductBatch.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [ProductBatch.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(ProductBatch, page)} +end + +@spec all!(Page.t()) :: [ProductBatch.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, ProductBatch.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, ProductBatch.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, ProductBatch.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: pb}} -> {:ok, pb} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, ProductBatch.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: ProductBatch.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, ProductBatch.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &ProductBatch.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: pb}} -> {:ok, pb} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, ProductBatch.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: ProductBatch.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, ProductBatch.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: pb}} -> {:ok, pb} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id()) :: ProductBatch.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :product_batch + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, ProductBatch.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &ProductBatch.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/product_batch/resolv.ex b/src/zenflows/vf/product_batch/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ProductBatch.Resolv do -@moduledoc "Resolvers of ProductBatch." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.ProductBatch.Domain def product_batch(params, _) do @@ -27,7 +28,10 @@ def product_batch(params, _) do end def product_batches(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_product_batch(%{product_batch: params}, _) do diff --git a/src/zenflows/vf/product_batch/type.ex b/src/zenflows/vf/product_batch/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ProductBatch.Type do -@moduledoc "GraphQL types of ProductBatches." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/proposal.ex b/src/zenflows/vf/proposal.ex @@ -22,11 +22,12 @@ Published requests or offers, sometimes with what is expected in return. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Intent, ProposedIntent, SpatialThing, - Validate, } @type t() :: %__MODULE__{ @@ -60,8 +61,8 @@ end @cast ~w[name has_beginning has_end unit_based note eligible_location_id]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Validate.name(:name) diff --git a/src/zenflows/vf/proposal/domain.ex b/src/zenflows/vf/proposal/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.Proposal.Domain do @moduledoc "Domain logic of Proposals." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{Proposal, Proposal.Filter} -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Proposal.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,68 +33,115 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Filter.error() | Paging.result() -def all(params \\ %{}) do - with {:ok, q} <- Filter.filter(params[:filter] || %{}) do - Paging.page(q, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Proposal.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Proposal.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + with {:ok, q} <- Filter.all(page) do + {:ok, Page.all(q, page)} end end -@spec create(params()) :: {:ok, Proposal.t()} | {:error, chgset()} +@spec all!(Page.t()) :: [Proposal.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) :: {:ok, Proposal.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Proposal.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: p}} -> {:ok, p} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) :: - {:ok, Proposal.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Proposal.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Proposal.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Proposal.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: p}} -> {:ok, p} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, Proposal.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Proposal.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, Proposal.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: pi}} -> {:ok, pi} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: Proposal.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Proposal.t(), :eligible_location | :publishes | :primary_intents | :reciprocal_intents) :: Proposal.t() -def preload(prop, :eligible_location) do - Repo.preload(prop, :eligible_location) +def preload(prop, x) when x in ~w[ + eligible_location publishes primary_intents reciprocal_intents +]a do + Repo.preload(prop, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :proposal + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) end -def preload(prop, :publishes) do - Repo.preload(prop, :publishes) +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Proposal.changeset(params)) end -def preload(prop, :primary_intents) do - Repo.preload(prop, :primary_intents) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Proposal.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(prop, :reciprocal_intents) do - Repo.preload(prop, :reciprocal_intents) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/proposal/filter.ex b/src/zenflows/vf/proposal/filter.ex @@ -16,81 +16,76 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Proposal.Filter do -@moduledoc "Filtering logic of Proposals." - -use Zenflows.DB.Schema +@moduledoc false import Ecto.Query -alias Ecto.Query -alias Zenflows.DB.{Filter, ID} -alias Zenflows.VF.{Proposal, Validate} - -@type error() :: Filter.error() +alias Ecto.{Changeset, Queryable} +alias Zenflows.DB.{ID, Page, Schema, Validate} +alias Zenflows.VF.Proposal -@spec filter(Filter.params()) :: Filter.result() -def filter(params) do - case chgset(params) do - %{valid?: true, changes: c} -> - {:ok, Enum.reduce(c, Proposal, &f(&2, &1))} - %{valid?: false} = cset -> - {:error, cset} +@spec all(Page.t()) :: {:ok, Queryable.t()} | {:error, Changeset.t()} +def all(%{filter: nil}), do: {:ok, Proposal} +def all(%{filter: params}) do + with {:ok, filters} <- all_validate(params) do + Enum.reduce(filters, Proposal, &all_f(&2, &1)) end end -@spec f(Query.t(), {atom(), term()}) :: Query.t() -defp f(q, {:primary_intents_resource_inventoried_as_conforms_to, v}) do +@spec all_f(Queryable.t(), {atom(), term()}) :: Queryable.t() +defp all_f(q, {:primary_intents_resource_inventoried_as_conforms_to, v}) do q |> join(:primary_intents_resource_inventoried_as) |> where([primary_intents_resource_inventoried_as: r], r.conforms_to_id in ^v) end -defp f(q, {:or_primary_intents_resource_inventoried_as_conforms_to, v}) do +defp all_f(q, {:or_primary_intents_resource_inventoried_as_conforms_to, v}) do q |> join(:primary_intents_resource_inventoried_as) |> or_where([primary_intents_resource_inventoried_as: r], r.conforms_to_id in ^v) end -defp f(q, {:primary_intents_resource_inventoried_as_primary_accountable, v}) do +defp all_f(q, {:primary_intents_resource_inventoried_as_primary_accountable, v}) do q |> join(:primary_intents_resource_inventoried_as) |> where([primary_intents_resource_inventoried_as: r], r.primary_accountable_id in ^v) end -defp f(q, {:or_primary_intents_resource_inventoried_as_primary_accountable, v}) do +defp all_f(q, {:or_primary_intents_resource_inventoried_as_primary_accountable, v}) do q |> join(:primary_intents_resource_inventoried_as) |> or_where([primary_intents_resource_inventoried_as: r], r.primary_accountable_id in ^v) end -defp f(q, {:primary_intents_resource_inventoried_as_classified_as, v}) do +defp all_f(q, {:primary_intents_resource_inventoried_as_classified_as, v}) do q |> join(:primary_intents_resource_inventoried_as) |> where([primary_intents_resource_inventoried_as: r], fragment("? @> ?", r.classified_as, ^v)) end -defp f(q, {:or_primary_intents_resource_inventoried_as_classified_as, v}) do +defp all_f(q, {:or_primary_intents_resource_inventoried_as_classified_as, v}) do q |> join(:primary_intents_resource_inventoried_as) |> or_where([primary_intents_resource_inventoried_as: r], fragment("? @> ?", r.classified_as, ^v)) end -defp f(q, {:primary_intents_resource_inventoried_as_name, v}) do +defp all_f(q, {:primary_intents_resource_inventoried_as_name, v}) do q |> join(:primary_intents_resource_inventoried_as) - |> where([primary_intents_resource_inventoried_as: r], ilike(r.name, ^"%#{Filter.escape_like(v)}%")) + |> where([primary_intents_resource_inventoried_as: r], ilike(r.name, ^"%#{v}%")) end -defp f(q, {:or_primary_intents_resource_inventoried_as_name, v}) do +defp all_f(q, {:or_primary_intents_resource_inventoried_as_name, v}) do q |> join(:primary_intents_resource_inventoried_as) - |> or_where([primary_intents_resource_inventoried_as: r], ilike(r.name, ^"%#{Filter.escape_like(v)}%")) + |> or_where([primary_intents_resource_inventoried_as: r], ilike(r.name, ^v)) end -defp f(q, {:primary_intents_resource_inventoried_as_id, v}) do +defp all_f(q, {:primary_intents_resource_inventoried_as_id, v}) do q |> join(:primary_intents_resource_inventoried_as) |> where([primary_intents_resource_inventoried_as: r], r.id in ^v) end -defp f(q, {:or_primary_intents_resource_inventoried_as_id, v}) do +defp all_f(q, {:or_primary_intents_resource_inventoried_as_id, v}) do q |> join(:primary_intents_resource_inventoried_as) |> or_where([primary_intents_resource_inventoried_as: r], r.id in ^v) end # join primary_intents +@spec join(Queryable.t(), atom()) :: Queryable.t() defp join(q, :primary_intents) do if has_named_binding?(q, :primary_intents), do: q, @@ -105,36 +100,33 @@ defp join(q, :primary_intents_resource_inventoried_as) do as: :primary_intents_resource_inventoried_as) end -embedded_schema do - field :primary_intents_resource_inventoried_as_conforms_to, {:array, ID} - field :or_primary_intents_resource_inventoried_as_conforms_to, {:array, ID} - field :primary_intents_resource_inventoried_as_primary_accountable, {:array, ID} - field :or_primary_intents_resource_inventoried_as_primary_accountable, {:array, ID} - field :primary_intents_resource_inventoried_as_classified_as, {:array, :string} - field :or_primary_intents_resource_inventoried_as_classified_as, {:array, :string} - field :primary_intents_resource_inventoried_as_name, :string - field :or_primary_intents_resource_inventoried_as_name, :string - field :primary_intents_resource_inventoried_as_id, {:array, ID} - field :or_primary_intents_resource_inventoried_as_id, {:array, ID} -end - -@cast ~w[ - primary_intents_resource_inventoried_as_conforms_to - or_primary_intents_resource_inventoried_as_conforms_to - primary_intents_resource_inventoried_as_primary_accountable - or_primary_intents_resource_inventoried_as_primary_accountable - primary_intents_resource_inventoried_as_classified_as - or_primary_intents_resource_inventoried_as_classified_as - primary_intents_resource_inventoried_as_name - or_primary_intents_resource_inventoried_as_name - primary_intents_resource_inventoried_as_id - or_primary_intents_resource_inventoried_as_id -]a - -@spec chgset(params()) :: Changeset.t() -defp chgset(params) do - %__MODULE__{} - |> Changeset.cast(params, @cast) +@spec all_validate(Schema.params()) + :: {:ok, Changeset.data()} | {:error, Changeset.t()} +defp all_validate(params) do + {%{}, %{ + primary_intents_resource_inventoried_as_conforms_to: {:array, ID}, + or_primary_intents_resource_inventoried_as_conforms_to: {:array, ID}, + primary_intents_resource_inventoried_as_primary_accountable: {:array, ID}, + or_primary_intents_resource_inventoried_as_primary_accountable: {:array, ID}, + primary_intents_resource_inventoried_as_classified_as: {:array, :string}, + or_primary_intents_resource_inventoried_as_classified_as: {:array, :string}, + primary_intents_resource_inventoried_as_name: :string, + or_primary_intents_resource_inventoried_as_name: :string, + primary_intents_resource_inventoried_as_id: {:array, ID}, + or_primary_intents_resource_inventoried_as_id: {:array, ID}, + }} + |> Changeset.cast(params, ~w[ + primary_intents_resource_inventoried_as_conforms_to + or_primary_intents_resource_inventoried_as_conforms_to + primary_intents_resource_inventoried_as_primary_accountable + or_primary_intents_resource_inventoried_as_primary_accountable + primary_intents_resource_inventoried_as_classified_as + or_primary_intents_resource_inventoried_as_classified_as + primary_intents_resource_inventoried_as_name + or_primary_intents_resource_inventoried_as_name + primary_intents_resource_inventoried_as_id + or_primary_intents_resource_inventoried_as_id + ]a) |> Validate.class(:primary_intents_resource_inventoried_as_conforms_to) |> Validate.class(:or_primary_intents_resource_inventoried_as_conforms_to) |> Validate.class(:primary_intents_resource_inventoried_as_primary_accountable) @@ -143,15 +135,18 @@ defp chgset(params) do |> Validate.class(:or_primary_intents_resource_inventoried_as_classified_as) |> Validate.name(:primary_intents_resource_inventoried_as_name) |> Validate.name(:or_primary_intents_resource_inventoried_as_name) - |> Filter.check_xor(:primary_intents_resource_inventoried_as_conforms_to, - :or_primary_intents_resource_inventoried_as_conforms_to) - |> Filter.check_xor(:primary_intents_resource_inventoried_as_primary_accountable, - :or_primary_intents_resource_inventoried_as_primary_accountable) - |> Filter.check_xor(:primary_intents_resource_inventoried_as_classified_as, - :or_primary_intents_resource_inventoried_as_classified_as) - |> Filter.check_xor(:primary_intents_resource_inventoried_as_name, - :or_primary_intents_resource_inventoried_as_name) - |> Filter.check_xor(:primary_intents_resource_inventoried_as_id, - :or_primary_intents_resource_inventoried_as_id) + |> Validate.exist_xor([:primary_intents_resource_inventoried_as_conforms_to, + :or_primary_intents_resource_inventoried_as_conforms_to]) + |> Validate.exist_xor([:primary_intents_resource_inventoried_as_primary_accountable, + :or_primary_intents_resource_inventoried_as_primary_accountable]) + |> Validate.exist_xor([:primary_intents_resource_inventoried_as_classified_as, + :or_primary_intents_resource_inventoried_as_classified_as]) + |> Validate.exist_xor([:primary_intents_resource_inventoried_as_name, + :or_primary_intents_resource_inventoried_as_name]) + |> Validate.exist_xor([:primary_intents_resource_inventoried_as_id, + :or_primary_intents_resource_inventoried_as_id]) + |> Validate.escape_like(:primary_intents_resource_inventoried_as_name) + |> Validate.escape_like(:or_primary_intents_resource_inventoried) + |> Changeset.apply_action(nil) end end diff --git a/src/zenflows/vf/proposal/resolv.ex b/src/zenflows/vf/proposal/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Proposal.Resolv do -@moduledoc "Resolvers of Proposal." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.Proposal.Domain def proposal(params, _) do @@ -25,7 +26,10 @@ def proposal(params, _) do end def proposals(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def offers(_params, _) do diff --git a/src/zenflows/vf/proposal/type.ex b/src/zenflows/vf/proposal/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Proposal.Type do -@moduledoc "GraphQL types of Proposals." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/proposed_intent.ex b/src/zenflows/vf/proposed_intent.ex @@ -24,6 +24,8 @@ including multiple intents. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.Schema alias Zenflows.VF.{Intent, Proposal} @type t() :: %__MODULE__{ @@ -43,8 +45,8 @@ end @cast @reqr ++ [:reciprocal] @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/proposed_intent/domain.ex b/src/zenflows/vf/proposed_intent/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.ProposedIntent.Domain do @moduledoc "Domain logic of ProposedIntents." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.ProposedIntent -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, ProposedIntent.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,43 +33,109 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(ProposedIntent, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: ProposedIntent.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [ProposedIntent.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(ProposedIntent, page)} end -@spec create(params()) :: {:ok, ProposedIntent.t()} | {:error, chgset()} +@spec all!(Page.t()) :: [ProposedIntent.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) :: {:ok, ProposedIntent.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() + Multi.new() + |> multi_insert(params) + |> Repo.transaction() + |> case do + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} + end +end + +@spec create!(Schema.params()) :: ProposedIntent.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, ProposedIntent.t()} | {:error, String.t() | Changeset.t()} +def update(id, params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, ProposedIntent.chgset(params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{insert: pi}} -> {:ok, pi} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, ProposedIntent.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: ProposedIntent.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, ProposedIntent.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: pi}} -> {:ok, pi} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: ProposedIntent.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(ProposedIntent.t(), :published_in | :publishes) :: ProposedIntent.t() -def preload(prop_int, :published_in) do - Repo.preload(prop_int, :published_in) +def preload(prop_int, x) when x in ~w[published_in publishes]a do + Repo.preload(prop_int, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :proposed_intent + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, ProposedIntent.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &ProposedIntent.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(prop_int, :publishes) do - Repo.preload(prop_int, :publishes) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/proposed_intent/resolv.ex b/src/zenflows/vf/proposed_intent/resolv.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ProposedIntent.Resolv do -@moduledoc "Resolvers of ProposedIntent." +@moduledoc false alias Zenflows.VF.ProposedIntent.Domain diff --git a/src/zenflows/vf/proposed_intent/type.ex b/src/zenflows/vf/proposed_intent/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ProposedIntent.Type do -@moduledoc "GraphQL types of ProposedIntent." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/proposed_to.ex b/src/zenflows/vf/proposed_to.ex @@ -23,6 +23,8 @@ published to many agents. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.Schema alias Zenflows.VF.{Agent, Proposal} @type t() :: %__MODULE__{ @@ -40,8 +42,8 @@ end @cast @reqr @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/recipe_exchange.ex b/src/zenflows/vf/recipe_exchange.ex @@ -22,7 +22,8 @@ Specifies an exchange agreement as part of a recipe. use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ name: String.t(), @@ -39,8 +40,8 @@ end @cast @reqr ++ [:note] @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/recipe_exchange/domain.ex b/src/zenflows/vf/recipe_exchange/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.RecipeExchange.Domain do @moduledoc "Domain logic of RecipeExchanges." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.RecipeExchange -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, RecipeExchange.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,47 +33,106 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(RecipeExchange, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: RecipeExchange.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [RecipeExchange.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(RecipeExchange, page)} +end + +@spec all!(Page.t()) :: [RecipeExchange.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, RecipeExchange.t()} | {:error, chgset()} +@spec create(Schema.params()) + :: {:ok, RecipeExchange.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, RecipeExchange.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: re}} -> {:ok, re} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, RecipeExchange.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: RecipeExchange.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, RecipeExchange.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &RecipeExchange.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: re}} -> {:ok, re} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, RecipeExchange.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: RecipeExchange.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, RecipeExchange.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: re}} -> {:ok, re} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id()) :: RecipeExchange.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :recipe_exchange + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, RecipeExchange.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &RecipeExchange.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/recipe_exchange/resolv.ex b/src/zenflows/vf/recipe_exchange/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeExchange.Resolv do -@moduledoc "Resolvers of RecipeExchanges." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.RecipeExchange.Domain def recipe_exchange(params, _) do @@ -25,7 +26,10 @@ def recipe_exchange(params, _) do end def recipe_exchanges(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_recipe_exchange(%{recipe_exchange: params}, _) do diff --git a/src/zenflows/vf/recipe_exchange/type.ex b/src/zenflows/vf/recipe_exchange/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeExchange.Type do -@moduledoc "GraphQL types of RecipeExchanges." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/recipe_flow.ex b/src/zenflows/vf/recipe_flow.ex @@ -22,6 +22,8 @@ The specification of a resource inflow to, or outflow from, a recipe process. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Action, Measure, @@ -29,7 +31,6 @@ alias Zenflows.VF.{ RecipeProcess, RecipeResource, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -68,38 +69,22 @@ end ]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) |> Validate.note(:note) |> Measure.cast(:effort_quantity) |> Measure.cast(:resource_quantity) - |> check_measures() + |> Validate.exist_or([ + :resource_quantity_has_unit_id, :effort_quantity_has_unit_id, + ], method: :both) + |> Validate.exist_or([ + :resource_quantity_has_numerical_value, :effort_quantity_has_numerical_value + ], method: :both) |> Changeset.assoc_constraint(:recipe_input_of) |> Changeset.assoc_constraint(:recipe_output_of) |> Changeset.assoc_constraint(:recipe_flow_resource) end - -# Validates that the DB doesn't end up with null measures, meaning either -# one must be filled. -@spec check_measures(Changeset.t()) :: Changeset.t() -defp check_measures(cset) do - # `*_has_numerical_value` fields not nil when `*_has_unit_id` - # fields are not nil. This is guranteed by the `Measure.cast/2` - # functions. - resqty = Changeset.get_field(cset, :resource_quantity_has_unit_id) - effqty = Changeset.get_field(cset, :effort_quantity_has_unit_id) - - if resqty || effqty do - cset - else - msg = "resource quantity and effort quantity cannot be null at the same time" - - cset - |> Changeset.add_error(:resource_quantity, msg) - |> Changeset.add_error(:effort_quantity, msg) - end -end end diff --git a/src/zenflows/vf/recipe_flow/domain.ex b/src/zenflows/vf/recipe_flow/domain.ex @@ -18,20 +18,11 @@ defmodule Zenflows.VF.RecipeFlow.Domain do @moduledoc "Domain logic of RecipeFlows." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} -alias Zenflows.VF.{ - Action, - Measure, - RecipeFlow, -} - -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} +alias Zenflows.VF.{Action, Measure, RecipeFlow} + +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, RecipeFlow.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -42,78 +33,117 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(RecipeFlow, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: RecipeFlow.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [RecipeFlow.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(RecipeFlow, page)} +end + +@spec all!(Page.t()) :: [RecipeFlow.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, RecipeFlow.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, RecipeFlow.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, RecipeFlow.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: rf}} -> {:ok, rf} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, RecipeFlow.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: RecipeFlow.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, RecipeFlow.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &RecipeFlow.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: rf}} -> {:ok, rf} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, RecipeFlow.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: RecipeFlow.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, RecipeFlow.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: rf}} -> {:ok, rf} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: RecipeFlow.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(RecipeFlow.t(), :resource_quantity | :effort_quantity | :recipe_flow_resource | :action | :recipe_input_of | :recipe_output_of | :recipe_clause_of) :: RecipeFlow.t() -def preload(rec_flow, :resource_quantity) do - Measure.preload(rec_flow, :resource_quantity) -end +def preload(rec_flow, x) when x in ~w[ + recipe_flow_resource recipe_input_of recipe_output_of recipe_clause_of +]a, + do: Repo.preload(rec_flow, x) +def preload(rec_flow, :action), + do: Action.preload(rec_flow, :action) +def preload(rec_flow, x) when x in ~w[resource_quantity effort_quantity]a, + do: Measure.preload(rec_flow, x) -def preload(rec_flow, :effort_quantity) do - Measure.preload(rec_flow, :effort_quantity) -end - -def preload(rec_flow, :recipe_flow_resource) do - Repo.preload(rec_flow, :recipe_flow_resource) -end +@spec multi_key() :: atom() +def multi_key(), do: :recipe_flow -def preload(rec_flow, :action) do - Action.preload(rec_flow, :action) +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) end -def preload(rec_flow, :recipe_input_of) do - Repo.preload(rec_flow, :recipe_input_of) +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, RecipeFlow.changeset(params)) end -def preload(rec_flow, :recipe_output_of) do - Repo.preload(rec_flow, :recipe_output_of) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &RecipeFlow.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(rec_flow, :recipe_clause_of) do - Repo.preload(rec_flow, :recipe_clause_of) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/recipe_flow/resolv.ex b/src/zenflows/vf/recipe_flow/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeFlow.Resolv do -@moduledoc "Resolvers of RecipeFlow." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.RecipeFlow.Domain def recipe_flow(params, _) do @@ -27,7 +28,10 @@ def recipe_flow(params, _) do end def recipe_flows(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_recipe_flow(%{recipe_flow: params}, _) do diff --git a/src/zenflows/vf/recipe_flow/type.ex b/src/zenflows/vf/recipe_flow/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeFlow.Type do -@moduledoc "GraphQL types of RecipeFlows." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/recipe_process.ex b/src/zenflows/vf/recipe_process.ex @@ -22,11 +22,12 @@ Specifies a process in a recipe for use in planning from recipe. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Duration, ProcessSpecification, TimeUnitEnum, - Validate, } @type t() :: %__MODULE__{ @@ -54,8 +55,8 @@ end @cast @reqr ++ ~w[process_classified_as note has_duration]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/recipe_process/domain.ex b/src/zenflows/vf/recipe_process/domain.ex @@ -16,18 +16,13 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeProcess.Domain do -@moduledoc "Domain logic of RecipeProcesss." +@moduledoc "Domain logic of RecipeProcesses." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{Duration, RecipeProcess} -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, RecipeProcess.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,57 +33,113 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(RecipeProcess, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: RecipeProcess.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value end -@spec create(params()) :: {:ok, RecipeProcess.t()} | {:error, chgset()} +@spec all(Page.t()) :: {:ok, [RecipeProcess.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(RecipeProcess, page)} +end + +@spec all!(Page.t()) :: [RecipeProcess.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) + :: {:ok, RecipeProcess.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, RecipeProcess.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: rp}} -> {:ok, rp} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, RecipeProcess.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: RecipeProcess.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, RecipeProcess.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &RecipeProcess.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: rp}} -> {:ok, rp} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, RecipeProcess.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: RecipeProcess.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, RecipeProcess.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: rp}} -> {:ok, rp} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: RecipeProcess.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(RecipeProcess.t(), :has_duration | :process_conforms_to) :: RecipeProcess.t() -def preload(rec_proc, :has_duration) do - Duration.preload(rec_proc, :has_duration) +def preload(rec_proc, :has_duration), + do: Duration.preload(rec_proc, :has_duration) +def preload(rec_proc, :process_conforms_to), + do: Repo.preload(rec_proc, :process_conforms_to) + +@spec multi_key() :: atom() +def multi_key(), do: :recipe_process + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, RecipeProcess.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &RecipeProcess.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(rec_proc, :process_conforms_to) do - Repo.preload(rec_proc, :process_conforms_to) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/recipe_process/resolv.ex b/src/zenflows/vf/recipe_process/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeProcess.Resolv do -@moduledoc "Resolvers of RecipeProcess." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.RecipeProcess.Domain def recipe_process(params, _) do @@ -27,7 +28,10 @@ def recipe_process(params, _) do end def recipe_processes(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_recipe_process(%{recipe_process: params}, _) do diff --git a/src/zenflows/vf/recipe_process/type.ex b/src/zenflows/vf/recipe_process/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeProcess.Type do -@moduledoc "GraphQL types of RecipeProcesses." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/recipe_resource.ex b/src/zenflows/vf/recipe_resource.ex @@ -23,12 +23,10 @@ recipe. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.File -alias Zenflows.VF.{ - ResourceSpecification, - Unit, - Validate, -} +alias Zenflows.VF.{ResourceSpecification, Unit} @type t() :: %__MODULE__{ name: String.t(), @@ -60,14 +58,14 @@ end ]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) |> Validate.name(:name) |> Validate.note(:note) - |> Changeset.cast_assoc(:images, with: &File.chgset/2) + |> Changeset.cast_assoc(:images) |> Validate.class(:resource_conforms_to) |> Changeset.assoc_constraint(:unit_of_resource) |> Changeset.assoc_constraint(:unit_of_effort) diff --git a/src/zenflows/vf/recipe_resource/domain.ex b/src/zenflows/vf/recipe_resource/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.RecipeResource.Domain do @moduledoc "Domain logic of RecipeResources." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.RecipeResource -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, RecipeResource.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,61 +33,111 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(RecipeResource, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: RecipeResource.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [RecipeResource.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(RecipeResource, page)} +end + +@spec all!(Page.t()) :: [RecipeResource.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, RecipeResource.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, RecipeResource.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, RecipeResource.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: rr}} -> {:ok, rr} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, RecipeResource.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: RecipeResource.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, RecipeResource.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &RecipeResource.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: rr}} -> {:ok, rr} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, RecipeResource.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: RecipeResource.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, RecipeResource.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: rr}} -> {:ok, rr} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: RecipeResource.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(RecipeResource.t(), :unit_of_resource | :unit_of_effort | :resource_conforms_to) :: RecipeResource.t() -def preload(rec_res, :unit_of_resource) do - Repo.preload(rec_res, :unit_of_resource) +def preload(rec_res, x) when x in ~w[ + unit_of_resource unit_of_effort resource_conforms_to +]a do + Repo.preload(rec_res, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :recipe_resource + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, RecipeResource.changeset(params)) end -def preload(rec_res, :unit_of_effort) do - Repo.preload(rec_res, :unit_of_effort) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &RecipeResource.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(rec_res, :resource_conforms_to) do - Repo.preload(rec_res, :resource_conforms_to) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/recipe_resource/resolv.ex b/src/zenflows/vf/recipe_resource/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeResource.Resolv do -@moduledoc "Resolvers of RecipeResources." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.RecipeResource.Domain def recipe_resource(params, _) do @@ -25,7 +26,10 @@ def recipe_resource(params, _) do end def recipe_resources(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_recipe_resource(%{recipe_resource: params}, _) do diff --git a/src/zenflows/vf/recipe_resource/type.ex b/src/zenflows/vf/recipe_resource/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RecipeResource.Type do -@moduledoc "GraphQL types of RecipeResources." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/resource_specification.ex b/src/zenflows/vf/resource_specification.ex @@ -24,8 +24,10 @@ classification when more information is needed, particularly for recipes. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.File -alias Zenflows.VF.{Unit, Validate} +alias Zenflows.VF.Unit @type t() :: %__MODULE__{ name: String.t(), @@ -54,14 +56,14 @@ end ]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) |> Validate.name(:name) |> Validate.note(:note) - |> Changeset.cast_assoc(:images, with: &File.chgset/2) + |> Changeset.cast_assoc(:images) |> Validate.class(:resource_classified_as) |> Changeset.assoc_constraint(:default_unit_of_resource) |> Changeset.assoc_constraint(:default_unit_of_effort) diff --git a/src/zenflows/vf/resource_specification/domain.ex b/src/zenflows/vf/resource_specification/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.ResourceSpecification.Domain do @moduledoc "Domain logic of ResourceSpecifications." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.ResourceSpecification -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, ResourceSpecification.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,63 +33,115 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(ResourceSpecification, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: ResourceSpecification.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value end -@spec create(repo(), params()) - :: {:ok, ResourceSpecification.t()} | {:error, chgset()} -def create(repo \\ Repo, params) do +@spec all(Page.t()) :: {:ok, [ResourceSpecification.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(ResourceSpecification, page)} +end + +@spec all!(Page.t()) :: [ResourceSpecification.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) + :: {:ok, ResourceSpecification.t()} | {:error, Changeset.t()} +def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, ResourceSpecification.chgset(params)) - |> repo.transaction() + |> multi_insert(params) + |> Repo.transaction() |> case do - {:ok, %{insert: rs}} -> {:ok, rs} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, ResourceSpecification.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: ResourceSpecification.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, ResourceSpecification.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &ResourceSpecification.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: rs}} -> {:ok, rs} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, ResourceSpecification.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: ResourceSpecification.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, ResourceSpecification.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: rs}} -> {:ok, rs} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: ResourceSpecification.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(ResourceSpecification.t(), :images | :default_unit_of_resource | :default_unit_of_effort) :: ResourceSpecification.t() -def preload(res_spec, :images) do - Repo.preload(res_spec, :images) +def preload(res_spec, x) when x in ~w[ + images default_unit_of_resource default_unit_of_effort +]a do + Repo.preload(res_spec, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :resource_specification + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, ResourceSpecification.changeset(params)) end -def preload(res_spec, :default_unit_of_resource) do - Repo.preload(res_spec, :default_unit_of_resource) +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &ResourceSpecification.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(res_spec, :default_unit_of_effort) do - Repo.preload(res_spec, :default_unit_of_effort) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/resource_specification/resolv.ex b/src/zenflows/vf/resource_specification/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ResourceSpecification.Resolv do -@moduledoc "Resolvers of ResourceSpecifications." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.ResourceSpecification.Domain def resource_specification(params, _) do @@ -25,7 +26,10 @@ def resource_specification(params, _) do end def resource_specifications(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_resource_specification(%{resource_specification: params}, _) do diff --git a/src/zenflows/vf/resource_specification/type.ex b/src/zenflows/vf/resource_specification/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ResourceSpecification.Type do -@moduledoc "GraphQL types of ResourceSpecifications." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/role_behavior.ex b/src/zenflows/vf/role_behavior.ex @@ -22,7 +22,8 @@ The general shape or behavior grouping of an agent relationship role. use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ name: String.t(), @@ -39,8 +40,8 @@ end @cast @reqr ++ [:note] @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/role_behavior/domain.ex b/src/zenflows/vf/role_behavior/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.RoleBehavior.Domain do @moduledoc "Domain logic of RoleBehaviors." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.RoleBehavior -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, RoleBehavior.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,46 +33,105 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(RoleBehavior, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: RoleBehavior.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [RoleBehavior.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(RoleBehavior, page)} +end + +@spec all!(Page.t()) :: [RoleBehavior.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, RoleBehavior.t()} | {:error, chgset()} +@spec create(Schema.params()) + :: {:ok, RoleBehavior.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, RoleBehavior.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: rb}} -> {:ok, rb} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, RoleBehavior.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: RoleBehavior.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, RoleBehavior.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &RoleBehavior.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: rb}} -> {:ok, rb} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, RoleBehavior.t()} | {:error, chgset()} +@spec update!(Schema.id(), Schema.params()) :: RoleBehavior.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, RoleBehavior.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: rb}} -> {:ok, rb} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id()) :: RoleBehavior.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :role_behavior + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, RoleBehavior.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &RoleBehavior.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/role_behavior/resolv.ex b/src/zenflows/vf/role_behavior/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RoleBehavior.Resolv do -@moduledoc "Resolvers of RoleBehaviors." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.RoleBehavior.Domain def role_behavior(params, _info) do @@ -25,7 +26,10 @@ def role_behavior(params, _info) do end def role_behaviors(params, _info) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_role_behavior(%{role_behavior: params}, _info) do diff --git a/src/zenflows/vf/role_behavior/type.ex b/src/zenflows/vf/role_behavior/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.RoleBehavior.Type do -@moduledoc "GraphQL types of RoleBehaviors." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/satisfaction.ex b/src/zenflows/vf/satisfaction.ex @@ -23,12 +23,13 @@ or events that partially or full satisfy one or more intents. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ EventOrCommitment, Intent, Measure, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -56,8 +57,8 @@ end @cast @reqr ++ ~w[resource_quantity effort_quantity note]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/scenario.ex b/src/zenflows/vf/scenario.ex @@ -23,10 +23,11 @@ used for budgeting, analysis, plan refinement, etc. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Scenario, ScenarioDefinition, - Validate, } @type t() :: %__MODULE__{ @@ -57,8 +58,8 @@ end ]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/scenario/domain.ex b/src/zenflows/vf/scenario/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.Scenario.Domain do @moduledoc "Domain logic of Scenarios." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.Scenario -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Scenario.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,55 +33,109 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Scenario, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Scenario.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Scenario.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(Scenario, page)} +end + +@spec all!(Page.t()) :: [Scenario.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, Scenario.t()} | {:error, chgset()} +@spec create(Schema.params()) :: {:ok, Scenario.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Scenario.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: s}} -> {:ok, s} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, Scenario.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Scenario.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Scenario.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Scenario.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: s}} -> {:ok, s} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Scenario.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Scenario.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, Scenario.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: s}} -> {:ok, s} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: Scenario.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(Scenario.t(), :defined_as | :refinement_of) :: Scenario.t() -def preload(scen, :defined_as) do - Repo.preload(scen, :defined_as) +def preload(scen, x) when x in ~w[defined_as refinement_of]a do + Repo.preload(scen, x) +end + +@spec multi_key() :: atom() +def multi_key(), do: :scenario + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Scenario.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Scenario.changeset(Map.fetch!(&1, "#{key}.one"), params)) end -def preload(scen, :refinement_of) do - Repo.preload(scen, :refinement_of) +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) end end diff --git a/src/zenflows/vf/scenario/resolv.ex b/src/zenflows/vf/scenario/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Scenario.Resolv do -@moduledoc "Resolvers of Scenario." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.Scenario.Domain def scenario(params, _) do @@ -27,7 +28,10 @@ def scenario(params, _) do end def scenarios(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_scenario(%{scenario: params}, _) do diff --git a/src/zenflows/vf/scenario/type.ex b/src/zenflows/vf/scenario/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Scenario.Type do -@moduledoc "GraphQL types of Scenarios." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/scenario_definition.ex b/src/zenflows/vf/scenario_definition.ex @@ -22,10 +22,11 @@ The type definition of one or more scenarios, such as Yearly Budget. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Duration, TimeUnitEnum, - Validate, } @type t() :: %__MODULE__{ @@ -49,8 +50,8 @@ end @cast @reqr ++ ~w[note has_duration]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do Changeset.cast(schema, params, @cast) |> Changeset.validate_required(@reqr) |> Validate.name(:name) diff --git a/src/zenflows/vf/scenario_definition/domain.ex b/src/zenflows/vf/scenario_definition/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.ScenarioDefinition.Domain do @moduledoc "Domain logic of ScenarioDefinitions." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.{Duration, ScenarioDefinition} -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, ScenarioDefinition.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,52 +33,111 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(ScenarioDefinition, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) + :: ScenarioDefinition.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [ScenarioDefinition.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(ScenarioDefinition, page)} +end + +@spec all!(Page.t()) :: [ScenarioDefinition.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, ScenarioDefinition.t()} | {:error, chgset()} +@spec create(Schema.params()) + :: {:ok, ScenarioDefinition.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, ScenarioDefinition.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: sd}} -> {:ok, sd} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, ScenarioDefinition.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: ScenarioDefinition.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, ScenarioDefinition.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &ScenarioDefinition.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: sd}} -> {:ok, sd} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) - :: {:ok, ScenarioDefinition.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: ScenarioDefinition.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, ScenarioDefinition.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: sd}} -> {:ok, sd} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end +@spec delete!(Schema.id()) :: ScenarioDefinition.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + @spec preload(ScenarioDefinition.t(), :has_duration) :: ScenarioDefinition.t() def preload(scen_def, :has_duration) do Duration.preload(scen_def, :has_duration) end + +@spec multi_key() :: atom() +def multi_key(), do: :scenario_definition + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, ScenarioDefinition.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &ScenarioDefinition.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/scenario_definition/resolv.ex b/src/zenflows/vf/scenario_definition/resolv.ex @@ -16,10 +16,11 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ScenarioDefinition.Resolv do -@moduledoc "Resolvers of ScenarioDefinition." +@moduledoc false use Absinthe.Schema.Notation +alias Zenflows.GQL.Connection alias Zenflows.VF.ScenarioDefinition.Domain def scenario_definition(params, _) do @@ -27,7 +28,10 @@ def scenario_definition(params, _) do end def scenario_definitions(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_scenario_definition(%{scenario_definition: params}, _) do diff --git a/src/zenflows/vf/scenario_definition/type.ex b/src/zenflows/vf/scenario_definition/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.ScenarioDefinition.Type do -@moduledoc "GraphQL types of ScenarioDefinitiones." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/settlement.ex b/src/zenflows/vf/settlement.ex @@ -23,12 +23,13 @@ that fully or partially settle one or more claims. use Zenflows.DB.Schema +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} alias Zenflows.VF.{ Claim, EconomicEvent, Measure, Unit, - Validate, } @type t() :: %__MODULE__{ @@ -56,8 +57,8 @@ end @cast @reqr ++ ~w[resource_quantity effort_quantity note]a @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/spatial_thing.ex b/src/zenflows/vf/spatial_thing.ex @@ -22,7 +22,8 @@ A physical mappable location. use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ id: String.t(), @@ -48,8 +49,8 @@ schema "vf_spatial_thing" do end @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/spatial_thing/domain.ex b/src/zenflows/vf/spatial_thing/domain.ex @@ -19,16 +19,11 @@ defmodule Zenflows.VF.SpatialThing.Domain do @moduledoc "Domain logic of SpatialThings." # Basically, a fancy name for (geo)location. :P -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.SpatialThing -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, SpatialThing.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -39,46 +34,105 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(SpatialThing, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: SpatialThing.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [SpatialThing.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(SpatialThing, page)} +end + +@spec all!(Page.t()) :: [SpatialThing.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value end -@spec create(params()) :: {:ok, SpatialThing.t()} | {:error, chgset()} +@spec create(Schema.params()) + :: {:ok, SpatialThing.t()} | {:error, Changeset.t()} def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, SpatialThing.chgset(params)) + |> multi_insert(params) |> Repo.transaction() |> case do - {:ok, %{insert: st}} -> {:ok, st} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, SpatialThing.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: SpatialThing.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, SpatialThing.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &SpatialThing.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: st}} -> {:ok, st} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, SpatialThing.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: SpatialThing.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) + :: {:ok, SpatialThing.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: st}} -> {:ok, st} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id()) :: SpatialThing.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :spatial_thing + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, SpatialThing.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &SpatialThing.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/spatial_thing/resolv.ex b/src/zenflows/vf/spatial_thing/resolv.ex @@ -16,9 +16,10 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.SpatialThing.Resolv do -@moduledoc "Resolvers of SpatialThings." +@moduledoc false # Basically, a fancy name for (geo)location. :P +alias Zenflows.GQL.Connection alias Zenflows.VF.SpatialThing.Domain def spatial_thing(params, _) do @@ -26,7 +27,10 @@ def spatial_thing(params, _) do end def spatial_things(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_spatial_thing(%{spatial_thing: params}, _) do diff --git a/src/zenflows/vf/spatial_thing/type.ex b/src/zenflows/vf/spatial_thing/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.SpatialThing.Type do -@moduledoc "GraphQL types of SpatialThings." +@moduledoc false # Basically, a fancy name for (geo)location. :P use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/time_unit/type.ex b/src/zenflows/vf/time_unit/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.TimeUnit.Type do -@moduledoc "GraphQL types of TimeUnits." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/unit.ex b/src/zenflows/vf/unit.ex @@ -23,7 +23,8 @@ From OM2 vocabulary. use Zenflows.DB.Schema -alias Zenflows.VF.Validate +alias Ecto.Changeset +alias Zenflows.DB.{Schema, Validate} @type t() :: %__MODULE__{ id: String.t(), @@ -41,8 +42,8 @@ end @cast @reqr @doc false -@spec chgset(Schema.t(), params()) :: Changeset.t() -def chgset(schema \\ %__MODULE__{}, params) do +@spec changeset(Schema.t(), Schema.params()) :: Changeset.t() +def changeset(schema \\ %__MODULE__{}, params) do schema |> Changeset.cast(params, @cast) |> Changeset.validate_required(@reqr) diff --git a/src/zenflows/vf/unit/domain.ex b/src/zenflows/vf/unit/domain.ex @@ -18,16 +18,11 @@ defmodule Zenflows.VF.Unit.Domain do @moduledoc "Domain logic of Units." -alias Ecto.Multi -alias Zenflows.DB.{Paging, Repo} +alias Ecto.{Changeset, Multi} +alias Zenflows.DB.{Page, Repo, Schema} alias Zenflows.VF.Unit -@typep repo() :: Ecto.Repo.t() -@typep chgset() :: Ecto.Changeset.t() -@typep id() :: Zenflows.DB.Schema.id() -@typep params() :: Zenflows.DB.Schema.params() - -@spec one(repo(), id() | map() | Keyword.t()) +@spec one(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: {:ok, Unit.t()} | {:error, String.t()} def one(repo \\ Repo, _) def one(repo, id) when is_binary(id), do: one(repo, id: id) @@ -38,47 +33,103 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Unit, params) +@spec one!(Ecto.Repo.t(), Schema.id() | map() | Keyword.t()) :: Unit.t() +def one!(repo \\ Repo, id_or_clauses) do + {:ok, value} = one(repo, id_or_clauses) + value +end + +@spec all(Page.t()) :: {:ok, [Unit.t()]} | {:error, Changeset.t()} +def all(page \\ Page.new()) do + {:ok, Page.all(Unit, page)} end -# `repo` is needed since we use that in a migration script. -@spec create(repo(), params()) :: {:ok, Unit.t()} | {:error, chgset()} -def create(repo \\ Repo, params) do +@spec all!(Page.t()) :: [Unit.t()] +def all!(page \\ Page.new()) do + {:ok, value} = all(page) + value +end + +@spec create(Schema.params()) :: {:ok, Unit.t()} | {:error, Changeset.t()} +def create(params) do + key = multi_key() Multi.new() - |> Multi.insert(:insert, Unit.chgset(params)) - |> repo.transaction() + |> multi_insert(params) + |> Repo.transaction() |> case do - {:ok, %{insert: u}} -> {:ok, u} - {:error, _, cset, _} -> {:error, cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec update(id(), params()) - :: {:ok, Unit.t()} | {:error, String.t() | chgset()} +@spec create!(Schema.params()) :: Unit.t() +def create!(params) do + {:ok, value} = create(params) + value +end + +@spec update(Schema.id(), Schema.params()) + :: {:ok, Unit.t()} | {:error, String.t() | Changeset.t()} def update(id, params) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.update(:update, &Unit.chgset(&1.one, params)) + |> multi_update(id, params) |> Repo.transaction() |> case do - {:ok, %{update: u}} -> {:ok, u} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end -@spec delete(id()) :: {:ok, Unit.t()} | {:error, String.t() | chgset()} +@spec update!(Schema.id(), Schema.params()) :: Unit.t() +def update!(id, params) do + {:ok, value} = update(id, params) + value +end + +@spec delete(Schema.id()) :: {:ok, Unit.t()} | {:error, String.t() | Changeset.t()} def delete(id) do + key = multi_key() Multi.new() - |> Multi.put(:id, id) - |> Multi.run(:one, &one/2) - |> Multi.delete(:delete, & &1.one) + |> multi_delete(id) |> Repo.transaction() |> case do - {:ok, %{delete: u}} -> {:ok, u} - {:error, _, msg_or_cset, _} -> {:error, msg_or_cset} + {:ok, %{^key => value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} end end + +@spec delete!(Schema.id()) :: Unit.t() +def delete!(id) do + {:ok, value} = delete(id) + value +end + +@spec multi_key() :: atom() +def multi_key(), do: :unit + +@spec multi_one(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_one(m, key \\ multi_key(), id) do + Multi.run(m, key, fn repo, _ -> one(repo, id) end) +end + +@spec multi_insert(Multi.t(), term(), Schema.params()) :: Multi.t() +def multi_insert(m, key \\ multi_key(), params) do + Multi.insert(m, key, Unit.changeset(params)) +end + +@spec multi_update(Multi.t(), term(), Schema.id(), Schema.params()) :: Multi.t() +def multi_update(m, key \\ multi_key(), id, params) do + m + |> multi_one("#{key}.one", id) + |> Multi.update(key, + &Unit.changeset(Map.fetch!(&1, "#{key}.one"), params)) +end + +@spec multi_delete(Multi.t(), term(), Schema.id()) :: Multi.t() +def multi_delete(m, key \\ multi_key(), id) do + m + |> multi_one("#{key}.one", id) + |> Multi.delete(key, &Map.fetch!(&1, "#{key}.one")) +end end diff --git a/src/zenflows/vf/unit/resolv.ex b/src/zenflows/vf/unit/resolv.ex @@ -16,8 +16,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Unit.Resolv do -@moduledoc "Resolvers of Units." +@moduledoc false +alias Zenflows.GQL.Connection alias Zenflows.VF.Unit.Domain def unit(params, _) do @@ -25,7 +26,10 @@ def unit(params, _) do end def units(params, _) do - Domain.all(params) + with {:ok, page} <- Connection.parse(params), + {:ok, schemas} <- Domain.all(page) do + {:ok, Connection.from_list(schemas, page)} + end end def create_unit(%{unit: params}, _) do diff --git a/src/zenflows/vf/unit/type.ex b/src/zenflows/vf/unit/type.ex @@ -16,7 +16,7 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. defmodule Zenflows.VF.Unit.Type do -@moduledoc "GraphQL types of Units." +@moduledoc false use Absinthe.Schema.Notation diff --git a/src/zenflows/vf/validate.ex b/src/zenflows/vf/validate.ex @@ -1,146 +0,0 @@ -# Zenflows is designed to implement the Valueflows vocabulary, -# written and maintained by srfsh <info@dyne.org>. -# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. - -defmodule Zenflows.VF.Validate do -@moduledoc """ -Common Valueflows validators for Ecto.Changesets. All the limitations -here are rough and can be changed in the future. -""" - -alias Ecto.Changeset, as: Chset - -require Logger - -@doc "Checks if the given string field is [16, 2048] bytes long." -@spec key(Chset.t(), atom()) :: Chset.t() -def key(cset, field) do - Chset.validate_change(cset, field, :valflow, fn - _, str when byte_size(str) < 16 -> - [{field, "should be at least 16 bytes long"}] - _, str when byte_size(str) > 2048 -> - [{field, "should be at most 2048 bytes long"}] - _, _ -> - [] - end) -end - -@doc "Checks if the given string field is [1, 256] bytes long." -@spec name(Chset.t(), atom()) :: Chset.t() -def name(cset, field) do - Chset.validate_change(cset, field, :valflow, fn - _, str when byte_size(str) < 1 -> - [{field, "should be at least 1 byte long"}] - _, str when byte_size(str) > 256 -> - [{field, "should be at most 256 bytes long"}] - _, _ -> - [] - end) -end - -@doc "Checks if the given string field is [1, 2048] bytes long." -@spec note(Chset.t(), atom()) :: Chset.t() -def note(cset, field) do - Chset.validate_change(cset, field, :valflow, fn - _, str when byte_size(str) < 1 -> - [{field, "should be at least 1 bytes long"}] - _, str when byte_size(str) > 2048 -> - [{field, "should be at most 2048 bytes long"}] - _, _ -> - [] - end) -end - -@doc "Checks if the given string is [1, 512] bytes long." -@spec uri(Chset.t(), atom()) :: Chset.t() -def uri(cset, field) do - Chset.validate_change(cset, field, :valflow, fn - _, str when byte_size(str) < 1 -> - [{field, "should be at least 1 bytes long"}] - _, str when byte_size(str) > 512 -> - [{field, "should be at most 512 bytes long"}] - _, _ -> - [] - end) -end - -@mebibyte 1024 * 1024 - -@doc """ -Check if the given base64-encoded binary data is at least 1B, at most -25MiB in size. And, display a warning if it is longer than 4MiB. -""" -@spec img(Chset.t(), atom()) :: Chset.t() -def img(cset, field) do - Chset.validate_change(cset, field, :valflow, fn - _, str when byte_size(str) < 1 -> - [{field, "should be at least 1B long"}] - _, str when byte_size(str) > 25 * @mebibyte -> - [{field, "should be at most 25MiB long"}] - _, str when byte_size(str) > 4 * @mebibyte -> - Logger.warning("file exceeds 4MiB") - [] - _, _ -> - [] - end) -end - -@doc """ -Checks if the given classifications (list of strings) for: - - - Each item in the list is [1, 512] bytes long; - - The list can contain only [1, 128] items. -""" -@spec class(Chset.t(), atom()) :: Chset.t() -def class(cset, field) do - Chset.validate_change(cset, field, :valflow, fn - _, [] -> - [{field, "must contain at least 1 item"}] - _, list -> - case do_class(list) do - {:exceeds, _ind} -> [{field, "must contain at most 128 items"}] - {:short, ind} -> [{field, "the item at #{ind + 1} cannot be shorter than 1 bytes"}] - {:long, ind} -> [{field, "the item at #{ind + 1} cannot be longer than 512 bytes"}] - {:valid, _ind} -> [] - end - end) -end - -@spec do_class([String.t()]) :: {atom(), integer()} -defp do_class(list) do - do_class(list, 0, 128) -end - -# The rationale of this function is to loop over the list while decreasing -# `remaining' and increasing `index' until either one of these happen (in -# that order): -# * remaining hits 0 -# * one of the items in the list is shorter than 3 bytes long -# * one of the items in the list is longer than 512 bytes long -@spec do_class([String.t()], integer(), integer()) :: {atom(), integer()} -defp do_class([head | tail], index, remaining) do - cond do - remaining == 0 -> {:exceeds, index} - byte_size(head) < 1 -> {:short, index} - byte_size(head) > 512 -> {:long, index} - true -> do_class(tail, index + 1, remaining - 1) - end -end - -defp do_class([], index, _) do - {:valid, index - 1} -end -end diff --git a/test/db/page.test.exs b/test/db/page.test.exs @@ -0,0 +1,298 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule ZenflowsTest.DB.Page do +use ZenflowsTest.Help.EctoCase, async: true + +# What we are testing here is a bit interesting. Because, you see, +# what we actually care about is dependant on the number of records we +# ask for (referred to by "num" from now on). This is because of we +# always try to fetch num+1 records. This basically means that we'll +# have a table of possible cases: +# +# num | len(edges) +# ----+----------- +# 0 | 0 +# 0 | 1 +# ----+----------- +# 1 | 0 +# 1 | 1 +# 1 | 2 +# ----+----------- +# 2 | 0 +# 2 | 1 +# 2 | 2 +# 2 | 3 +# ----+----------- +# 3 | 0 +# 3 | 1 +# 3 | 2 +# 3 | 3 +# 3 | 4 +# ----+----------- +# 4 | 0 +# 4 | 1 +# 4 | 2 +# 4 | 3 +# 4 | 4 +# 4 | 5 + +# Here, we cover the cases of: +# num | len(edges) +# ----+----------- +# 0 | 0 +# 1 | 0 +# 2 | 0 +# 3 | 0 +# 4 | 0 +test "num>=0 && len(edges)==0:" do + Enum.each(0..10, fn n -> + assert %{data: %{"people" => data}} = + run!(""" + query ($n: Int!) { + people (first: $n) {...people} + } + """, vars: %{"n" => n}) + + assert [] = Map.fetch!(data, "edges") + + assert %{ + "startCursor" => nil, + "endCursor" => nil, + "hasPreviousPage" => false, + "hasNextPage" => false, + "totalCount" => 0, + "pageLimit" => ^n, + } = Map.fetch!(data, "pageInfo") + end) +end + +# Here, we cover the cases of: +# num | len(edges) +# ----+----------- +# 1 | 1 +# 2 | 2 +# 3 | 3 +# 4 | 4 +test "num>=1 && len(edges)==num:" do + Enum.reduce(1..10, [], fn n, pers -> + last = %{id: last_cur} = Factory.insert!(:person) + pers = pers ++ [last] + [%{id: first_cur} | _] = pers + + assert %{data: %{"people" => data}} = + run!(""" + query ($n: Int!) { + people (first: $n) {...people} + } + """, vars: %{"n" => n}) + + edges = Map.fetch!(data, "edges") + assert length(edges) == n + + assert %{ + "startCursor" => ^first_cur, + "endCursor" => ^last_cur, + "hasPreviousPage" => false, + "hasNextPage" => false, + "totalCount" => ^n, + "pageLimit" => ^n, + } = Map.fetch!(data, "pageInfo") + + pers + end) +end + +# Here, we cover the cases of: +# num | len(edges) +# ----+----------- +# 0 | 1 +# 1 | 2 +# 2 | 3 +# 3 | 4 +# 4 | 5 +test "num>=0 && len(edges)==num+1:" do + Enum.reduce(0..10, [], fn n, pers -> + pers = pers ++ [Factory.insert!(:person)] + {tmp, _} = Enum.split(pers, n) + first = List.first(tmp) + last = List.last(tmp) + first_cur = if first != nil, do: first.id, else: nil + last_cur = if last != nil, do: last.id, else: nil + + assert %{data: %{"people" => data}} = + run!(""" + query ($n: Int!) { + people (first: $n) {...people} + } + """, vars: %{"n" => n}) + + edges = Map.fetch!(data, "edges") + assert length(edges) == n + + assert %{ + "startCursor" => ^first_cur, + "endCursor" => ^last_cur, + "hasPreviousPage" => false, + "hasNextPage" => true, + "totalCount" => ^n, + "pageLimit" => ^n, + } = Map.fetch!(data, "pageInfo") + + pers + end) +end + +# Here, we cover the last case, which prooves we cover all the cases +# (this is so because of the fact that we only deal with len(edges)<num +# cases, where num>=1): +# num | len(edges) +# ----+----------- +# 2 | 1 +# ----+----------- +# 3 | 1 +# 3 | 2 +# ----+----------- +# 4 | 1 +# 4 | 2 +# 4 | 3 +test "num>=2 && len(edges)>=0 && len(edges)<num:" do + Enum.reduce(1..9, [], fn e, pers -> + pers = pers ++ [Factory.insert!(:person)] + + Enum.each(2..10, fn n -> + if e < n do + assert %{data: %{"people" => data}} = + run!(""" + query ($n: Int!) { + people (first: $n) {...people} + } + """, vars: %{"n" => n}) + + edges = Map.fetch!(data, "edges") + assert length(edges) == e + + %{id: first_cur} = List.first(pers) + %{id: last_cur} = List.last(pers) + + assert %{ + "startCursor" => ^first_cur, + "endCursor" => ^last_cur, + "hasPreviousPage" => false, + "hasNextPage" => false, + "totalCount" => ^e, + "pageLimit" => ^n, + } = Map.fetch!(data, "pageInfo") + end + end) + + pers + end) +end + +# We're dealing with cursors here now. Most of the cases are the +# same as the ones without the cursors, so we omit them. + +# Here, we cover the cases of: +# num | len(edges) +# ----+----------- +# 1 | 1 +# 2 | 2 +# 3 | 3 +# 4 | 4 +test "with cursor: num>=1 && len(edges)==num:" do + Enum.each(1..10, fn n -> + p = Factory.insert!(:person) + + assert %{data: %{"people" => data}} = + run!(""" + query ($cur: ID! $n: Int!) { + people (after: $cur first: $n) {...people} + } + """, vars: %{"n" => n, "cur" => p.id}) + + assert [] = Map.fetch!(data, "edges") + + assert %{ + "startCursor" => nil, + "endCursor" => nil, + "hasPreviousPage" => true, # spec says so if we can't determine + "hasNextPage" => false, + "totalCount" => 0, + "pageLimit" => ^n, + } = Map.fetch!(data, "pageInfo") + end) +end + +# Here, we cover the cases of: +# num | len(edges) +# ----+----------- +# 1 | 2 +# 2 | 3 +# 3 | 4 +# 4 | 5 +test "with cursor: num>=1 && len(edges)==num+1:" do + pers = [Factory.insert!(:person)] + Enum.reduce(1..10, pers, fn n, pers -> + %{id: after_cur} = List.last(pers) + last = %{id: last_cur} = Factory.insert!(:person) + pers = pers ++ [last] + + assert %{data: %{"people" => data}} = + run!(""" + query ($cur: ID! $n: Int!) { + people (after: $cur first: $n) {...people} + } + """, vars: %{"n" => n, "cur" => after_cur}) + + assert [_] = Map.fetch!(data, "edges") + + assert %{ + "startCursor" => ^last_cur, + "endCursor" => ^last_cur, + "hasPreviousPage" => true, # spec + "hasNextPage" => false, + "totalCount" => 1, + "pageLimit" => ^n, + } = Map.fetch!(data, "pageInfo") + + pers + end) +end + +@spec run!(String.t(), Keyword.t()) :: Absinthe.run_result() +def run!(doc, opts \\ []) do + """ + #{doc} + fragment people on PersonConnection { + pageInfo { + startCursor + endCursor + hasPreviousPage + hasNextPage + totalCount + pageLimit + } + edges { + cursor + node {id} + } + } + """ + |> ZenflowsTest.Help.AbsinCase.run!(opts) +end +end diff --git a/test/db/paging.test.exs b/test/db/paging.test.exs @@ -1,298 +0,0 @@ -# Zenflows is designed to implement the Valueflows vocabulary, -# written and maintained by srfsh <info@dyne.org>. -# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. - -defmodule ZenflowsTest.DB.Paging do -use ZenflowsTest.Help.EctoCase, async: true - -# What we are testing here is a bit interesting. Because, you see, -# what we actually care about is dependant on the number of records we -# ask for (referred to by "num" from now on). This is because of we -# always try to fetch num+1 records. This basically means that we'll -# have a table of possible cases: -# -# num | len(edges) -# ----+----------- -# 0 | 0 -# 0 | 1 -# ----+----------- -# 1 | 0 -# 1 | 1 -# 1 | 2 -# ----+----------- -# 2 | 0 -# 2 | 1 -# 2 | 2 -# 2 | 3 -# ----+----------- -# 3 | 0 -# 3 | 1 -# 3 | 2 -# 3 | 3 -# 3 | 4 -# ----+----------- -# 4 | 0 -# 4 | 1 -# 4 | 2 -# 4 | 3 -# 4 | 4 -# 4 | 5 - -# Here, we cover the cases of: -# num | len(edges) -# ----+----------- -# 0 | 0 -# 1 | 0 -# 2 | 0 -# 3 | 0 -# 4 | 0 -test "num>=0 && len(edges)==0:" do - Enum.each(0..10, fn n -> - assert %{data: %{"people" => data}} = - run!(""" - query ($n: Int!) { - people (first: $n) {...people} - } - """, vars: %{"n" => n}) - - assert [] = Map.fetch!(data, "edges") - - assert %{ - "startCursor" => nil, - "endCursor" => nil, - "hasPreviousPage" => false, - "hasNextPage" => false, - "totalCount" => 0, - "pageLimit" => ^n, - } = Map.fetch!(data, "pageInfo") - end) -end - -# Here, we cover the cases of: -# num | len(edges) -# ----+----------- -# 1 | 1 -# 2 | 2 -# 3 | 3 -# 4 | 4 -test "num>=1 && len(edges)==num:" do - Enum.reduce(1..10, [], fn n, pers -> - last = %{id: last_cur} = Factory.insert!(:person) - pers = pers ++ [last] - [%{id: first_cur} | _] = pers - - assert %{data: %{"people" => data}} = - run!(""" - query ($n: Int!) { - people (first: $n) {...people} - } - """, vars: %{"n" => n}) - - edges = Map.fetch!(data, "edges") - assert length(edges) == n - - assert %{ - "startCursor" => ^first_cur, - "endCursor" => ^last_cur, - "hasPreviousPage" => false, - "hasNextPage" => false, - "totalCount" => ^n, - "pageLimit" => ^n, - } = Map.fetch!(data, "pageInfo") - - pers - end) -end - -# Here, we cover the cases of: -# num | len(edges) -# ----+----------- -# 0 | 1 -# 1 | 2 -# 2 | 3 -# 3 | 4 -# 4 | 5 -test "num>=0 && len(edges)==num+1:" do - Enum.reduce(0..10, [], fn n, pers -> - pers = pers ++ [Factory.insert!(:person)] - {tmp, _} = Enum.split(pers, n) - first = List.first(tmp) - last = List.last(tmp) - first_cur = if first != nil, do: first.id, else: nil - last_cur = if last != nil, do: last.id, else: nil - - assert %{data: %{"people" => data}} = - run!(""" - query ($n: Int!) { - people (first: $n) {...people} - } - """, vars: %{"n" => n}) - - edges = Map.fetch!(data, "edges") - assert length(edges) == n - - assert %{ - "startCursor" => ^first_cur, - "endCursor" => ^last_cur, - "hasPreviousPage" => false, - "hasNextPage" => true, - "totalCount" => ^n, - "pageLimit" => ^n, - } = Map.fetch!(data, "pageInfo") - - pers - end) -end - -# Here, we cover the last case, which prooves we cover all the cases -# (this is so because of the fact that we only deal with len(edges)<num -# cases, where num>=1): -# num | len(edges) -# ----+----------- -# 2 | 1 -# ----+----------- -# 3 | 1 -# 3 | 2 -# ----+----------- -# 4 | 1 -# 4 | 2 -# 4 | 3 -test "num>=2 && len(edges)>=0 && len(edges)<num:" do - Enum.reduce(1..9, [], fn e, pers -> - pers = pers ++ [Factory.insert!(:person)] - - Enum.each(2..10, fn n -> - if e < n do - assert %{data: %{"people" => data}} = - run!(""" - query ($n: Int!) { - people (first: $n) {...people} - } - """, vars: %{"n" => n}) - - edges = Map.fetch!(data, "edges") - assert length(edges) == e - - %{id: first_cur} = List.first(pers) - %{id: last_cur} = List.last(pers) - - assert %{ - "startCursor" => ^first_cur, - "endCursor" => ^last_cur, - "hasPreviousPage" => false, - "hasNextPage" => false, - "totalCount" => ^e, - "pageLimit" => ^n, - } = Map.fetch!(data, "pageInfo") - end - end) - - pers - end) -end - -# We're dealing with cursors here now. Most of the cases are the -# same as the ones without the cursors, so we omit them. - -# Here, we cover the cases of: -# num | len(edges) -# ----+----------- -# 1 | 1 -# 2 | 2 -# 3 | 3 -# 4 | 4 -test "with cursor: num>=1 && len(edges)==num:" do - Enum.each(1..10, fn n -> - p = Factory.insert!(:person) - - assert %{data: %{"people" => data}} = - run!(""" - query ($cur: ID! $n: Int!) { - people (after: $cur first: $n) {...people} - } - """, vars: %{"n" => n, "cur" => p.id}) - - assert [] = Map.fetch!(data, "edges") - - assert %{ - "startCursor" => nil, - "endCursor" => nil, - "hasPreviousPage" => true, # spec says so if we can't determine - "hasNextPage" => false, - "totalCount" => 0, - "pageLimit" => ^n, - } = Map.fetch!(data, "pageInfo") - end) -end - -# Here, we cover the cases of: -# num | len(edges) -# ----+----------- -# 1 | 2 -# 2 | 3 -# 3 | 4 -# 4 | 5 -test "with cursor: num>=1 && len(edges)==num+1:" do - pers = [Factory.insert!(:person)] - Enum.reduce(1..10, pers, fn n, pers -> - %{id: after_cur} = List.last(pers) - last = %{id: last_cur} = Factory.insert!(:person) - pers = pers ++ [last] - - assert %{data: %{"people" => data}} = - run!(""" - query ($cur: ID! $n: Int!) { - people (after: $cur first: $n) {...people} - } - """, vars: %{"n" => n, "cur" => after_cur}) - - assert [_] = Map.fetch!(data, "edges") - - assert %{ - "startCursor" => ^last_cur, - "endCursor" => ^last_cur, - "hasPreviousPage" => true, # spec - "hasNextPage" => false, - "totalCount" => 1, - "pageLimit" => ^n, - } = Map.fetch!(data, "pageInfo") - - pers - end) -end - -@spec run!(String.t(), Keyword.t()) :: Absinthe.run_result() -def run!(doc, opts \\ []) do - """ - #{doc} - fragment people on PersonConnection { - pageInfo { - startCursor - endCursor - hasPreviousPage - hasNextPage - totalCount - pageLimit - } - edges { - cursor - node {id} - } - } - """ - |> ZenflowsTest.Help.AbsinCase.run!(opts) -end -end diff --git a/test/file.test.exs b/test/file.test.exs @@ -67,7 +67,7 @@ test "works on EconomicResource" do agent = Factory.insert!(:agent) unit = Factory.insert!(:unit) spec = Factory.insert!(:resource_specification) - {:ok, %EconomicEvent{}, %EconomicResource{} = res, nil} = + {:ok, %EconomicEvent{} = evt} = EconomicEvent.Domain.create( %{ action_id: "raise", @@ -108,6 +108,9 @@ test "works on EconomicResource" do ], }) + evt = EconomicEvent.Domain.preload(evt, :resource_inventoried_as) + res = EconomicResource.Domain.preload(evt.resource_inventoried_as, :images) + [%File{}, %File{}] = res.images end @@ -248,7 +251,7 @@ end test "doesn't work without a belongs_to field" do {:error, %Changeset{errors: errs}} = - File.chgset(%File{}, %{ + File.changeset(%File{}, %{ hash: "asnotehusnatoheusntaoehusntaeohu", name: "satoehusnoaethu", description: "foobaour", diff --git a/test/vf/action.test.exs b/test/vf/action.test.exs @@ -32,13 +32,13 @@ embedded_schema do field :action, :map, virtual: true end -def chgset(params) do +def changeset(params) do %__MODULE__{} |> common(params) |> Map.put(:action, :insert) end -def chgset(schema, params) do +def changeset(schema, params) do schema |> common(params) |> Map.put(:action, :update) @@ -56,20 +56,20 @@ end test "insert" do # doesn't work with invalid ids assert %Changeset{valid?: false, changes: %{}, errors: errs} - = Dummy.chgset(%{action_id: "doesn't exists"}) + = Dummy.changeset(%{action_id: "doesn't exists"}) assert Keyword.has_key?(errs, :action_id) # works with all valid ids Enum.each(Action.ID.values(), fn x -> assert %Changeset{valid?: true, changes: %{action_id: ^x}, errors: []} - = Dummy.chgset(%{action_id: x}) + = Dummy.changeset(%{action_id: x}) end) end test "update", %{inserted: schema} do # doesn't work with invalid ids assert %Changeset{valid?: false, changes: %{}, errors: errs} - = Dummy.chgset(schema, %{action_id: "doesn't exists"}) + = Dummy.changeset(schema, %{action_id: "doesn't exists"}) assert Keyword.has_key?(errs, :action_id) # because if the values are the same, there won't be any change @@ -77,7 +77,7 @@ test "update", %{inserted: schema} do # works with all valid ids Enum.each(all, fn x -> assert %Changeset{valid?: true, changes: %{action_id: ^x}, errors: []} - = Dummy.chgset(schema, %{action_id: x}) + = Dummy.changeset(schema, %{action_id: x}) end) end diff --git a/test/vf/appreciation.test.exs b/test/vf/appreciation.test.exs @@ -32,7 +32,7 @@ end test "create Appreciation", %{params: params} do assert {:ok, %Appreciation{} = appr} = params - |> Appreciation.chgset() + |> Appreciation.changeset() |> Repo.insert() assert appr.appreciation_of_id == params.appreciation_of_id @@ -45,7 +45,7 @@ test "update Appreciation", %{params: params} do assert {:ok, %Appreciation{} = appr} = :appreciation |> Factory.insert!() - |> Appreciation.chgset(params) + |> Appreciation.changeset(params) |> Repo.update() assert appr.appreciation_of_id == params.appreciation_of_id diff --git a/test/vf/claim.test.exs b/test/vf/claim.test.exs @@ -48,7 +48,7 @@ end test "create Claim", %{params: params} do assert {:ok, %Claim{} = claim} = params - |> Claim.chgset() + |> Claim.changeset() |> Repo.insert() assert claim.action_id == params.action_id @@ -73,7 +73,7 @@ test "update Appreciation", %{params: params} do assert {:ok, %Claim{} = claim} = :claim |> Factory.insert!() - |> Claim.chgset(params) + |> Claim.changeset(params) |> Repo.update() assert claim.action_id == params.action_id diff --git a/test/vf/commitment.test.exs b/test/vf/commitment.test.exs @@ -37,7 +37,12 @@ setup do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - due: DateTime.utc_now(), + resource_inventoried_as_id: Factory.insert!(:economic_resource).id, + resource_conforms_to_id: Factory.insert!(:resource_specification).id, + has_point_in_time: Factory.now(), + has_beginning: Factory.now(), + has_end: Factory.now(), + due: Factory.now(), finished: Factory.bool(), note: Factory.str("note"), # in_scope_of_id: @@ -51,12 +56,12 @@ end describe "create Commitment" do test "with both :has_point_in_time and :has_beginning", %{params: params} do params = params - |> Map.put(:has_point_in_time, DateTime.utc_now()) - |> Map.put(:has_beginning, DateTime.utc_now()) + |> Map.delete(:resource_inventoried_as_id) + |> Map.delete(:has_end) assert {:error, %Changeset{errors: errs}} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert {:ok, _} = Keyword.fetch(errs, :has_point_in_time) @@ -65,12 +70,12 @@ describe "create Commitment" do test "with both :has_point_in_time and :has_end", %{params: params} do params = params - |> Map.put(:has_point_in_time, DateTime.utc_now()) - |> Map.put(:has_end, DateTime.utc_now()) + |> Map.delete(:resource_inventoried_as_id) + |> Map.delete(:has_beginning) assert {:error, %Changeset{errors: errs}} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert {:ok, _} = Keyword.fetch(errs, :has_point_in_time) @@ -78,11 +83,14 @@ describe "create Commitment" do end test "with only :has_point_in_time", %{params: params} do - params = Map.put(params, :has_point_in_time, DateTime.utc_now()) + params = params + |> Map.delete(:resource_inventoried_as_id) + |> Map.delete(:has_beginning) + |> Map.delete(:has_end) assert {:ok, %Commitment{} = comm} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert comm.action_id == params.action_id @@ -91,7 +99,7 @@ describe "create Commitment" do assert comm.input_of_id == params.input_of_id assert comm.output_of_id == params.output_of_id assert comm.resource_classified_as == params.resource_classified_as - assert comm.resource_conforms_to_id == nil + assert comm.resource_conforms_to_id == params.resource_conforms_to_id assert comm.resource_inventoried_as_id == nil assert comm.resource_quantity_has_unit_id == params.resource_quantity.has_unit_id assert comm.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value @@ -111,11 +119,14 @@ describe "create Commitment" do end test "with only :has_beginning", %{params: params} do - params = Map.put(params, :has_beginning, DateTime.utc_now()) + params = params + |> Map.delete(:resource_inventoried_as_id) + |> Map.delete(:has_point_in_time) + |> Map.delete(:has_end) assert {:ok, %Commitment{} = comm} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert comm.action_id == params.action_id @@ -124,7 +135,7 @@ describe "create Commitment" do assert comm.input_of_id == params.input_of_id assert comm.output_of_id == params.output_of_id assert comm.resource_classified_as == params.resource_classified_as - assert comm.resource_conforms_to_id == nil + assert comm.resource_conforms_to_id == params.resource_conforms_to_id assert comm.resource_inventoried_as_id == nil assert comm.resource_quantity_has_unit_id == params.resource_quantity.has_unit_id assert comm.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value @@ -144,11 +155,14 @@ describe "create Commitment" do end test "with only :has_end", %{params: params} do - params = Map.put(params, :has_end, DateTime.utc_now()) + params = params + |> Map.delete(:resource_inventoried_as_id) + |> Map.delete(:has_point_in_time) + |> Map.delete(:has_beginning) assert {:ok, %Commitment{} = comm} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert comm.action_id == params.action_id @@ -157,8 +171,8 @@ describe "create Commitment" do assert comm.input_of_id == params.input_of_id assert comm.output_of_id == params.output_of_id assert comm.resource_classified_as == params.resource_classified_as - assert comm.resource_conforms_to_id == nil - assert comm.resource_inventoried_as_id == nil + assert comm.resource_conforms_to_id == params.resource_conforms_to_id + assert comm.resource_inventoried_as_id == nil assert comm.resource_quantity_has_unit_id == params.resource_quantity.has_unit_id assert comm.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value assert comm.effort_quantity_has_unit_id == params.effort_quantity.has_unit_id @@ -178,12 +192,12 @@ describe "create Commitment" do test "with both :has_beginning and :has_end", %{params: params} do params = params - |> Map.put(:has_beginning, DateTime.utc_now()) - |> Map.put(:has_end, DateTime.utc_now()) + |> Map.delete(:resource_inventoried_as_id) + |> Map.delete(:has_point_in_time) assert {:ok, %Commitment{} = comm} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert comm.action_id == params.action_id @@ -192,7 +206,7 @@ describe "create Commitment" do assert comm.input_of_id == params.input_of_id assert comm.output_of_id == params.output_of_id assert comm.resource_classified_as == params.resource_classified_as - assert comm.resource_conforms_to_id == nil + assert comm.resource_conforms_to_id == params.resource_conforms_to_id assert comm.resource_inventoried_as_id == nil assert comm.resource_quantity_has_unit_id == params.resource_quantity.has_unit_id assert comm.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value @@ -212,14 +226,11 @@ describe "create Commitment" do end test "with both :resource_conforms_to and :resource_inventoried_as", %{params: params} do - params = params - |> Map.put(:resource_conforms_to_id, Factory.insert!(:resource_specification).id) - |> Map.put(:resource_inventoried_as_id, Factory.insert!(:economic_resource).id) - |> Map.put(:has_point_in_time, DateTime.utc_now()) + params = Map.delete(params, :has_point_in_time) assert {:error, %Changeset{errors: errs}} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert {:ok, _} = Keyword.fetch(errs, :resource_conforms_to_id) @@ -228,12 +239,12 @@ describe "create Commitment" do test "with only :resource_conforms_to", %{params: params} do params = params - |> Map.put(:resource_conforms_to_id, Factory.insert!(:resource_specification).id) - |> Map.put(:has_point_in_time, DateTime.utc_now()) + |> Map.delete(:has_point_in_time) + |> Map.delete(:resource_inventoried_as_id) assert {:ok, %Commitment{} = comm} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert comm.action_id == params.action_id @@ -248,9 +259,9 @@ describe "create Commitment" do assert comm.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value assert comm.effort_quantity_has_unit_id == params.effort_quantity.has_unit_id assert comm.effort_quantity_has_numerical_value == params.effort_quantity.has_numerical_value - assert comm.has_beginning == nil - assert comm.has_end == nil - assert comm.has_point_in_time == params.has_point_in_time + assert comm.has_beginning == params.has_beginning + assert comm.has_end == params.has_end + assert comm.has_point_in_time == nil assert comm.due == params.due assert comm.finished == params.finished assert comm.note == params.note @@ -263,12 +274,12 @@ describe "create Commitment" do test "with only :resource_inventoried_as", %{params: params} do params = params - |> Map.put(:resource_inventoried_as_id, Factory.insert!(:economic_resource).id) - |> Map.put(:has_point_in_time, DateTime.utc_now()) + |> Map.delete(:has_point_in_time) + |> Map.delete(:resource_conforms_to_id) assert {:ok, %Commitment{} = comm} = params - |> Commitment.chgset() + |> Commitment.changeset() |> Repo.insert() assert comm.action_id == params.action_id @@ -283,9 +294,9 @@ describe "create Commitment" do assert comm.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value assert comm.effort_quantity_has_unit_id == params.effort_quantity.has_unit_id assert comm.effort_quantity_has_numerical_value == params.effort_quantity.has_numerical_value - assert comm.has_beginning == nil - assert comm.has_end == nil - assert comm.has_point_in_time == params.has_point_in_time + assert comm.has_beginning == params.has_beginning + assert comm.has_end == params.has_end + assert comm.has_point_in_time == nil assert comm.due == params.due assert comm.finished == params.finished assert comm.note == params.note @@ -296,37 +307,4 @@ describe "create Commitment" do assert comm.clause_of_id == params.clause_of_id end end - -test "with present assocs", %{params: params} do - old = Factory.insert!(:commitment) - - assert {:ok, %Commitment{} = new} = - old - |> Commitment.chgset(params) - |> Repo.update() - - assert new.action_id == params.action_id - assert new.provider_id == params.provider_id - assert new.receiver_id == params.receiver_id - assert new.input_of_id == params.input_of_id - assert new.output_of_id == params.output_of_id - assert new.resource_classified_as == params.resource_classified_as - assert new.resource_conforms_to_id == old.resource_conforms_to_id - assert new.resource_inventoried_as_id == old.resource_inventoried_as_id - assert new.resource_quantity_has_unit_id == params.resource_quantity.has_unit_id - assert new.resource_quantity_has_numerical_value == params.resource_quantity.has_numerical_value - assert new.effort_quantity_has_unit_id == params.effort_quantity.has_unit_id - assert new.effort_quantity_has_numerical_value == params.effort_quantity.has_numerical_value - assert new.has_beginning == old.has_beginning - assert new.has_end == old.has_end - assert new.has_point_in_time == old.has_point_in_time - assert new.due == params.due - assert new.finished == params.finished - assert new.note == params.note - # assert in_scope_of_id - assert new.agreed_in == params.agreed_in - assert new.independent_demand_of_id == params.independent_demand_of_id - assert new.at_location_id == params.at_location_id - assert new.clause_of_id == params.clause_of_id -end end diff --git a/test/vf/duration.test.exs b/test/vf/duration.test.exs @@ -32,13 +32,13 @@ embedded_schema do field :has_duration_numeric_duration, :float end -def chgset(params) do +def changeset(params) do %__MODULE__{} |> common(params) |> Map.put(:action, :insert) end -def chgset(schema, params) do +def changeset(schema, params) do schema |> common(params) |> Map.put(:action, :update) @@ -66,42 +66,42 @@ end test "insert", %{params: params} do # no changes when params is `%{}` - assert %Changeset{valid?: true, changes: %{}} = Dummy.chgset(%{}) + assert %Changeset{valid?: true, changes: %{}} = Dummy.changeset(%{}) # fields are nil when `:has_duration` is `nil` - assert %Changeset{valid?: true, changes: chgs} = Dummy.chgset(%{has_duration: nil}) + assert %Changeset{valid?: true, changes: chgs} = Dummy.changeset(%{has_duration: nil}) assert chgs.has_duration_unit_type == nil assert chgs.has_duration_numeric_duration == nil # fields are properly set when `:has_duration` is properly set - assert %Changeset{valid?: true, changes: chgs} = Dummy.chgset(%{has_duration: params}) + assert %Changeset{valid?: true, changes: chgs} = Dummy.changeset(%{has_duration: params}) assert chgs.has_duration_unit_type == params.unit_type assert chgs.has_duration_numeric_duration == params.numeric_duration # when no fields are provided, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(%{has_duration: %{}}) + = Dummy.changeset(%{has_duration: %{}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) # when `:unit_type` is `nil`, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(%{has_duration: %{unit_type: nil}}) + = Dummy.changeset(%{has_duration: %{unit_type: nil}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) # when `:numeric_duration` is `nil`, no fields are set assert %Changeset{valid?: false, changes: %{has_duration: _}, errors: errs} - = Dummy.chgset(%{has_duration: %{numeric_duration: nil}}) + = Dummy.changeset(%{has_duration: %{numeric_duration: nil}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) # when both fields are `nil`, no fields are set assert %Changeset{valid?: false, changes: %{has_duration: _}, errors: errs} - = Dummy.chgset(%{has_duration: %{unit_type: nil, numeric_duration: nil}}) + = Dummy.changeset(%{has_duration: %{unit_type: nil, numeric_duration: nil}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) @@ -109,17 +109,17 @@ end test "update", %{params: params, inserted: schema} do # no changes when params is `%{}` - assert %Changeset{valid?: true, changes: %{}} = Dummy.chgset(schema, %{}) + assert %Changeset{valid?: true, changes: %{}} = Dummy.changeset(schema, %{}) # fields are nil when `:has_duration` is `nil` assert %Changeset{valid?: true, changes: %{ has_duration_unit_type: nil, has_duration_numeric_duration: nil, - }} = Dummy.chgset(schema, %{has_duration: nil}) + }} = Dummy.changeset(schema, %{has_duration: nil}) # fields are changed when `:has_duration` is properly set assert %Changeset{valid?: true, changes: chgs} - = Dummy.chgset(schema, %{has_duration: params}) + = Dummy.changeset(schema, %{has_duration: params}) # since ecto won't change it if it is already there if schema.has_duration_unit_type != params.unit_type, do: assert chgs.has_duration_unit_type == params.unit_type @@ -127,28 +127,28 @@ test "update", %{params: params, inserted: schema} do # when no fields are provided, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(schema, %{has_duration: %{}}) + = Dummy.changeset(schema, %{has_duration: %{}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) # when `:unit_type` is `nil`, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(schema, %{has_duration: %{unit_type: nil}}) + = Dummy.changeset(schema, %{has_duration: %{unit_type: nil}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) # when `:numeric_duration` is `nil`, no fields are set assert %Changeset{valid?: false, changes: %{has_duration: _}, errors: errs} - = Dummy.chgset(schema, %{has_duration: %{numeric_duration: nil}}) + = Dummy.changeset(schema, %{has_duration: %{numeric_duration: nil}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) # when both fields are `nil`, no fields are set assert %Changeset{valid?: false, changes: %{has_duration: _}, errors: errs} - = Dummy.chgset(schema, %{has_duration: %{unit_type: nil, numeric_duration: nil}}) + = Dummy.changeset(schema, %{has_duration: %{unit_type: nil, numeric_duration: nil}}) assert length(Keyword.get_values(errs, :has_duration)) == 2 refute Map.has_key?(chgs, :has_duration_unit_type) or Map.has_key?(chgs, :has_duration_numeric_duration) diff --git a/test/vf/economic_event.test.exs b/test/vf/economic_event.test.exs @@ -21,12 +21,16 @@ use ZenflowsTest.Help.EctoCase, async: true alias Ecto.Changeset alias Zenflows.VF.EconomicEvent +setup_all do + [errmsg_exist_xnor: "exactly one of them must be provided"] +end + test """ `chgset/1`: every event requires the `:action_id`, `:provider_id`, `:receiver_id` fields and the allowed combinations of the datetime fields `:has_point_in_time`, `:has_beginning`, `:has_end` """ do - assert %Changeset{valid?: false} = cset = EconomicEvent.chgset(%{}) + assert %Changeset{valid?: false} = cset = EconomicEvent.changeset(%{}) err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:action_id]) assert {[_], err} = pop_in(err[:provider_id]) @@ -37,7 +41,7 @@ fields `:has_point_in_time`, `:has_beginning`, `:has_end` assert err == %{} assert %Changeset{valid?: false} = cset = - EconomicEvent.chgset(%{has_point_in_time: DateTime.utc_now()}) + EconomicEvent.changeset(%{has_point_in_time: DateTime.utc_now()}) err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:action_id]) assert {[_], err} = pop_in(err[:provider_id]) @@ -45,7 +49,7 @@ fields `:has_point_in_time`, `:has_beginning`, `:has_end` assert err == %{} assert %Changeset{valid?: false} = cset = - EconomicEvent.chgset(%{has_beginning: DateTime.utc_now()}) + EconomicEvent.changeset(%{has_beginning: DateTime.utc_now()}) err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:action_id]) assert {[_], err} = pop_in(err[:provider_id]) @@ -53,7 +57,7 @@ fields `:has_point_in_time`, `:has_beginning`, `:has_end` assert err == %{} assert %Changeset{valid?: false} = cset = - EconomicEvent.chgset(%{has_end: DateTime.utc_now()}) + EconomicEvent.changeset(%{has_end: DateTime.utc_now()}) err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:action_id]) assert {[_], err} = pop_in(err[:provider_id]) @@ -61,7 +65,7 @@ fields `:has_point_in_time`, `:has_beginning`, `:has_end` assert err == %{} assert %Changeset{valid?: false} = cset = - EconomicEvent.chgset(%{ + EconomicEvent.changeset(%{ has_beginning: DateTime.utc_now(), has_end: DateTime.utc_now(), }) @@ -93,7 +97,7 @@ describe "`chgset/1` with raise:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end test "pass with `:resource_inventoried_as`", %{params: params} do @@ -101,30 +105,30 @@ describe "`chgset/1` with raise:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_inventoried_as_id, res.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end - test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do - assert %Changeset{valid?: false} = cset = EconomicEvent.chgset(params) + test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do + assert %Changeset{valid?: false} = cset = EconomicEvent.changeset(params) err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end - test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do + test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do res = Factory.insert!(:economic_resource) spec = Factory.insert!(:resource_specification) assert %Changeset{valid?: false} = cset = params |> Map.put(:resource_inventoried_as_id, res.id) |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end @@ -136,7 +140,7 @@ describe "`chgset/1` with raise:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -145,7 +149,7 @@ describe "`chgset/1` with raise:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -175,7 +179,7 @@ describe "`chgset/1` with produce:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end test "pass with `:resource_inventoried_as`", %{params: params} do @@ -183,30 +187,30 @@ describe "`chgset/1` with produce:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_inventoried_as_id, res.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end - test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do - assert %Changeset{valid?: false} = cset = EconomicEvent.chgset(params) + test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do + assert %Changeset{valid?: false} = cset = EconomicEvent.changeset(params) err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end - test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do + test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do res = Factory.insert!(:economic_resource) spec = Factory.insert!(:resource_specification) assert %Changeset{valid?: false} = cset = params |> Map.put(:resource_inventoried_as_id, res.id) |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end @@ -218,7 +222,7 @@ describe "`chgset/1` with produce:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -227,7 +231,7 @@ describe "`chgset/1` with produce:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -253,7 +257,7 @@ describe "`chgset/1` with lower:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -262,7 +266,7 @@ describe "`chgset/1` with lower:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -271,7 +275,7 @@ describe "`chgset/1` with lower:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -299,7 +303,7 @@ describe "`chgset/1` with consume:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -308,7 +312,7 @@ describe "`chgset/1` with consume:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -317,7 +321,7 @@ describe "`chgset/1` with consume:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -340,27 +344,27 @@ describe "`chgset/1` with use:" do }} end - test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do - assert %Changeset{valid?: false} = cset = EconomicEvent.chgset(params) + test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do + assert %Changeset{valid?: false} = cset = EconomicEvent.changeset(params) err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end - test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do + test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do res = Factory.insert!(:economic_resource) spec = Factory.insert!(:resource_specification) assert %Changeset{valid?: false} = cset = params |> Map.put(:resource_inventoried_as_id, res.id) |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end @@ -369,7 +373,7 @@ describe "`chgset/1` with use:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end test "pass with `:resource_inventoried_as`", %{params: params} do @@ -377,12 +381,12 @@ describe "`chgset/1` with use:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_inventoried_as_id, res.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end end test "`chgset/1` with work: pass when all good" do - assert %Changeset{valid?: true} = EconomicEvent.chgset(%{ + assert %Changeset{valid?: true} = EconomicEvent.changeset(%{ action_id: "work", input_of_id: Factory.insert!(:process).id, provider_id: Factory.insert!(:agent).id, @@ -416,7 +420,7 @@ describe "`chgset/1` with cite:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end test "pass with `:resource_inventoried_as`", %{params: params} do @@ -424,30 +428,30 @@ describe "`chgset/1` with cite:" do assert %Changeset{valid?: true} = params |> Map.put(:resource_inventoried_as_id, res.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end - test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do - assert %Changeset{valid?: false} = cset = EconomicEvent.chgset(params) + test "fail without `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do + assert %Changeset{valid?: false} = cset = EconomicEvent.changeset(params) err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end - test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", %{params: params} do + test "fail with `:resource_conforms_to` and `:resource_inventoried_as`", + %{params: params, errmsg_exist_xnor: errmsg} do res = Factory.insert!(:economic_resource) spec = Factory.insert!(:resource_specification) assert %Changeset{valid?: false} = cset = params |> Map.put(:resource_inventoried_as_id, res.id) |> Map.put(:resource_conforms_to_id, spec.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "these are mutually exclusive and exactly one must be provided" - assert {[^msg], err} = pop_in(err[:resource_conforms_to_id]) - assert {[^msg], err} = pop_in(err[:resource_inventoried_as_id]) + assert {[^errmsg], err} = pop_in(err[:resource_conforms_to_id]) + assert {[^errmsg], err} = pop_in(err[:resource_inventoried_as_id]) assert err == %{} end end @@ -470,30 +474,30 @@ describe "`chgset/1` with deliverService:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:output_of_id, params.input_of_id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) - msg = "must have different processes" + msg = "all of them must be different" assert {[^msg], err} = pop_in(err[:input_of_id]) assert {[^msg], err} = pop_in(err[:output_of_id]) assert err == %{} end test "pass with `:input_of` and `:output_of` differ", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "pass without `:input_of`", %{params: params} do assert %Changeset{valid?: true} = params |> Map.delete(:input_of) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end test "pass without `:output_of`", %{params: params} do assert %Changeset{valid?: true} = params |> Map.delete(:output_of) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() end end @@ -516,7 +520,7 @@ describe "`chgset/1` with pickup:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -525,7 +529,7 @@ describe "`chgset/1` with pickup:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -534,7 +538,7 @@ describe "`chgset/1` with pickup:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -562,7 +566,7 @@ describe "`chgset/1` with dropoff:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -571,7 +575,7 @@ describe "`chgset/1` with dropoff:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -580,7 +584,7 @@ describe "`chgset/1` with dropoff:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -607,7 +611,7 @@ describe "`chgset/1` with accept:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -616,7 +620,7 @@ describe "`chgset/1` with accept:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -625,7 +629,7 @@ describe "`chgset/1` with accept:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -653,7 +657,7 @@ describe "`chgset/1` with modify:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -662,7 +666,7 @@ describe "`chgset/1` with modify:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -671,7 +675,7 @@ describe "`chgset/1` with modify:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -680,7 +684,7 @@ describe "`chgset/1` with modify:" do end test "`chgset/1` with transferCustody: pass when all good" do - assert %Changeset{valid?: true} = EconomicEvent.chgset(%{ + assert %Changeset{valid?: true} = EconomicEvent.changeset(%{ action_id: "transferCustody", provider_id: Factory.insert!(:agent).id, receiver_id: Factory.insert!(:agent).id, @@ -694,7 +698,7 @@ test "`chgset/1` with transferCustody: pass when all good" do end test "`chgset/1` with transferAllRights: pass when all good" do - assert %Changeset{valid?: true} = EconomicEvent.chgset(%{ + assert %Changeset{valid?: true} = EconomicEvent.changeset(%{ action_id: "transferAllRights", provider_id: Factory.insert!(:agent).id, receiver_id: Factory.insert!(:agent).id, @@ -709,7 +713,7 @@ test "`chgset/1` with transferAllRights: pass when all good" do end test "`chgset/1` with transfer: pass when all good" do - assert %Changeset{valid?: true} = EconomicEvent.chgset(%{ + assert %Changeset{valid?: true} = EconomicEvent.changeset(%{ action_id: "transfer", provider_id: Factory.insert!(:agent).id, receiver_id: Factory.insert!(:agent).id, @@ -741,7 +745,7 @@ describe "`chgset/1` with move:" do end test "pass when all good", %{params: params} do - assert %Changeset{valid?: true} = EconomicEvent.chgset(params) + assert %Changeset{valid?: true} = EconomicEvent.changeset(params) end test "fail when `:provider` and `:receiver` differ", %{params: params} do @@ -750,7 +754,7 @@ describe "`chgset/1` with move:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:provider_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -759,7 +763,7 @@ describe "`chgset/1` with move:" do assert %Changeset{valid?: false} = cset = params |> Map.put(:receiver_id, agent.id) - |> EconomicEvent.chgset() + |> EconomicEvent.changeset() err = Changeset.traverse_errors(cset, &elem(&1, 0)) assert {[_], err} = pop_in(err[:provider_id]) assert {[_], err} = pop_in(err[:receiver_id]) @@ -777,7 +781,7 @@ describe "" do # assert {:error, %Changeset{errors: errs}} = # params - # |> EconomicEvent.chgset() + # |> EconomicEvent.changeset() # |> Repo.insert() # assert {:ok, _} = Keyword.fetch(errs, :resource_conforms_to_id) @@ -793,7 +797,7 @@ describe "" do # assert {:error, %Changeset{errors: errs}} = # params - # |> EconomicEvent.chgset() + # |> EconomicEvent.changeset() # |> Repo.insert() # assert {:ok, _} = Keyword.fetch(errs, :resource_conforms_to_id) @@ -808,7 +812,7 @@ describe "" do # assert {:ok, %EconomicEvent{} = eco_evt} = # params - # |> EconomicEvent.chgset() + # |> EconomicEvent.changeset() # |> Repo.insert() # assert eco_evt.action_id == params.action_id @@ -843,7 +847,7 @@ describe "" do # assert {:ok, %EconomicEvent{} = eco_evt} = # params - # |> EconomicEvent.chgset() + # |> EconomicEvent.changeset() # |> Repo.insert() # assert eco_evt.action_id == params.action_id @@ -879,7 +883,7 @@ describe "" do # assert {:ok, %EconomicEvent{} = eco_evt} = # params - # |> EconomicEvent.chgset() + # |> EconomicEvent.changeset() # |> Repo.insert() # assert eco_evt.action_id == params.action_id @@ -914,7 +918,7 @@ describe "" do # assert {:ok, %EconomicEvent{} = eco_evt} = # params - # |> EconomicEvent.chgset() + # |> EconomicEvent.changeset() # |> Repo.insert() # assert eco_evt.action_id == params.action_id @@ -948,7 +952,7 @@ test "update EconomicEvent", %{params: _params} do # assert {:ok, %EconomicEvent{} = new} = # old - # |> EconomicEvent.chgset(params) + # |> EconomicEvent.changeset(params) # |> Repo.update() # assert new.action_id == old.action_id diff --git a/test/vf/economic_event/domain.test.exs b/test/vf/economic_event/domain.test.exs @@ -43,9 +43,11 @@ setup ctx do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, res, _} = Domain.create(params, %{name: Factory.str("name")}) + assert %EconomicEvent{resource_inventoried_as: res} = + Domain.create!(params, %{name: Factory.str("name")}) + |> Domain.preload(:resource_inventoried_as) if ctx[:want_contained] || ctx[:want_container] do agent = Factory.insert!(:agent) @@ -58,17 +60,19 @@ setup ctx do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(params, %{name: Factory.str("name")}) + assert %EconomicEvent{resource_inventoried_as: tmp_res} = + Domain.create!(params, %{name: Factory.str("name")}) + |> Domain.preload(:resource_inventoried_as) # TODO: use combine-separate when implemented instead if ctx[:want_contained] do - assert {:ok, _} = Changeset.change(res, contained_in_id: tmp_res.id) |> Repo.update() + Changeset.change(res, contained_in_id: tmp_res.id) |> Repo.update!() end if ctx[:want_container] do - assert {:ok, _} = Changeset.change(tmp_res, contained_in_id: res.id) |> Repo.update() + Changeset.change(tmp_res, contained_in_id: res.id) |> Repo.update!() end end @@ -96,7 +100,7 @@ describe "`create/2` with raise:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), }} end end @@ -113,7 +117,7 @@ describe "`create/2` with raise:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), to_location_id: Factory.insert!(:spatial_thing).id, } res_params = %{ @@ -129,8 +133,10 @@ describe "`create/2` with raise:" do license: Factory.str("license"), metadata: %{Factory.str("key") => Factory.str("val")}, } - assert {:ok, %EconomicEvent{}, %EconomicResource{} = res, _} = + assert {:ok, %EconomicEvent{} = evt} = Domain.create(evt_params, res_params) + evt = Domain.preload(evt, :resource_inventoried_as) + res = evt.resource_inventoried_as assert res.name == res_params.name assert res.note == res_params.note @@ -154,9 +160,9 @@ describe "`create/2` with raise:" do end test "pass with `:resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value + params.resource_quantity.has_numerical_value @@ -171,23 +177,23 @@ describe "`create/2` with raise:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have ownership over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do - assert {:error, "you can't raise into a contained resource"} = Domain.create(params, nil) + assert {:error, "you can't raise into a contained resource"} = Domain.create(params) end @tag :want_container test "fail when the resource is a container resource", %{params: params} do - assert {:error, "you can't raise into a container resource"} = Domain.create(params, nil) + assert {:error, "you can't raise into a container resource"} = Domain.create(params) end end @@ -207,7 +213,7 @@ describe "`create/2` with produce:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} end end @@ -225,7 +231,7 @@ describe "`create/2` with produce:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), to_location_id: Factory.insert!(:spatial_thing).id, } res_params = %{ @@ -241,8 +247,10 @@ describe "`create/2` with produce:" do license: Factory.str("license"), metadata: %{Factory.str("key") => Factory.str("val")}, } - assert {:ok, %EconomicEvent{}, %EconomicResource{} = res, _} = + assert {:ok, %EconomicEvent{} = evt} = Domain.create(evt_params, res_params) + evt = Domain.preload(evt, :resource_inventoried_as) + res = evt.resource_inventoried_as assert res.name == res_params.name assert res.note == res_params.note @@ -266,9 +274,9 @@ describe "`create/2` with produce:" do end test "pass with `:resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value + params.resource_quantity.has_numerical_value @@ -283,23 +291,23 @@ describe "`create/2` with produce:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have ownership over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do - assert {:error, "you can't produce into a contained resource"} = Domain.create(params, nil) + assert {:error, "you can't produce into a contained resource"} = Domain.create(params) end @tag :want_container test "fail when the resource is a container resource", %{params: params} do - assert {:error, "you can't produce into a container resource"} = Domain.create(params, nil) + assert {:error, "you can't produce into a container resource"} = Domain.create(params) end end @@ -314,14 +322,14 @@ describe "`create/2` with lower:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), }} end test "pass when all good", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value assert res_after.onhand_quantity_has_numerical_value == @@ -335,23 +343,23 @@ describe "`create/2` with lower:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have ownership over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do - assert {:error, "you can't lower a contained resource"} = Domain.create(params, nil) + assert {:error, "you can't lower a contained resource"} = Domain.create(params) end @tag :want_container test "fail when the resource is a container resource", %{params: params} do - assert {:error, "you can't lower a container resource"} = Domain.create(params, nil) + assert {:error, "you can't lower a container resource"} = Domain.create(params) end end @@ -367,15 +375,15 @@ describe "`create/2` with consume:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_beginning: DateTime.utc_now(), - has_end: DateTime.utc_now(), + has_beginning: Factory.now(), + has_end: Factory.now(), }} end test "pass when all good", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -390,23 +398,23 @@ describe "`create/2` with consume:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have ownership over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do - assert {:error, "you can't consume a contained resource"} = Domain.create(params, nil) + assert {:error, "you can't consume a contained resource"} = Domain.create(params) end @tag :want_container test "fail when the resource is a container resource", %{params: params} do - assert {:error, "you can't consume a container resource"} = Domain.create(params, nil) + assert {:error, "you can't consume a container resource"} = Domain.create(params) end end @@ -426,28 +434,28 @@ describe "`create/2` with use:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), }} end test "pass when all good", %{params: params} do - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) + assert {:ok, %EconomicEvent{}} = Domain.create(params) end test "fail when the event's resource quantity unit doesn't match with the resource's", %{params: params} do assert {:error, "the unit of resource quantity must match with the unit of this resource"} = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) - |> Domain.create(nil) + |> Domain.create() end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do - assert {:error, "you can't use a contained resource"} = Domain.create(params, nil) + assert {:error, "you can't use a contained resource"} = Domain.create(params) end @tag :want_container test "fail when the resource is a container resource", %{params: params} do - assert {:error, "you can't use a container resource"} = Domain.create(params, nil) + assert {:error, "you can't use a container resource"} = Domain.create(params) end end @@ -463,12 +471,12 @@ describe "`create/2` with pickup:" do has_unit_id: res.onhand_quantity_has_unit_id, has_numerical_value: res.onhand_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} end test "pass when all good", %{params: params} do - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) + assert {:ok, %EconomicEvent{}} = Domain.create(params) end test "fail when provider doesn't have custody over the resource", %{params: params} do @@ -478,48 +486,49 @@ describe "`create/2` with pickup:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have custody over this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do assert {:error, "you can't pickup a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's quantity value and resource's onhand-quantity value differ", %{params: params} do params = update_in(params.resource_quantity.has_numerical_value, &(&1 + 1)) assert {:error, "the pickup events need to fully pickup the resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when more than one pickup event references the same resource in the same process", %{params: params} do - assert {:ok, _} = Domain.create(params, nil) + assert {:ok, _} = Domain.create(params) assert {:error, "no more than one pickup event in the same process, referring to the same resource is allowed"} - = Domain.create(params, nil) + = Domain.create(params) end end describe "`create/2` with dropoff:" do setup %{res: res} do - assert {:ok, pair_evt} = Domain.create(%{ - action_id: "pickup", - input_of_id: Factory.insert!(:process).id, - provider_id: res.custodian_id, - receiver_id: res.custodian_id, - resource_inventoried_as_id: res.id, - resource_quantity: %{ - has_unit_id: res.onhand_quantity_has_unit_id, - has_numerical_value: res.onhand_quantity_has_numerical_value, - }, - has_end: DateTime.utc_now(), - }, nil) + assert %EconomicEvent{} = pair_evt = + Domain.create!(%{ + action_id: "pickup", + input_of_id: Factory.insert!(:process).id, + provider_id: res.custodian_id, + receiver_id: res.custodian_id, + resource_inventoried_as_id: res.id, + resource_quantity: %{ + has_unit_id: res.onhand_quantity_has_unit_id, + has_numerical_value: res.onhand_quantity_has_numerical_value, + }, + has_end: Factory.now(), + }) %{params: %{ action_id: "dropoff", @@ -531,15 +540,16 @@ describe "`create/2` with dropoff:" do has_unit_id: pair_evt.resource_quantity_has_unit_id, has_numerical_value: pair_evt.resource_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), - has_end: DateTime.utc_now(), + has_beginning: Factory.now(), + has_end: Factory.now(), to_location_id: Factory.insert!(:spatial_thing).id, }} end test "pass when all good", %{params: params} do - assert {:ok, %EconomicEvent{} = evt} = Domain.create(params, nil) - {:ok, res} = EconomicResource.Domain.one(evt.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{} = evt} = Domain.create(params) + evt = Domain.preload(evt, :resource_inventoried_as) + res = evt.resource_inventoried_as assert res.current_location_id == params.to_location_id end @@ -550,26 +560,26 @@ describe "`create/2` with dropoff:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have custody over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and paired event's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of the paired event"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when the resource is a container and event's quantity value and resource's onhand-quantity value differ", %{params: params} do params = update_in(params.resource_quantity.has_numerical_value, &(&1 + 1)) assert {:error, "the dropoff events need to fully dropoff the resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when more than one dropoff event references the same resource in the same process", %{params: params} do - assert {:ok, _} = Domain.create(params, nil) + assert {:ok, _} = Domain.create(params) assert {:error, "no more than one dropoff event in the same process, referring to the same resource is allowed"} - = Domain.create(params, nil) + = Domain.create(params) end end @@ -585,14 +595,14 @@ describe "`create/2` with accept:" do has_unit_id: res.onhand_quantity_has_unit_id, has_numerical_value: res.onhand_quantity_has_numerical_value, }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), }} end test "pass when all good", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value @@ -607,68 +617,67 @@ describe "`create/2` with accept:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have custody over this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do assert {:error, "you can't accept a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's quantity value and resource's onhand-quantity value differ", %{params: params} do params = update_in(params.resource_quantity.has_numerical_value, &(&1 + 1)) assert {:error, "the accept events need to fully accept the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag skip: "TODO: use combine-separate when implemented" @tag :want_combine test "fail when the there are any combine events", %{params: params} do assert {:error, "you can't add another accept event to the same process where there are at least one combine or separate events"} = - Domain.create(params, nil) + Domain.create(params) end @tag skip: "TODO: use combine-separate when implemented" @tag :want_combine test "fail when the there are any separate events", %{params: params} do assert {:error, "you can't add another accept event to the same process where there are at least one combine or separate events"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when more than one accept event references the same resource in the same process", %{params: params} do - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) + assert {:ok, %EconomicEvent{}} = Domain.create(params) # in order to satisfy the fact that they it should fully # accept the resource - params - |> Map.put(:action_id, "raise") - |> Domain.create(nil) + params |> Map.put(:action_id, "raise") |> Domain.create() assert {:error, "no more than one accept event in the same process, referring to the same resource is allowed"} = - Domain.create(params, nil) + Domain.create(params) end end describe "`create/2` with modify:" do setup %{res: res} do - assert {:ok, pair_evt} = Domain.create(%{ - action_id: "accept", - input_of_id: Factory.insert!(:process).id, - provider_id: res.custodian_id, - receiver_id: res.custodian_id, - resource_inventoried_as_id: res.id, - resource_quantity: %{ - has_unit_id: res.onhand_quantity_has_unit_id, - has_numerical_value: res.onhand_quantity_has_numerical_value, - }, - has_end: DateTime.utc_now(), - }, nil) + assert %EconomicEvent{} = pair_evt = + Domain.create!(%{ + action_id: "accept", + input_of_id: Factory.insert!(:process).id, + provider_id: res.custodian_id, + receiver_id: res.custodian_id, + resource_inventoried_as_id: res.id, + resource_quantity: %{ + has_unit_id: res.onhand_quantity_has_unit_id, + has_numerical_value: res.onhand_quantity_has_numerical_value, + }, + has_end: Factory.now(), + }) %{params: %{ action_id: "modify", @@ -680,18 +689,18 @@ describe "`create/2` with modify:" do has_unit_id: pair_evt.resource_quantity_has_unit_id, has_numerical_value: pair_evt.resource_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), - has_end: DateTime.utc_now(), + has_beginning: Factory.now(), + has_end: Factory.now(), }} end test "pass when all good", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_before.stage_id == nil - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, proc} = Process.Domain.one(params.output_of_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + proc = Process.Domain.one!(params.output_of_id) assert res_after.stage_id == proc.based_on_id assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value @@ -706,7 +715,7 @@ describe "`create/2` with modify:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have custody over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and paired event's unit differ", %{params: params} do @@ -714,25 +723,23 @@ describe "`create/2` with modify:" do Factory.insert!(:unit).id end) assert {:error, "the unit of resource quantity must match with the unit of the paired event"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's quantity value and resource's onhand-quantity value differ", %{params: params} do params = update_in(params.resource_quantity.has_numerical_value, &(&1 + 1)) assert {:error, "the modify events need to fully modify the resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when more than one modify event references the same resource in the same process", %{params: params} do - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) + assert {:ok, %EconomicEvent{}} = Domain.create(params) # in order to satisfy the fact that they it should fully # modify the resource - params - |> Map.put(:action_id, "raise") - |> Domain.create(nil) + params |> Map.put(:action_id, "raise") |> Domain.create!() assert {:error, "no more than one modify event in the same process, referring to the same resource is allowed"} = - Domain.create(params, nil) + Domain.create(params) end end @@ -749,21 +756,22 @@ describe "`create/2` with transferCustody:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), } - assert {:ok, _, to_res, _} = Domain.create(params, %{name: Factory.str("name")}) + assert %EconomicEvent{resource_inventoried_as_id: to_res_id} = + Domain.create!(params, %{name: Factory.str("name")}) %{params: %{ action_id: "transferCustody", provider_id: res.custodian_id, receiver_id: Factory.insert!(:agent).id, resource_inventoried_as_id: res.id, - to_resource_inventoried_as_id: to_res.id, + to_resource_inventoried_as_id: to_res_id, resource_quantity: %{ has_unit_id: res.onhand_quantity_has_unit_id, has_numerical_value: res.onhand_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} else %{params: %{ @@ -776,13 +784,13 @@ describe "`create/2` with transferCustody:" do has_numerical_value: res.onhand_quantity_has_numerical_value, }, to_location_id: Factory.insert!(:spatial_thing).id, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} end end test "pass without `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) contained_ids = Enum.map(0..9, fn _ -> agent = Factory.insert!(:agent) @@ -795,11 +803,15 @@ describe "`create/2` with transferCustody:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - assert {:ok, _} = - Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) |> Repo.update() + assert %EconomicEvent{} = evt = + Domain.create!(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as + + Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) + |> Repo.update!() tmp_res.id end) @@ -817,8 +829,10 @@ describe "`create/2` with transferCustody:" do license: Factory.str("license"), metadata: %{Factory.str("key") => Factory.str("val")}, } - assert {:ok, %EconomicEvent{} = evt, _, %EconomicResource{} = to_res} = Domain.create(params, res_params) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{} = evt} = Domain.create(params, res_params) + evt = Domain.preload(evt, :to_resource_inventoried_as) + to_res = evt.to_resource_inventoried_as + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value @@ -857,12 +871,12 @@ describe "`create/2` with transferCustody:" do @tag :want_to_resource test "pass with `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_before} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_before = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_after} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_after = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value @@ -882,45 +896,45 @@ describe "`create/2` with transferCustody:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have custody over this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do assert {:error, "you can't transfer-custody a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when the resource is a container and onhand-quantity is non-positive", %{params: params} do err = "the transfer-custody events need container resources to have positive onhand-quantity" - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res = EconomicResource.Domain.one!(params.resource_inventoried_as_id) Changeset.change(res, onhand_quantity_has_numerical_value: 0.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) Changeset.change(res, onhand_quantity_has_numerical_value: -1.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) end @tag :want_container test "fail when event's quantity value and resource's onhand-quantity value differ", %{params: params} do params = update_in(params.resource_quantity.has_numerical_value, &(&1 + 1)) assert {:error, "the transfer-custody events need to fully transfer the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container @tag :want_to_resource test "fail when transferring a container resource into another resource", %{params: params} do assert {:error, "you can't transfer-custody a container resource into another resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -935,18 +949,18 @@ describe "`create/2` with transferCustody:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - # TODO: use combine-separate when implemented instead - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{resource_inventoried_as_id: tmp_res_id}} = + Domain.create(raise_params, %{name: Factory.str("name")}) - res - |> Changeset.change(contained_in_id: tmp_res.id) + # TODO: use combine-separate when implemented instead + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) + |> Changeset.change(contained_in_id: tmp_res_id) |> Repo.update!() assert {:error, "you can't transfer-custody into a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -961,39 +975,38 @@ describe "`create/2` with transferCustody:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as # TODO: use combine-separate when implemented instead Changeset.change(tmp_res, contained_in_id: params.to_resource_inventoried_as_id) - |> Repo.update() + |> Repo.update!() assert {:error, "you can't transfer-custody into a container resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when event's unit and to-resource's unit differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(onhand_quantity_has_unit_id: Factory.insert!(:unit).id) |> Repo.update!() assert {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when resoure and to-resource don't conform to the same spec", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(conforms_to_id: Factory.insert!(:resource_specification).id) |> Repo.update!() assert {:error, "the resources must conform to the same specification"} = - Domain.create(params, nil) + Domain.create(params) end end @@ -1010,21 +1023,22 @@ describe "`create/2` with transferAllRights:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), } - assert {:ok, _, to_res, _} = Domain.create(params, %{name: Factory.str("name")}) + assert %EconomicEvent{resource_inventoried_as_id: to_res_id} = + Domain.create!(params, %{name: Factory.str("name")}) %{params: %{ action_id: "transferAllRights", provider_id: res.primary_accountable_id, receiver_id: Factory.insert!(:agent).id, resource_inventoried_as_id: res.id, - to_resource_inventoried_as_id: to_res.id, + to_resource_inventoried_as_id: to_res_id, resource_quantity: %{ has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: res.accounting_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} else %{params: %{ @@ -1036,13 +1050,13 @@ describe "`create/2` with transferAllRights:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: res.accounting_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} end end test "pass without `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) contained_ids = Enum.map(0..9, fn _ -> agent = Factory.insert!(:agent) @@ -1055,11 +1069,14 @@ describe "`create/2` with transferAllRights:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - assert {:ok, _} = - Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) |> Repo.update() + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as + Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) + |> Repo.update!() tmp_res.id end) @@ -1077,8 +1094,11 @@ describe "`create/2` with transferAllRights:" do license: Factory.str("license"), metadata: %{Factory.str("key") => Factory.str("val")}, } - assert {:ok, %EconomicEvent{} = evt, _, %EconomicResource{} = to_res} = Domain.create(params, res_params) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(params, res_params) + evt = Domain.preload(evt, :to_resource_inventoried_as) + to_res = evt.to_resource_inventoried_as + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -1116,12 +1136,12 @@ describe "`create/2` with transferAllRights:" do @tag :want_to_resource test "pass with `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_before} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_before = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_after} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_after = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -1141,45 +1161,45 @@ describe "`create/2` with transferAllRights:" do |> Map.put(:provider_id, agent.id) |> Map.put(:receiver_id, agent.id) assert {:error, "you don't have accountability over this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do assert {:error, "you can't transfer-all-rights a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when the resource is a container and accounting-quantity is non-positive", %{params: params} do err = "the transfer-all-rights events need container resources to have positive accounting-quantity" - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res = EconomicResource.Domain.one!(params.resource_inventoried_as_id) Changeset.change(res, accounting_quantity_has_numerical_value: 0.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) Changeset.change(res, accounting_quantity_has_numerical_value: -1.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) end @tag :want_container test "fail when event's quantity value and resource's accounting-quantity value differ", %{params: params} do params = update_in(params.resource_quantity.has_numerical_value, &(&1 + 1)) assert {:error, "the transfer-all-rights events need to fully transfer the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container @tag :want_to_resource test "fail when transferring a container resource into another resource", %{params: params} do assert {:error, "you can't transfer-all-rights a container resource into another resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -1194,18 +1214,18 @@ describe "`create/2` with transferAllRights:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - # TODO: use combine-separate when implemented instead - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{resource_inventoried_as_id: tmp_res_id}} = + Domain.create(raise_params, %{name: Factory.str("name")}) - res - |> Changeset.change(contained_in_id: tmp_res.id) + # TODO: use combine-separate when implemented instead + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) + |> Changeset.change(contained_in_id: tmp_res_id) |> Repo.update!() assert {:error, "you can't transfer-all-rights into a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -1220,39 +1240,38 @@ describe "`create/2` with transferAllRights:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as # TODO: use combine-separate when implemented instead Changeset.change(tmp_res, contained_in_id: params.to_resource_inventoried_as_id) |> Repo.update() assert {:error, "you can't transfer-all-rights into a container resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when event's unit and to-resource's unit differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(accounting_quantity_has_unit_id: Factory.insert!(:unit).id) |> Repo.update!() assert {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when resoure and to-resource don't conform to the same spec", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(conforms_to_id: Factory.insert!(:resource_specification).id) |> Repo.update!() assert {:error, "the resources must conform to the same specification"} = - Domain.create(params, nil) + Domain.create(params) end end @@ -1269,21 +1288,22 @@ describe "`create/2` with transfer:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), } - assert {:ok, _, to_res, _} = Domain.create(params, %{name: Factory.str("name")}) + assert %EconomicEvent{resource_inventoried_as_id: to_res_id} = + Domain.create!(params, %{name: Factory.str("name")}) %{params: %{ action_id: "transfer", provider_id: res.primary_accountable_id, receiver_id: Factory.insert!(:agent).id, resource_inventoried_as_id: res.id, - to_resource_inventoried_as_id: to_res.id, + to_resource_inventoried_as_id: to_res_id, resource_quantity: %{ has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: res.accounting_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} else %{params: %{ @@ -1295,13 +1315,13 @@ describe "`create/2` with transfer:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: res.accounting_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} end end test "pass without `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) contained_ids = Enum.map(0..9, fn _ -> agent = Factory.insert!(:agent) @@ -1314,11 +1334,14 @@ describe "`create/2` with transfer:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - assert {:ok, _} = - Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) |> Repo.update() + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as + Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) + |> Repo.update!() tmp_res.id end) @@ -1336,8 +1359,13 @@ describe "`create/2` with transfer:" do license: Factory.str("license"), metadata: %{Factory.str("key") => Factory.str("val")}, } - assert {:ok, %EconomicEvent{} = evt, _, %EconomicResource{} = to_res} = Domain.create(params, res_params) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{} = evt} + = Domain.create( + params, + res_params) + evt = Domain.preload(evt, :to_resource_inventoried_as) + to_res = evt.to_resource_inventoried_as + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -1376,12 +1404,12 @@ describe "`create/2` with transfer:" do @tag :want_to_resource test "pass with `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_before} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_before = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_after} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_after = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -1395,100 +1423,96 @@ describe "`create/2` with transfer:" do end test "fail when provider doesn't have accountability over the resource", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(primary_accountable_id: Factory.insert!(:agent).id) |> Repo.update!() + assert {:error, "you don't have accountability over this resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when provider doesn't have custody over the resource", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(custodian_id: Factory.insert!(:agent).id) |> Repo.update!() + assert {:error, "you don't have custody over this resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do assert {:error, "you can't transfer a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when event's unit and to-resource's unit differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(accounting_quantity_has_unit_id: Factory.insert!(:unit).id) |> Repo.update!() assert {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when the resource is a container and accounting-quantity is non-positive", %{params: params} do err = "the transfer events need container resources to have positive accounting-quantity" - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res = EconomicResource.Domain.one!(params.resource_inventoried_as_id) - Changeset.change(res, accounting_quantity_has_numerical_value: 0.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + Changeset.change(res, accounting_quantity_has_numerical_value: 0.0) + |> Repo.update!() + assert {:error, ^err} = Domain.create(params) Changeset.change(res, accounting_quantity_has_numerical_value: -1.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) end @tag :want_container test "fail when the resource is a container and onhand-quantity is non-positive", %{params: params} do err = "the transfer events need container resources to have positive onhand-quantity" - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res = EconomicResource.Domain.one!(params.resource_inventoried_as_id) - Changeset.change(res, onhand_quantity_has_numerical_value: 0.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + Changeset.change(res, onhand_quantity_has_numerical_value: 0.0) + |> Repo.update!() + assert {:error, ^err} = Domain.create(params) - Changeset.change(res, onhand_quantity_has_numerical_value: -1.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + Changeset.change(res, onhand_quantity_has_numerical_value: -1.0) + |> Repo.update!() + assert {:error, ^err} = Domain.create(params) end @tag :want_container test "fail when event's quantity value and resource's accounting-quantity value differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(accounting_quantity_has_numerical_value: params.resource_quantity.has_numerical_value + 1) |> Repo.update!() + assert {:error, "the transfer events need to fully transfer the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when event's quantity value and resource's onhnad-quantity value differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(onhand_quantity_has_numerical_value: params.resource_quantity.has_numerical_value + 1) |> Repo.update!() assert {:error, "the transfer events need to fully transfer the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container @tag :want_to_resource test "fail when transferring a container resource into another resource", %{params: params} do assert {:error, "you can't transfer a container resource into another resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -1503,18 +1527,18 @@ describe "`create/2` with transfer:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - # TODO: use combine-separate when implemented instead - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{resource_inventoried_as_id: tmp_res_id}} = + Domain.create(raise_params, %{name: Factory.str("name")}) - res - |> Changeset.change(contained_in_id: tmp_res.id) + # TODO: use combine-separate when implemented instead + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) + |> Changeset.change(contained_in_id: tmp_res_id) |> Repo.update!() assert {:error, "you can't transfer into a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -1529,27 +1553,28 @@ describe "`create/2` with transfer:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as # TODO: use combine-separate when implemented instead Changeset.change(tmp_res, contained_in_id: params.to_resource_inventoried_as_id) |> Repo.update() assert {:error, "you can't transfer into a container resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when resoure and to-resource don't conform to the same spec", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(conforms_to_id: Factory.insert!(:resource_specification).id) |> Repo.update!() assert {:error, "the resources must conform to the same specification"} = - Domain.create(params, nil) + Domain.create(params) end end @@ -1565,21 +1590,22 @@ describe "`create/2` with move:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: Factory.float(), }, - has_point_in_time: DateTime.utc_now(), + has_point_in_time: Factory.now(), } - assert {:ok, _, to_res, _} = Domain.create(params, %{name: Factory.str("name")}) + assert %EconomicEvent{resource_inventoried_as_id: to_res_id} = + Domain.create!(params, %{name: Factory.str("name")}) %{params: %{ action_id: "move", provider_id: res.primary_accountable_id, receiver_id: res.custodian_id, resource_inventoried_as_id: res.id, - to_resource_inventoried_as_id: to_res.id, + to_resource_inventoried_as_id: to_res_id, resource_quantity: %{ has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: res.accounting_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} else %{params: %{ @@ -1591,13 +1617,13 @@ describe "`create/2` with move:" do has_unit_id: res.accounting_quantity_has_unit_id, has_numerical_value: res.accounting_quantity_has_numerical_value, }, - has_beginning: DateTime.utc_now(), + has_beginning: Factory.now(), }} end end test "pass without `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) contained_ids = Enum.map(0..9, fn _ -> agent = Factory.insert!(:agent) @@ -1610,11 +1636,14 @@ describe "`create/2` with move:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - assert {:ok, _} = - Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) |> Repo.update() + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as + Changeset.change(tmp_res, contained_in_id: params.resource_inventoried_as_id) + |> Repo.update!() tmp_res.id end) @@ -1632,8 +1661,11 @@ describe "`create/2` with move:" do license: Factory.str("license"), metadata: %{Factory.str("key") => Factory.str("val")}, } - assert {:ok, %EconomicEvent{} = evt, _, %EconomicResource{} = to_res} = Domain.create(params, res_params) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(params, res_params) + evt = Domain.preload(evt, :to_resource_inventoried_as) + to_res = evt.to_resource_inventoried_as + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -1672,12 +1704,12 @@ describe "`create/2` with move:" do @tag :want_to_resource test "pass with `:to_resource_inventoried_as`", %{params: params} do - {:ok, res_before} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_before} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + res_before = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_before = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) - assert {:ok, %EconomicEvent{}} = Domain.create(params, nil) - {:ok, res_after} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - {:ok, to_res_after} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{}} = Domain.create(params) + res_after = EconomicResource.Domain.one!(params.resource_inventoried_as_id) + to_res_after = EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) assert res_after.accounting_quantity_has_numerical_value == res_before.accounting_quantity_has_numerical_value - params.resource_quantity.has_numerical_value @@ -1691,122 +1723,114 @@ describe "`create/2` with move:" do end test "fail when provider doesn't have accountability over the resource", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(primary_accountable_id: Factory.insert!(:agent).id) |> Repo.update!() + assert {:error, "you don't have accountability over resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when provider doesn't have custody over the resource", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(custodian_id: Factory.insert!(:agent).id) |> Repo.update!() + assert {:error, "you don't have custody over resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when provider doesn't have accountability over the to-resource", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(primary_accountable_id: Factory.insert!(:agent).id) |> Repo.update!() + assert {:error, "you don't have accountability over to-resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when provider doesn't have custody over the to-resource", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(custodian_id: Factory.insert!(:agent).id) |> Repo.update!() + assert {:error, "you don't have custody over to-resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_contained test "fail when the resource is a contained resource", %{params: params} do assert {:error, "you can't move a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end test "fail when event's unit and resource's unit differ", %{params: params} do params = update_in(params.resource_quantity.has_unit_id, fn _ -> Factory.insert!(:unit).id end) assert {:error, "the unit of resource-quantity must match with the unit of resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when event's unit and to-resource's unit differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(accounting_quantity_has_unit_id: Factory.insert!(:unit).id) |> Repo.update!() assert {:error, "the unit of resource-quantity must match with the unit of to-resource-inventoried-as"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when the resource is a container and accounting-quantity is non-positive", %{params: params} do err = "the move events need container resources to have positive accounting-quantity" - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res = EconomicResource.Domain.one!(params.resource_inventoried_as_id) Changeset.change(res, accounting_quantity_has_numerical_value: 0.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) Changeset.change(res, accounting_quantity_has_numerical_value: -1.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) end @tag :want_container test "fail when the resource is a container and onhand-quantity is non-positive", %{params: params} do err = "the move events need container resources to have positive onhand-quantity" - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) + res = EconomicResource.Domain.one!(params.resource_inventoried_as_id) Changeset.change(res, onhand_quantity_has_numerical_value: 0.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) Changeset.change(res, onhand_quantity_has_numerical_value: -1.0) |> Repo.update!() - assert {:error, ^err} = Domain.create(params, nil) + assert {:error, ^err} = Domain.create(params) end @tag :want_container test "fail when event's quantity value and resource's accounting-quantity value differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(accounting_quantity_has_numerical_value: params.resource_quantity.has_numerical_value + 1) |> Repo.update!() + assert {:error, "the move events need to fully move the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container test "fail when event's quantity value and resource's onhnad-quantity value differ", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.resource_inventoried_as_id) |> Changeset.change(onhand_quantity_has_numerical_value: params.resource_quantity.has_numerical_value + 1) |> Repo.update!() + assert {:error, "the move events need to fully move the resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_container @tag :want_to_resource test "fail when transfering a container resource into another resource", %{params: params} do assert {:error, "you can't move a container resource into another resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -1821,18 +1845,18 @@ describe "`create/2` with move:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) - # TODO: use combine-separate when implemented instead - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) + assert {:ok, %EconomicEvent{resource_inventoried_as_id: tmp_res_id}} = + Domain.create(raise_params, %{name: Factory.str("name")}) - res - |> Changeset.change(contained_in_id: tmp_res.id) + # TODO: use combine-separate when implemented instead + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) + |> Changeset.change(contained_in_id: tmp_res_id) |> Repo.update!() assert {:error, "you can't move into a contained resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource @@ -1847,27 +1871,28 @@ describe "`create/2` with move:" do has_unit_id: Factory.insert!(:unit).id, has_numerical_value: Factory.float(), }, - has_end: DateTime.utc_now(), + has_end: Factory.now(), } - assert {:ok, _, tmp_res, _} = Domain.create(raise_params, %{name: Factory.str("name")}) + assert {:ok, %EconomicEvent{} = evt} = + Domain.create(raise_params, %{name: Factory.str("name")}) + evt = Domain.preload(evt, :resource_inventoried_as) + tmp_res = evt.resource_inventoried_as # TODO: use combine-separate when implemented instead Changeset.change(tmp_res, contained_in_id: params.to_resource_inventoried_as_id) - |> Repo.update() + |> Repo.update!() assert {:error, "you can't move into a container resource"} = - Domain.create(params, nil) + Domain.create(params) end @tag :want_to_resource test "fail when resoure and to-resource don't conform to the same spec", %{params: params} do - {:ok, res} = EconomicResource.Domain.one(params.to_resource_inventoried_as_id) - - res + EconomicResource.Domain.one!(params.to_resource_inventoried_as_id) |> Changeset.change(conforms_to_id: Factory.insert!(:resource_specification).id) |> Repo.update!() assert {:error, "the resources must conform to the same specification"} = - Domain.create(params, nil) + Domain.create(params) end end diff --git a/test/vf/economic_resource/domain.test.exs b/test/vf/economic_resource/domain.test.exs @@ -78,8 +78,7 @@ end test "classifications/0 returns list of unique `classified_as` values" do Enum.each(1..10, fn _ -> Factory.insert!(:economic_resource) end) - {:ok, %{edges: edges}} = Domain.all() - left = Enum.flat_map(edges, & &1.node.classified_as) + left = Enum.flat_map(Domain.all!(), & &1.classified_as) right = Domain.classifications() assert [] = left -- right end diff --git a/test/vf/event_or_commitment.test.exs b/test/vf/event_or_commitment.test.exs @@ -33,7 +33,7 @@ describe "create EventOrCommitment" do test "with both event and commitment", %{params: params} do assert {:error, %Changeset{errors: errs}} = params - |> EventOrCommitment.chgset() + |> EventOrCommitment.changeset() |> Repo.insert() assert {:ok, _} = Keyword.fetch(errs, :event_id) @@ -45,7 +45,7 @@ describe "create EventOrCommitment" do assert {:ok, %EventOrCommitment{} = evt_comm} = params |> Map.delete(:commitment_id) - |> EventOrCommitment.chgset() + |> EventOrCommitment.changeset() |> Repo.insert() assert evt_comm.event_id == params.event_id @@ -57,7 +57,7 @@ describe "create EventOrCommitment" do assert {:ok, %EventOrCommitment{} = evt_comm} = params |> Map.delete(:event_id) - |> EventOrCommitment.chgset() + |> EventOrCommitment.changeset() |> Repo.insert() assert evt_comm.event_id == nil @@ -71,7 +71,7 @@ describe "update EventOrCommitment" do assert {:error, %Changeset{errors: errs}} = :event_or_commitment |> Factory.insert!(event: Factory.build(:economic_event), commitment: nil) - |> EventOrCommitment.chgset(params) + |> EventOrCommitment.changeset(params) |> Repo.update() assert {:ok, _} = Keyword.fetch(errs, :commitment_id) @@ -79,7 +79,7 @@ describe "update EventOrCommitment" do assert {:error, %Changeset{errors: errs}} = :event_or_commitment |> Factory.insert!(event: nil, commitment: Factory.build(:commitment)) - |> EventOrCommitment.chgset(params) + |> EventOrCommitment.changeset(params) |> Repo.update() assert {:ok, _} = Keyword.fetch(errs, :event_id) @@ -90,7 +90,7 @@ describe "update EventOrCommitment" do assert {:ok, %EventOrCommitment{} = evt_comm} = :event_or_commitment |> Factory.insert!(event: Factory.build(:economic_event), commitment: nil) - |> EventOrCommitment.chgset( + |> EventOrCommitment.changeset( Map.delete(params, :commitment_id) ) |> Repo.update() @@ -104,7 +104,7 @@ describe "update EventOrCommitment" do assert {:ok, %EventOrCommitment{} = evt_comm} = :event_or_commitment |> Factory.insert!(event: nil, commitment: Factory.build(:commitment)) - |> EventOrCommitment.chgset( + |> EventOrCommitment.changeset( Map.delete(params, :event_id) ) |> Repo.update() diff --git a/test/vf/fulfillment.test.exs b/test/vf/fulfillment.test.exs @@ -40,7 +40,7 @@ end test "create Fulfillment", %{params: params} do assert {:ok, %Fulfillment{} = fulf} = params - |> Fulfillment.chgset() + |> Fulfillment.changeset() |> Repo.insert() assert fulf.note == params.note @@ -57,7 +57,7 @@ test "update Fulfillment", %{params: params} do assert {:ok, %Fulfillment{} = fulf} = :fulfillment |> Factory.insert!() - |> Fulfillment.chgset(params) + |> Fulfillment.changeset(params) |> Repo.update() assert fulf.note == params.note diff --git a/test/vf/measure.test.exs b/test/vf/measure.test.exs @@ -33,13 +33,13 @@ embedded_schema do field :quantity_has_numerical_value, :float end -def chgset(params) do +def changeset(params) do %__MODULE__{} |> common(params) |> Map.put(:action, :insert) end -def chgset(schema, params) do +def changeset(schema, params) do schema |> common(params) |> Map.put(:action, :update) @@ -67,50 +67,50 @@ end test "insert", %{params: params} do # no changes when params is `%{}` - assert %Changeset{valid?: true, changes: %{}} = Dummy.chgset(%{}) + assert %Changeset{valid?: true, changes: %{}} = Dummy.changeset(%{}) # fields are nil when `:quantity` is `nil` - assert %Changeset{valid?: true, changes: chgs} = Dummy.chgset(%{quantity: nil}) + assert %Changeset{valid?: true, changes: chgs} = Dummy.changeset(%{quantity: nil}) assert chgs.quantity_has_unit_id == nil assert chgs.quantity_has_numerical_value == nil # fields are properly set when `:quantity` is properly set - assert %Changeset{valid?: true, changes: chgs} = Dummy.chgset(%{quantity: params}) + assert %Changeset{valid?: true, changes: chgs} = Dummy.changeset(%{quantity: params}) assert chgs.quantity_has_unit_id == params.has_unit_id assert chgs.quantity_has_numerical_value == params.has_numerical_value # `:has_numerical_value` must be positive assert %Changeset{valid?: false, errors: errs} - = Dummy.chgset(%{quantity: Map.put(params, :has_numerical_value, 0)}) + = Dummy.changeset(%{quantity: Map.put(params, :has_numerical_value, 0)}) assert length(Keyword.get_values(errs, :quantity)) == 1 assert %Changeset{valid?: false, errors: errs} - = Dummy.chgset(%{quantity: Map.put(params, :has_numerical_value, -1)}) + = Dummy.changeset(%{quantity: Map.put(params, :has_numerical_value, -1)}) assert length(Keyword.get_values(errs, :quantity)) == 1 # when no fields are provided, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(%{quantity: %{}}) + = Dummy.changeset(%{quantity: %{}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) # when `:has_unit_id` is `nil`, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(%{quantity: %{has_unit_id: nil}}) + = Dummy.changeset(%{quantity: %{has_unit_id: nil}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) # when `:has_numerical_value` is `nil`, no fields are set assert %Changeset{valid?: false, changes: %{quantity: _}, errors: errs} - = Dummy.chgset(%{quantity: %{has_numerical_value: nil}}) + = Dummy.changeset(%{quantity: %{has_numerical_value: nil}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) # when both fields are `nil`, no fields are set assert %Changeset{valid?: false, changes: %{quantity: _}, errors: errs} - = Dummy.chgset(%{quantity: %{has_unit_id: nil, has_numerical_value: nil}}) + = Dummy.changeset(%{quantity: %{has_unit_id: nil, has_numerical_value: nil}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) @@ -118,52 +118,52 @@ end test "update", %{params: params, inserted: schema} do # no changes when params is `%{}` - assert %Changeset{valid?: true, changes: %{}} = Dummy.chgset(schema, %{}) + assert %Changeset{valid?: true, changes: %{}} = Dummy.changeset(schema, %{}) # fields are nil when `:quantity` is `nil` assert %Changeset{valid?: true, changes: %{ quantity_has_unit_id: nil, quantity_has_numerical_value: nil, - }} = Dummy.chgset(schema, %{quantity: nil}) + }} = Dummy.changeset(schema, %{quantity: nil}) # fields are changed when `:quantity` is properly set assert %Changeset{valid?: true, changes: chgs} - = Dummy.chgset(schema, %{quantity: params}) + = Dummy.changeset(schema, %{quantity: params}) assert chgs.quantity_has_unit_id == params.has_unit_id assert chgs.quantity_has_numerical_value == params.has_numerical_value # `:has_numerical_value` must be positive assert %Changeset{valid?: false, errors: errs} - = Dummy.chgset(schema, %{quantity: Map.put(params, :has_numerical_value, 0)}) + = Dummy.changeset(schema, %{quantity: Map.put(params, :has_numerical_value, 0)}) assert length(Keyword.get_values(errs, :quantity)) == 1 assert %Changeset{valid?: false, errors: errs} - = Dummy.chgset(schema, %{quantity: Map.put(params, :has_numerical_value, -1)}) + = Dummy.changeset(schema, %{quantity: Map.put(params, :has_numerical_value, -1)}) assert length(Keyword.get_values(errs, :quantity)) == 1 # when no fields are provided, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(schema, %{quantity: %{}}) + = Dummy.changeset(schema, %{quantity: %{}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) # when `:has_unit_id` is `nil`, no fields are set assert %Changeset{valid?: false, changes: chgs, errors: errs} - = Dummy.chgset(schema, %{quantity: %{has_unit_id: nil}}) + = Dummy.changeset(schema, %{quantity: %{has_unit_id: nil}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) # when `:has_numerical_value` is `nil`, no fields are set assert %Changeset{valid?: false, changes: %{quantity: _}, errors: errs} - = Dummy.chgset(schema, %{quantity: %{has_numerical_value: nil}}) + = Dummy.changeset(schema, %{quantity: %{has_numerical_value: nil}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) # when both fields are `nil`, no fields are set assert %Changeset{valid?: false, changes: %{quantity: _}, errors: errs} - = Dummy.chgset(schema, %{quantity: %{has_unit_id: nil, has_numerical_value: nil}}) + = Dummy.changeset(schema, %{quantity: %{has_unit_id: nil, has_numerical_value: nil}}) assert length(Keyword.get_values(errs, :quantity)) == 2 refute Map.has_key?(chgs, :quantity_has_unit_id) or Map.has_key?(chgs, :quantity_has_numerical_value) diff --git a/test/vf/proposal.test.exs b/test/vf/proposal.test.exs @@ -34,7 +34,7 @@ end test "create Proposal", %{params: params} do assert {:ok, %Proposal{} = prop} = params - |> Proposal.chgset() + |> Proposal.changeset() |> Repo.insert() assert prop.name == params.name @@ -49,7 +49,7 @@ test "update Proposal", %{params: params} do assert {:ok, %Proposal{} = prop} = :proposal |> Factory.insert!() - |> Proposal.chgset(params) + |> Proposal.changeset(params) |> Repo.update() assert prop.name == params.name diff --git a/test/vf/proposed_intent.test.exs b/test/vf/proposed_intent.test.exs @@ -31,7 +31,7 @@ end test "create ProposedIntent", %{params: params} do assert {:ok, %ProposedIntent{} = prop_int} = params - |> ProposedIntent.chgset() + |> ProposedIntent.changeset() |> Repo.insert() assert prop_int.reciprocal == params.reciprocal @@ -43,7 +43,7 @@ test "update ProposedIntent", %{params: params} do assert {:ok, %ProposedIntent{} = prop_int} = :proposed_intent |> Factory.insert!() - |> ProposedIntent.chgset(params) + |> ProposedIntent.changeset(params) |> Repo.update() assert prop_int.reciprocal == params.reciprocal diff --git a/test/vf/proposed_to.test.exs b/test/vf/proposed_to.test.exs @@ -30,7 +30,7 @@ end test "create ProposedTo", %{params: params} do assert {:ok, %ProposedTo{} = prop_to} = params - |> ProposedTo.chgset() + |> ProposedTo.changeset() |> Repo.insert() assert prop_to.proposed_to_id == params.proposed_to_id @@ -41,7 +41,7 @@ test "update ProposedTo", %{params: params} do assert {:ok, %ProposedTo{} = prop_to} = :proposed_to |> Factory.insert!() - |> ProposedTo.chgset(params) + |> ProposedTo.changeset(params) |> Repo.update() assert prop_to.proposed_to_id == params.proposed_to_id diff --git a/test/vf/recipe_flow/domain.test.exs b/test/vf/recipe_flow/domain.test.exs @@ -105,15 +105,17 @@ describe "create/1" do assert new.effort_quantity_has_numerical_value == params.effort_quantity.has_numerical_value end - test "with bad params (without :resource_qunatity and :effort_quantit): doesn't create a RecipeFlow", %{params: params} do + test "with bad params (without :resource_qunatity and :effort_quantity): doesn't create a RecipeFlow", %{params: params} do params = params |> Map.delete(:resource_quantity) |> Map.delete(:effort_quantity) assert {:error, %Changeset{errors: errs}} = Domain.create(params) - assert Keyword.has_key?(errs, :resource_quantity) - assert Keyword.has_key?(errs, :effort_quantity) + assert Keyword.has_key?(errs, :resource_quantity_has_numerical_value) + assert Keyword.has_key?(errs, :resource_quantity_has_unit_id) + assert Keyword.has_key?(errs, :effort_quantity_has_numerical_value) + assert Keyword.has_key?(errs, :effort_quantity_has_unit_id) end test "with bad params: doesn't create a RecipeFlow" do diff --git a/test/vf/satisfaction.test.exs b/test/vf/satisfaction.test.exs @@ -40,7 +40,7 @@ end test "create Satisfaction", %{params: params} do assert {:ok, %Satisfaction{} = satis} = params - |> Satisfaction.chgset() + |> Satisfaction.changeset() |> Repo.insert() assert satis.satisfied_by_id == params.satisfied_by_id @@ -56,7 +56,7 @@ end test "update Satisfaction", %{params: params} do assert {:ok, %Satisfaction{} = satis} = Factory.insert!(:satisfaction) - |> Satisfaction.chgset(params) + |> Satisfaction.changeset(params) |> Repo.update() assert satis.satisfied_by_id == params.satisfied_by_id diff --git a/test/vf/settlement.test.exs b/test/vf/settlement.test.exs @@ -40,7 +40,7 @@ end test "create Settlement", %{params: params} do assert {:ok, %Settlement{} = settl} = params - |> Settlement.chgset() + |> Settlement.changeset() |> Repo.insert() assert settl.note == params.note @@ -57,7 +57,7 @@ test "update Settlement", %{params: params} do assert {:ok, %Settlement{} = settl} = :settlement |> Factory.insert!() - |> Settlement.chgset(params) + |> Settlement.changeset(params) |> Repo.update() assert settl.note == params.note diff --git a/test/vf/validate.test.exs b/test/vf/validate.test.exs @@ -1,245 +0,0 @@ -# Zenflows is designed to implement the Valueflows vocabulary, -# written and maintained by srfsh <info@dyne.org>. -# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. - -defmodule ZenflowsTest.VF.Validate do -use ExUnit.Case, async: true - -alias Ecto.Changeset -alias Zenflows.VF.Validate - -@spec key_chgset(map()) :: Changeset.t() -defp key_chgset(changes) do - Changeset.change({%{}, %{key: :string}}, changes) -end - -@spec name_chgset(map()) :: Changeset.t() -defp name_chgset(changes) do - Changeset.change({%{}, %{name: :string}}, changes) -end - -@spec note_chgset(map()) :: Changeset.t() -defp note_chgset(changes) do - Changeset.change({%{}, %{note: :string}}, changes) -end - -@spec img_chgset(map()) :: Changeset.t() -defp img_chgset(changes) do - Changeset.change({%{}, %{img: :string}}, changes) -end - -@spec uri_chgset(map()) :: Changeset.t() -defp uri_chgset(changes) do - Changeset.change({%{}, %{uri: :string}}, changes) -end - -@spec class_chgset(map()) :: Changeset.t() -defp class_chgset(changes) do - Changeset.change({%{}, %{list: {:array, :string}}}, changes) -end - -describe "key/2" do - test "with too short param" do - assert %Changeset{errors: errs} = - %{key: String.duplicate("a", 15)} - |> key_chgset() - |> Validate.key(:key) - - assert {:ok, _} = Keyword.fetch(errs, :key) - end - - test "with too long param" do - assert %Changeset{errors: errs} = - %{key: String.duplicate("a", 2048 + 1)} - |> key_chgset() - |> Validate.key(:key) - - assert {:ok, _} = Keyword.fetch(errs, :key) - end - - test "with the right size param" do - assert %Changeset{errors: errs} = - %{key: String.duplicate("a", 16)} - |> key_chgset() - |> Validate.key(:key) - - assert :error = Keyword.fetch(errs, :key) - end -end - -describe "name/2" do - test "with too short param" do - assert %Changeset{errors: errs} = - %{name: ""} - |> name_chgset() - |> Validate.name(:name) - - assert {:ok, _} = Keyword.fetch(errs, :name) - end - - test "with too long param" do - assert %Changeset{errors: errs} = - %{name: String.duplicate("a", 256 + 1)} - |> name_chgset() - |> Validate.name(:name) - - assert {:ok, _} = Keyword.fetch(errs, :name) - end - - test "with the right size param" do - assert %Changeset{errors: errs} = - %{name: "aa"} - |> name_chgset() - |> Validate.name(:name) - - assert :error = Keyword.fetch(errs, :name) - end -end - -describe "note/2" do - test "with too short param" do - assert %Changeset{errors: errs} = - %{note: ""} - |> note_chgset() - |> Validate.note(:note) - - assert {:ok, _} = Keyword.fetch(errs, :note) - end - - test "with too long param" do - assert %Changeset{errors: errs} = - %{note: String.duplicate("a", 2048 + 1)} - |> note_chgset() - |> Validate.note(:note) - - assert {:ok, _} = Keyword.fetch(errs, :note) - end - - test "with the right size param" do - assert %Changeset{errors: errs} = - %{note: "aa"} - |> note_chgset() - |> Validate.note(:note) - - assert :error = Keyword.fetch(errs, :note) - end -end - -describe "img/2" do - test "with too short param" do - assert %Changeset{errors: errs} = - %{img: ""} - |> img_chgset() - |> Validate.img(:img) - - assert {:ok, _} = Keyword.fetch(errs, :img) - end - - test "with too long param" do - assert %Changeset{errors: errs} = - %{img: String.duplicate("a", 25 * 1024 * 1024 + 1)} - |> img_chgset() - |> Validate.img(:img) - - assert {:ok, _} = Keyword.fetch(errs, :img) - end - - test "with the right size param" do - assert %Changeset{errors: errs} = - %{img: String.duplicate("a", 1024)} - |> img_chgset() - |> Validate.img(:img) - - assert :error = Keyword.fetch(errs, :img) - end -end - -describe "uri/2" do - test "with too short param" do - assert %Changeset{errors: errs} = - %{uri: ""} - |> uri_chgset() - |> Validate.uri(:uri) - - assert {:ok, _} = Keyword.fetch(errs, :uri) - end - - test "with too long param" do - assert %Changeset{errors: errs} = - %{uri: String.duplicate("a", 512 + 1)} - |> uri_chgset() - |> Validate.uri(:uri) - - assert {:ok, _} = Keyword.fetch(errs, :uri) - end - - test "with the right size param" do - assert %Changeset{errors: errs} = - %{uri: "https://example.test/example.jpg"} - |> uri_chgset() - |> Validate.uri(:uri) - - assert :error = Keyword.fetch(errs, :uri) - end -end - -describe "class/2" do - test "with too few items" do - assert %Changeset{errors: errs} = - %{list: []} - |> class_chgset() - |> Validate.class(:list) - - assert {:ok, _} = Keyword.fetch(errs, :list) - end - - test "with too many items" do - assert %Changeset{errors: errs} = - %{list: Enum.map(0..127 + 1, &("uri #{&1}"))} - |> class_chgset() - |> Validate.class(:list) - - assert {:ok, _} = Keyword.fetch(errs, :list) - end - - test "with one of the items too short" do - assert %Changeset{errors: errs} = - %{list: Enum.map(0..64, &("uri #{&1}")) ++ [""]} - |> class_chgset() - |> Validate.class(:list) - - assert {:ok, _} = Keyword.fetch(errs, :list) - end - - test "with one of the items too long" do - assert %Changeset{errors: errs} = - %{list: Enum.map(0..64, &("uri #{&1}")) ++ [String.duplicate("a", 513)]} - |> class_chgset() - |> Validate.class(:list) - - assert {:ok, _} = Keyword.fetch(errs, :list) - end - - test "with everthing just right" do - assert %Changeset{errors: errs} = - %{list: ["aaa"]} - |> class_chgset() - |> Validate.class(:list) - - assert :error = Keyword.fetch(errs, :list) - end -end -end