commit e5b306f9cb2a3a0ea211a3a251a18107b2688527
parent 761bdaabe3cbd6495289bf10054b892647a5c65a
Author: srfsh <dev@srf.sh>
Date: Tue, 23 Aug 2022 11:03:10 +0300
Zenflows{Test,}.VF.RecipeResource: add paging support and small improvements
Diffstat:
5 files changed, 140 insertions(+), 124 deletions(-)
diff --git a/src/zenflows/vf/recipe_resource/domain.ex b/src/zenflows/vf/recipe_resource/domain.ex
@@ -20,35 +20,48 @@ defmodule Zenflows.VF.RecipeResource.Domain do
alias Ecto.Multi
alias Zenflows.DB.Repo
+alias Zenflows.GQL.Paging
alias Zenflows.VF.RecipeResource
@typep repo() :: Ecto.Repo.t()
@typep chgset() :: Ecto.Changeset.t()
-@typep changes() :: Ecto.Multi.changes()
@typep id() :: Zenflows.DB.Schema.id()
@typep params() :: Zenflows.DB.Schema.params()
-@spec by_id(repo(), id()) :: RecipeResource.t() | nil
-def by_id(repo \\ Repo, id) do
- repo.get(RecipeResource, id)
+@spec one(repo(), 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)
+def one(repo, clauses) do
+ case repo.get_by(RecipeResource, clauses) do
+ nil -> {:error, "not found"}
+ found -> {:ok, found}
+ end
+end
+
+@spec all(Paging.params()) :: Paging.result(RecipeResource.t())
+def all(params) do
+ Paging.page(RecipeResource, params)
end
@spec create(params()) :: {:ok, RecipeResource.t()} | {:error, chgset()}
def create(params) do
Multi.new()
- |> Multi.insert(:rec_res, RecipeResource.chgset(params))
+ |> Multi.insert(:insert, RecipeResource.chgset(params))
|> Repo.transaction()
|> case do
- {:ok, %{rec_res: rr}} -> {:ok, rr}
+ {:ok, %{insert: rr}} -> {:ok, rr}
{:error, _, cset, _} -> {:error, cset}
end
end
-@spec update(id(), params()) :: {:ok, RecipeResource.t()} | {:error, chgset()}
+@spec update(id(), params())
+ :: {:ok, RecipeResource.t()} | {:error, String.t() | chgset()}
def update(id, params) do
Multi.new()
- |> Multi.run(:get, multi_get(id))
- |> Multi.update(:update, &RecipeResource.chgset(&1.get, params))
+ |> Multi.put(:id, id)
+ |> Multi.run(:one, &one/2)
+ |> Multi.update(:update, &RecipeResource.chgset(&1.one, params))
|> Repo.transaction()
|> case do
{:ok, %{update: rr}} -> {:ok, rr}
@@ -56,11 +69,13 @@ def update(id, params) do
end
end
-@spec delete(id()) :: {:ok, RecipeResource.t()} | {:error, chgset()}
+@spec delete(id())
+ :: {:ok, RecipeResource.t()} | {:error, String.t() | chgset()}
def delete(id) do
Multi.new()
- |> Multi.run(:get, multi_get(id))
- |> Multi.delete(:delete, &(&1.get))
+ |> Multi.put(:id, id)
+ |> Multi.run(:one, &one/2)
+ |> Multi.delete(:delete, & &1.one)
|> Repo.transaction()
|> case do
{:ok, %{delete: rr}} -> {:ok, rr}
@@ -81,16 +96,4 @@ end
def preload(rec_res, :resource_conforms_to) do
Repo.preload(rec_res, :resource_conforms_to)
end
-
-# Returns a RecipeResource in ok-err tuple from given ID. Used inside
-# Ecto.Multi.run/5 to get a record in transaction.
-@spec multi_get(id()) :: (repo(), changes() -> {:ok, RecipeResource.t()} | {:error, String.t()})
-defp multi_get(id) do
- fn repo, _ ->
- case by_id(repo, id) do
- nil -> {:error, "not found"}
- rr -> {:ok, rr}
- end
- end
-end
end
diff --git a/src/zenflows/vf/recipe_resource/resolv.ex b/src/zenflows/vf/recipe_resource/resolv.ex
@@ -18,44 +18,45 @@
defmodule Zenflows.VF.RecipeResource.Resolv do
@moduledoc "Resolvers of RecipeResources."
-alias Zenflows.VF.{
- RecipeResource,
- RecipeResource.Domain,
-}
+alias Zenflows.VF.RecipeResource.Domain
-def recipe_resource(%{id: id}, _info) do
- {:ok, Domain.by_id(id)}
+def recipe_resource(params, _) do
+ Domain.one(params)
end
-def create_recipe_resource(%{recipe_resource: params}, _info) do
+def recipe_resources(params, _) do
+ Domain.all(params)
+end
+
+def create_recipe_resource(%{recipe_resource: params}, _) do
with {:ok, proc_spec} <- Domain.create(params) do
{:ok, %{recipe_resource: proc_spec}}
end
end
-def update_recipe_resource(%{recipe_resource: %{id: id} = params}, _info) do
+def update_recipe_resource(%{recipe_resource: %{id: id} = params}, _) do
with {:ok, proc_spec} <- Domain.update(id, params) do
{:ok, %{recipe_resource: proc_spec}}
end
end
-def delete_recipe_resource(%{id: id}, _info) do
+def delete_recipe_resource(%{id: id}, _) do
with {:ok, _} <- Domain.delete(id) do
{:ok, true}
end
end
-def unit_of_resource(%RecipeResource{} = rec_res, _args, _info) do
+def unit_of_resource(rec_res, _, _) do
rec_res = Domain.preload(rec_res, :unit_of_resource)
{:ok, rec_res.unit_of_resource}
end
-def unit_of_effort(%RecipeResource{} = rec_res, _args, _info) do
+def unit_of_effort(rec_res, _, _) do
rec_res = Domain.preload(rec_res, :unit_of_effort)
{:ok, rec_res.unit_of_effort}
end
-def resource_conforms_to(%RecipeResource{} = rec_res, _args, _info) do
+def resource_conforms_to(rec_res, _, _) do
rec_res = Domain.preload(rec_res, :resource_conforms_to)
{:ok, rec_res.resource_conforms_to}
end
diff --git a/src/zenflows/vf/recipe_resource/type.ex b/src/zenflows/vf/recipe_resource/type.ex
@@ -32,16 +32,19 @@ The base64-encoded image binary relevant to the entity, such as a photo, diagram
@unit_of_resource """
The unit of inventory used for this resource in the recipe.
"""
+@unit_of_resource_id "(`Unit`) #{@unit_of_resource}"
@unit_of_effort """
The unit used for use action on this resource or work action in the
recipe.
"""
+@unit_of_effort_id "(`Unit`) #{@unit_of_resource}"
@note "A textual description or comment."
@resource_conforms_to """
The primary resource specification or definition of an existing or
potential economic resource. A resource will have only one, as this
specifies exactly what the resource is.
"""
+@resource_conforms_to_id "(`ResourceSpecification`) #{@resource_conforms_to}"
@resource_classified_as """
References a concept in a common taxonomy or other classification scheme
for purposes of categorization or grouping.
@@ -86,21 +89,14 @@ object :recipe_resource do
field :substitutable, non_null(:boolean)
end
-object :recipe_resource_response do
- field :recipe_resource, non_null(:recipe_resource)
-end
-
input_object :recipe_resource_create_params do
@desc @name
field :name, non_null(:string)
- # TODO: When
- # https://github.com/absinthe-graphql/absinthe/issues/1126 results,
- # apply the correct changes if any.
- @desc "(`Unit`) " <> @unit_of_resource
+ @desc @unit_of_resource_id
field :unit_of_resource_id, :id, name: "unit_of_resource"
- @desc "(`Unit`) " <> @unit_of_effort
+ @desc @unit_of_effort_id
field :unit_of_effort_id, :id, name: "unit_of_effort"
@desc @image
@@ -109,7 +105,7 @@ input_object :recipe_resource_create_params do
@desc @note
field :note, :string
- @desc "(`ResourceSpecification`) " <> @resource_conforms_to
+ @desc @resource_conforms_to_id
field :resource_conforms_to_id, :id, name: "resource_conforms_to"
@desc @resource_classified_as
@@ -125,10 +121,10 @@ input_object :recipe_resource_update_params do
@desc @name
field :name, :string
- @desc "(`Unit`) " <> @unit_of_resource
+ @desc @unit_of_resource_id
field :unit_of_resource_id, :id, name: "unit_of_resource"
- @desc "(`Unit`) " <> @unit_of_effort
+ @desc @unit_of_effort_id
field :unit_of_effort_id, :id, name: "unit_of_effort"
@desc @image
@@ -137,7 +133,7 @@ input_object :recipe_resource_update_params do
@desc @note
field :note, :string
- @desc "(`ResourceSpecification`) " <> @resource_conforms_to
+ @desc @resource_conforms_to_id
field :resource_conforms_to_id, :id, name: "resource_conforms_to"
@desc @resource_classified_as
@@ -147,13 +143,33 @@ input_object :recipe_resource_update_params do
field :substitutable, :boolean
end
+object :recipe_resource_response do
+ field :recipe_resource, non_null(:recipe_resource)
+end
+
+object :recipe_resource_edge do
+ field :cursor, non_null(:id)
+ field :node, non_null(:recipe_resource)
+end
+
+object :recipe_resource_connection do
+ field :page_info, non_null(:page_info)
+ field :edges, non_null(list_of(non_null(:recipe_resource_edge)))
+end
+
object :query_recipe_resource do
field :recipe_resource, :recipe_resource do
arg :id, non_null(:id)
resolve &Resolv.recipe_resource/2
end
- #recipeResources(start: ID, limit: Int): [RecipeResource!]
+ field :recipe_resources, :recipe_resource_connection do
+ arg :first, :integer
+ arg :after, :id
+ arg :last, :integer
+ arg :before, :id
+ resolve &Resolv.recipe_resources/2
+ end
end
object :mutation_recipe_resource do
diff --git a/test/vf/recipe_resource/domain.test.exs b/test/vf/recipe_resource/domain.test.exs
@@ -37,37 +37,41 @@ setup do
note: Factory.uniq("note"),
image: Factory.img(),
},
- recipe_resource: Factory.insert!(:recipe_resource),
+ inserted: Factory.insert!(:recipe_resource),
}
end
-test "by_id/1 returns a RecipeResource", %{recipe_resource: rec_res} do
- assert %RecipeResource{} = Domain.by_id(rec_res.id)
+describe "one/1" do
+ test "with good id: finds the RecipeResource", %{inserted: %{id: id}} do
+ assert {:ok, %RecipeResource{}} = Domain.one(id)
+ end
+
+ test "with bad id: doesn't find the RecipeResource" do
+ assert {:error, "not found"} = Domain.one(Factory.id())
+ end
end
describe "create/1" do
- test "creates a RecipeResource with valid params", %{params: params} do
- assert {:ok, %RecipeResource{} = rec_res} = Domain.create(params)
-
- assert rec_res.name == params.name
- assert rec_res.resource_classified_as == params.resource_classified_as
- assert rec_res.unit_of_resource_id == params.unit_of_resource_id
- assert rec_res.unit_of_effort_id == params.unit_of_effort_id
- assert rec_res.resource_conforms_to_id == params.resource_conforms_to_id
- assert rec_res.substitutable == params.substitutable
- assert rec_res.note == params.note
- assert rec_res.image == params.image
+ test "with good params: creates a RecipeResource", %{params: params} do
+ assert {:ok, %RecipeResource{} = new} = Domain.create(params)
+ assert new.name == params.name
+ assert new.resource_classified_as == params.resource_classified_as
+ assert new.unit_of_resource_id == params.unit_of_resource_id
+ assert new.unit_of_effort_id == params.unit_of_effort_id
+ assert new.resource_conforms_to_id == params.resource_conforms_to_id
+ assert new.substitutable == params.substitutable
+ assert new.note == params.note
+ assert new.image == params.image
end
- test "doesn't create a RecipeResource with invalid params" do
+ test "with bad params: doesn't create a Process" do
assert {:error, %Changeset{}} = Domain.create(%{})
end
end
describe "update/2" do
- test "updates a RecipeResource with valid params", %{params: params, recipe_resource: old} do
+ test "with good params: updates the RecipeResource", %{params: params, inserted: old} do
assert {:ok, %RecipeResource{} = new} = Domain.update(old.id, params)
-
assert new.name == params.name
assert new.resource_classified_as == params.resource_classified_as
assert new.unit_of_resource_id == params.unit_of_resource_id
@@ -78,9 +82,8 @@ describe "update/2" do
assert new.image == params.image
end
- test "doesn't update a RecipeResource", %{recipe_resource: old} do
+ test "with bad params: doesn't update the RecipeResource", %{inserted: old} do
assert {:ok, %RecipeResource{} = new} = Domain.update(old.id, %{})
-
assert new.name == old.name
assert new.resource_classified_as == old.resource_classified_as
assert new.unit_of_resource_id == old.unit_of_resource_id
@@ -92,19 +95,25 @@ describe "update/2" do
end
end
-test "delete/1 deletes a RecipeResource", %{recipe_resource: %{id: id}} do
- assert {:ok, %RecipeResource{id: ^id}} = Domain.delete(id)
- assert Domain.by_id(id) == nil
+describe "delete/1" do
+ test "with good id: deletes the RecipeResource", %{inserted: %{id: id}} do
+ assert {:ok, %RecipeResource{id: ^id}} = Domain.delete(id)
+ assert {:error, "not found"} = Domain.one(id)
+ end
+
+ test "with bad id: doesn't delete the RecipeResource" do
+ assert {:error, "not found"} = Domain.delete(Factory.id())
+ end
end
describe "preload/2" do
- test "preloads :unit_of_resource", %{recipe_resource: rec_res} do
+ test "preloads :unit_of_resource", %{inserted: rec_res} do
rec_res = Domain.preload(rec_res, :unit_of_resource)
assert unit_res = %Unit{} = rec_res.unit_of_resource
assert unit_res.id == rec_res.unit_of_resource_id
end
- test "preloads :unit_of_effort", %{recipe_resource: rec_res} do
+ test "preloads :unit_of_effort", %{inserted: rec_res} do
rec_res = Domain.preload(rec_res, :unit_of_effort)
assert unit_eff = %Unit{} = rec_res.unit_of_effort
assert unit_eff.id == rec_res.unit_of_effort_id
diff --git a/test/vf/recipe_resource/type.test.exs b/test/vf/recipe_resource/type.test.exs
@@ -30,58 +30,54 @@ setup do
"note" => Factory.uniq("note"),
"image" => Factory.img(),
},
- recipe_resource: Factory.insert!(:recipe_resource),
+ inserted: Factory.insert!(:recipe_resource),
}
end
+@frag """
+fragment recipeResource on RecipeResource {
+ id
+ name
+ resourceClassifiedAs
+ unitOfResource {id}
+ unitOfEffort {id}
+ resourceConformsTo {id}
+ substitutable
+ image
+ note
+}
+"""
+
describe "Query" do
- test "recipeResource()", %{recipe_resource: rec_res} do
+ test "recipeResource", %{inserted: new} do
assert %{data: %{"recipeResource" => data}} =
run!("""
+ #{@frag}
query ($id: ID!) {
- recipeResource(id: $id) {
- id
- name
- resourceClassifiedAs
- unitOfResource { id }
- unitOfEffort { id }
- resourceConformsTo { id }
- substitutable
- image
- note
- }
+ recipeResource(id: $id) {...recipeResource}
}
- """, vars: %{"id" => rec_res.id})
+ """, vars: %{"id" => new.id})
- assert data["id"] == rec_res.id
- assert data["name"] == rec_res.name
- assert data["resourceClassifiedAs"] == rec_res.resource_classified_as
- assert data["unitOfResource"]["id"] == rec_res.unit_of_resource_id
- assert data["unitOfEffort"]["id"] == rec_res.unit_of_effort_id
- assert data["resourceConformsTo"]["id"] == rec_res.resource_conforms_to_id
- assert data["note"] == rec_res.note
- assert data["substitutable"] == rec_res.substitutable
- assert data["image"] == rec_res.image
+ assert data["id"] == new.id
+ assert data["name"] == new.name
+ assert data["resourceClassifiedAs"] == new.resource_classified_as
+ assert data["unitOfResource"]["id"] == new.unit_of_resource_id
+ assert data["unitOfEffort"]["id"] == new.unit_of_effort_id
+ assert data["resourceConformsTo"]["id"] == new.resource_conforms_to_id
+ assert data["note"] == new.note
+ assert data["substitutable"] == new.substitutable
+ assert data["image"] == new.image
end
end
describe "Mutation" do
- test "createRecipeResource()", %{params: params} do
+ test "createRecipeResource", %{params: params} do
assert %{data: %{"createRecipeResource" => %{"recipeResource" => data}}} =
run!("""
+ #{@frag}
mutation ($recipeResource: RecipeResourceCreateParams!) {
createRecipeResource(recipeResource: $recipeResource) {
- recipeResource {
- id
- name
- image
- resourceClassifiedAs
- unitOfResource { id }
- unitOfEffort { id }
- resourceConformsTo { id }
- substitutable
- note
- }
+ recipeResource {...recipeResource}
}
}
""", vars: %{"recipeResource" => params})
@@ -94,27 +90,18 @@ describe "Mutation" do
assert data["resourceConformsTo"]["id"] == params["resourceConformsTo"]
end
- test "updateRecipeResource()", %{params: params, recipe_resource: rec_res} do
+ test "updateRecipeResource()", %{params: params, inserted: old} do
assert %{data: %{"updateRecipeResource" => %{"recipeResource" => data}}} =
run!("""
+ #{@frag}
mutation ($recipeResource: RecipeResourceUpdateParams!) {
updateRecipeResource(recipeResource: $recipeResource) {
- recipeResource {
- id
- name
- resourceClassifiedAs
- unitOfResource { id }
- unitOfEffort { id }
- resourceConformsTo { id }
- substitutable
- note
- image
- }
+ recipeResource {...recipeResource}
}
}
- """, vars: %{"recipeResource" => Map.put(params, "id", rec_res.id)})
+ """, vars: %{"recipeResource" => Map.put(params, "id", old.id)})
- assert data["id"] == rec_res.id
+ assert data["id"] == old.id
keys = ~w[name note image resourceClassifiedAs substitutable]
assert Map.take(data, keys) == Map.take(params, keys)
assert data["unitOfResource"]["id"] == params["unitOfResource"]
@@ -122,7 +109,7 @@ describe "Mutation" do
assert data["resourceConformsTo"]["id"] == params["resourceConformsTo"]
end
- test "deleteRecipeResource()", %{recipe_resource: %{id: id}} do
+ test "deleteRecipeResource()", %{inserted: %{id: id}} do
assert %{data: %{"deleteRecipeResource" => true}} =
run!("""
mutation ($id: ID!) {