diff --git a/README.md b/README.md index 6faf6da..31e30ab 100644 --- a/README.md +++ b/README.md @@ -443,13 +443,44 @@ Note: This tutorial was updated on macOS 11.6.3. import_types(ZeroPhoenixWeb.GraphQL.Schemas.Queries.Person) + alias ZeroPhoenix.Accounts + alias ZeroPhoenix.Repo + query do import_fields(:person_queries) end + + def context(ctx) do + loader = + Dataloader.new + |> Dataloader.add_source(Accounts, Accounts.datasource()) + + Map.put(ctx, :loader, loader) + end + + def plugins do + [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() + end + end + ``` + +22. update the `Accounts` context by adding the following after the `change_person` function: + + `lib/zero_phoenix/accounts.ex`: + + ```elixir + # Dataloader + + def datasource() do + Dataloader.Ecto.new(Repo, query: &query/2) + end + + def query(queryable, _) do + queryable end ``` -22. add our Person type which will be performing queries against: +23. add our Person type which will be performing queries against: `lib/zero_phoenix_web/graphql/types/person.ex`: @@ -457,38 +488,34 @@ Note: This tutorial was updated on macOS 11.6.3. defmodule ZeroPhoenixWeb.GraphQL.Types.Person do use Absinthe.Schema.Notation - import Ecto + import Absinthe.Resolution.Helpers, only: [dataloader: 1] - alias ZeroPhoenix.Repo + alias ZeroPhoenix.Accounts @desc "a person" object :person do @desc "unique identifier for the person" - field :id, non_null(:string) + field(:id, non_null(:string)) @desc "first name of a person" - field :first_name, non_null(:string) + field(:first_name, non_null(:string)) @desc "last name of a person" - field :last_name, non_null(:string) + field(:last_name, non_null(:string)) @desc "username of a person" - field :username, non_null(:string) + field(:username, non_null(:string)) @desc "email of a person" - field :email, non_null(:string) + field(:email, non_null(:string)) @desc "a list of friends for our person" - field :friends, list_of(:person) do - resolve fn _, %{source: person} -> - {:ok, Repo.all(assoc(person, :friends))} - end - end + field :friends, list_of(:person), resolve: dataloader(Accounts) end end ``` -23. add the `person_queries` object to contain all the queries for a person: +24. add the `person_queries` object to contain all the queries for a person: `lib/zero_phoenix_web/graphql/schemas/queries/person.ex`: @@ -506,7 +533,7 @@ Note: This tutorial was updated on macOS 11.6.3. end ``` -24. add the `PersonResolver` to fetch the individual fields of our person object: +25. add the `PersonResolver` to fetch the individual fields of our person object: `lib/zero_phoenix_web/graphql/resolvers/person_resolver.ex`: @@ -515,7 +542,7 @@ Note: This tutorial was updated on macOS 11.6.3. alias ZeroPhoenix.Accounts alias ZeroPhoenix.Accounts.Person - def find(_parent, %{id: id}, _info) do + def find(_parent, %{id: id}, _resolution) do case Accounts.get_person(id) do %Person{} = person -> {:ok, person} @@ -527,7 +554,7 @@ Note: This tutorial was updated on macOS 11.6.3. end ``` -25. add routes for our GraphQL API and GraphiQL browser endpoints: +26. add routes for our GraphQL API and GraphiQL browser endpoints: `lib/zero_phoenix_web/router.ex`: @@ -559,19 +586,19 @@ Note: This tutorial was updated on macOS 11.6.3. end ``` -26. start the server +27. start the server ```zsh mix phx.server ``` -27. navigate to our application within the browser +28. navigate to our application within the browser ```zsh open http://localhost:4000/graphiql ``` -28. enter the below GraphQL query on the left side of the browser window +29. enter the below GraphQL query on the left side of the browser window ```graphql { @@ -590,7 +617,7 @@ Note: This tutorial was updated on macOS 11.6.3. } ``` -29. run the GraphQL query +30. run the GraphQL query ```text Control + Enter diff --git a/lib/zero_phoenix/accounts.ex b/lib/zero_phoenix/accounts.ex index 4935529..b35d40e 100644 --- a/lib/zero_phoenix/accounts.ex +++ b/lib/zero_phoenix/accounts.ex @@ -21,6 +21,28 @@ defmodule ZeroPhoenix.Accounts do Repo.all(Person) end + @doc """ + Returns a list of people matching the given `criteria`. + + Example Criteria: + + [{:limit, 15}, {:order, :asc}] + """ + + def list_people(criteria) do + query = from p in Person + + Enum.reduce(criteria, query, fn + {:limit, limit}, query -> + from p in query, limit: ^limit + + {:order, order}, query -> + from p in query, order_by: [{^order, :id}] + end) + |> Repo.all + end + + @doc """ Gets a single person. @@ -124,4 +146,14 @@ defmodule ZeroPhoenix.Accounts do def change_person(%Person{} = person) do Person.changeset(person, %{}) end + + # Dataloader + + def datasource() do + Dataloader.Ecto.new(Repo, query: &query/2) + end + + def query(queryable, _) do + queryable + end end diff --git a/lib/zero_phoenix_web/graphql/resolvers/person_resolver.ex b/lib/zero_phoenix_web/graphql/resolvers/person_resolver.ex index a1f3a93..1e66d0f 100644 --- a/lib/zero_phoenix_web/graphql/resolvers/person_resolver.ex +++ b/lib/zero_phoenix_web/graphql/resolvers/person_resolver.ex @@ -1,9 +1,6 @@ defmodule ZeroPhoenixWeb.GraphQL.Resolvers.PersonResolver do - import Ecto - alias ZeroPhoenix.Accounts alias ZeroPhoenix.Accounts.Person - alias ZeroPhoenix.Repo def find(_parent, %{id: id}, _resolution) do case Accounts.get_person(id) do @@ -28,8 +25,4 @@ defmodule ZeroPhoenixWeb.GraphQL.Resolvers.PersonResolver do {:error, "Could not create person"} end end - - def friends(parent, _args, _resolution) do - {:ok, Repo.all(assoc(parent, :friends))} - end end diff --git a/lib/zero_phoenix_web/graphql/schema.ex b/lib/zero_phoenix_web/graphql/schema.ex index 2675d3e..1043ecb 100644 --- a/lib/zero_phoenix_web/graphql/schema.ex +++ b/lib/zero_phoenix_web/graphql/schema.ex @@ -2,9 +2,11 @@ defmodule ZeroPhoenixWeb.GraphQL.Schema do use Absinthe.Schema import_types(ZeroPhoenixWeb.GraphQL.Types.Person) - + import_types(ZeroPhoenixWeb.GraphQL.Schemas.Queries.Person) + alias ZeroPhoenix.Accounts + query do import_fields(:person_queries) end @@ -14,4 +16,16 @@ defmodule ZeroPhoenixWeb.GraphQL.Schema do mutation do import_fields(:person_mutations) end + + def context(ctx) do + loader = + Dataloader.new + |> Dataloader.add_source(Accounts, Accounts.datasource()) + + Map.put(ctx, :loader, loader) + end + + def plugins do + [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() + end end diff --git a/lib/zero_phoenix_web/graphql/types/person.ex b/lib/zero_phoenix_web/graphql/types/person.ex index 8a919d1..deabaeb 100644 --- a/lib/zero_phoenix_web/graphql/types/person.ex +++ b/lib/zero_phoenix_web/graphql/types/person.ex @@ -1,7 +1,9 @@ -defmodule ZeroPhoenixWeb.Graphql.Types.Person do +defmodule ZeroPhoenixWeb.GraphQL.Types.Person do use Absinthe.Schema.Notation - alias ZeroPhoenixWeb.Graphql.Resolvers + import Absinthe.Resolution.Helpers, only: [dataloader: 1] + + alias ZeroPhoenix.Accounts @desc "a person" object :person do @@ -21,9 +23,7 @@ defmodule ZeroPhoenixWeb.Graphql.Types.Person do field(:email, non_null(:string)) @desc "a list of friends for our person" - field :friends, list_of(:person) do - resolve &Resolvers.PersonResolver.friends/3 - end + field :friends, list_of(:person), resolve: dataloader(Accounts) end @desc "a person input" diff --git a/mix.exs b/mix.exs index ff29449..3373539 100644 --- a/mix.exs +++ b/mix.exs @@ -47,7 +47,8 @@ defmodule ZeroPhoenix.Mixfile do {:plug_cowboy, "~> 2.5.2"}, {:absinthe, "~> 1.7.0"}, {:absinthe_plug, "~> 1.5.8"}, - {:cors_plug, "~> 2.0.3"} + {:cors_plug, "~> 2.0.3"}, + {:dataloader, "~> 1.0.10"} ] end diff --git a/priv/repo/dataloader_example.exs b/priv/repo/dataloader_example.exs index 0d471ed..dbdd633 100644 --- a/priv/repo/dataloader_example.exs +++ b/priv/repo/dataloader_example.exs @@ -1,6 +1,6 @@ alias ZeroPhoenix.Accounts -# Fetch 3 places we want to get the bookings for: +# Fetch 3 people we want to get the friends for: [person1, person2, person3] = Accounts.list_people([limit: 3]) @@ -18,7 +18,7 @@ source = Dataloader.Ecto.new(ZeroPhoenix.Repo) loader = loader |> Dataloader.add_source(Accounts, source) -# Create a batch of bookings to be loaded (does not query the database): +# Create a batch of friends to be loaded (does not query the database): loader = loader @@ -30,9 +30,9 @@ loader = loader = loader |> Dataloader.run -# 🔥 This runs one Ecto query to fetch all the bookings for all the places! +# 🔥 This runs one Ecto query to fetch all the friends for all the people! -# Now you can get the bookings for a particular place: +# Now you can get the friends for a particular person: loader |> Dataloader.load(Accounts, :friends, person1) |> IO.inspect loader |> Dataloader.load(Accounts, :friends, person2) |> IO.inspect