absinthe-graphql / absinthe_ecto Goto Github PK
View Code? Open in Web Editor NEWDEPRECATED: Use dataloader
License: MIT License
DEPRECATED: Use dataloader
License: MIT License
hi there ~
i am upgrade absinthe from 1.4 to 1.5 on my project, the absinthe_ecto README says:
but when i follow the step, i got error:
env info:
elixir 1.10.3
-----
{:phoenix, "~> 1.5.7"},
{:phoenix_pubsub, "~> 2.0"},
{:phoenix_html, "~> 2.14.3"},
{:ecto_sql, "~> 3.5.4"},
{:phoenix_ecto, "~> 4.2.1"},
{:postgrex, "~> 0.15.8"},
{:gettext, "~> 0.18.0"},
{:plug_cowboy, "~> 2.4.1"},
{:plug, "~> 1.11.0"},
# GraphQl tool
{:absinthe, "~> 1.5.5"},
# {:absinthe_ecto, "~> 0.1.3"},
{:absinthe_plug, "~> 1.5.4"},
{:dataloader, "~> 1.0.7"},
...
Let's say all comments
should be ordered by inserted_at
desc. Will Absinthe.Ecto
provide functionality similar to the following?
field :comments, list_of(:comment), resolve: assoc(:comments, order_by: [desc: :inserted_at])
This is a duplicate of absinthe-graphql/dataloader#7. I'm not using dataloader anymore at the moment and figured out it has more of its place here.
Let's say I have a :fruits_basket
object that can contains fruits and that can be placed in another basket. Something like:
object :fruits_basket do
field :id, non_null(:id)
field :slug, non_null(:string)
field :fruits, list_of(:delicious_fruit), do: resolve assoc(:fruits)
field :parent, :fruits_basket, do: resolve assoc(:parent_basket)
end
As of today a request like:
basket(slug: "grandma-basket") {
id
fruits {
name
color
}
parent {
id # We only ask for parent's id and no other field
}
}
...would make two queries like:
SELECT (...) FROM fruits_baskets WHERE slug = "grandma-basket" -- Get basket
SELECT (...) FROM fruits_baskets WHERE id = 42 -- Get parent basket
The thing is that second query is not necessary: we already have the parent id in grandma's basket.
I think it is a very common pattern to query only for the id of an association. In this example, the request could be used to show a "Go to parent" link on the frontend.
My real use case is a comment system where comments can reply to each others by using a reply_to
field. I would love to see assoc
use the reply_to_id
field instead of preloading the full reply_to
when requesting only the id.
The easiest way to achieve this with actual system is to add a field for the id:
...
field :reply_to_id, :id
field :reply_to :comment, do: resolve assoc(:reply_to)
...
But that doesn't look very neat to me.
Hi,
The README says:
The assoc macro just builds a resolution function which calls ecto_batch/4.
See the ecto_batch/4 function for how to do this from within a regular resolution function.
As a beginner I have a hard time picturing the equivilent ecto_batch code. I know there is documentation on how to use ecto_batch, but I think what would really help a newcomer to this repo is a quick example of how you would implement the specific assoc example in the README if you had to do it with ecto_batch only.
What do you think?
hi guys ~
let's say i have a post type with many started_users field witch resolved by assoc
macro, it's great , but how to do pagination in this situation ?
object :post do
field(:id, non_null(:id))
field(:title, non_null(:string))
field(:body, non_null(:string))
field(:author, :author, resolve: assoc(:author))
> field(:starred_users, list_of(:user), resolve: assoc(:starredUsers))
end
Hi, we broke our build today because the versions changed.
Any reason this repo couldn't be hosted on hex.pm?
Thanks
The recent merge of #11 (committed in 29e9921) breaks :through associations.
defmodule Api.Deliverable do
# ...
belongs_to :plan, Api.Plan
has_one :project, through: [:plan, :project]
# ...
Now when I try to use this in a query:
object :deliverable do
field :project, :project, resolve: assoc(:project)
end
I get:
** (MatchError) no match of right hand side value: %Ecto.Association.HasThrough{cardinality: :one, field: :project, on_cast: nil, owner: Api.Deliverable, owner_key: :plan_id, relationship: :child, through: [:plan, :project], unique: true}
This is because the %Ecto.Association.HasThrough{}
struct doesn't include the queryable
key (which the new updates try to match on).
Didn't realize 0.1.0 was pushed to Hex last month so switching to that instead of master is working fine for now. I'll try and get a PR in when I have some time.
Hi, I've been using Absinthe
and Absinthe.Relay
intensively for a private project and wrote a few helper functions in order to resolve GraphQL queries into Ecto queries.
I've put the code here: https://gist.github.com/redrabbit/be3528a4e4479886acbe648a693e65c0
I don't know if Absinthe.Ecto
is going to be implemented in a similar manner. If there is interest, I can write a PR and add some tests to it.
Basically, it uses Ecto.Schema
and Absinthe.Resolution
to resolve pretty much every kind of GraphQL query I'd came across so far.
There are two distinct functions, build_query/3
and batch_query/5
.
By default, build_query/3
automatically resolves :join
, :preload
and :select
clauses. It tries to be as performant as possible and selects only required fields.
Let's say we have schemas representing a music library:
schema "artists" do
field :name, :string
has_many :albums, Album
has_many :tracks, Track
timestamps
end
schema "albums" do
field :title, :string
field :release_date, Ecto.Date
belongs_to :artist, Artist
many_to_many :genres, Genre, join_through: "albums_genres"
has_many :tracks, Track
timestamps
end
schema "genres" do
field :name, :string
many_to_many :albums, Album, join_through: "albums_genres"
timestamps
end
schema "tracks" do
field :title, :string
field :track_nr, :integer
field :duration, :integer
field :popularity, :integer
belongs_to :artist, Artist
belongs_to :album, Album
timestamps
end
And the following matching GraphQL types:
object :artist do
field :id, :id
field :name, :string
field :albums, list_of(:album)
field :tracks, list_of(:track) do
arg :top, :integer, default_value: 10
resolve &resolve_artist_top_tracks/3
end
end
object :album do
field :id, :id
field :title, :string
field :release_date, :date
field :artist, :artist
field :tracks, list_of(:track)
field :genres, list_of(:genre)
end
object :genre do
field :id, :id
field :name, :string
field :albums, list_of(:album)
end
object :track do
field :id, :id
field :title, :string
field :artist, :artist
field :album, :album
field :track_nr, :integer
field :duration, :integer
field :popularity, :integer
end
Following GraphQL query:
{
artists {
name
albums {
title
tracks {
title
}
genres {
name
}
}
}
}
Returns a single Ecto.Query
:
#Ecto.Query<from a0 in Artist,
join: a1 in assoc(a0, :albums),
join: g in assoc(a1, :genres),
join: t in assoc(a1, :tracks),
select: [:id, :name, {:albums, [:id, :title, {:genres, [:id, :name]}, {:tracks, [:id, :title]}]}],
preload: [albums: {a1, [genres: g, tracks: t]}]>
It handles :belongs_to
, :has_one
, :has_many
and :many_to_many
associations and will try to join and preload as much as possible. It also support resolving cyclic graphs.
Writing resolvers is straight forward:
def resolve_artists(_args, info) do
query = build_query(Artist, info, &order_by(&1, [asc: :name]))
{:ok, Repo.all(query)}
end
def resolve_artist_by_id(%{id: id}, info) do
id = String.to_integer(id)
query = build_query(Artist, info, &where(&1, [id: ^id]))
case Repo.one(query) do
nil ->
{:error, "Cannot find artist with id #{id}."}
artist ->
{:ok, artist}
end
end
In order to support batching and avoid N+1 queries, batch_query/5
provides similar functionalities and resolve the given associations automatically. For example:
object :artist do
field :id, :id
field :name, :string
field :albums, list_of(:album)
field :tracks, list_of(:track) do
arg :top, :integer, default_value: 10
resolve fn artist, args, info ->
batch_query(Track, artist, info, &Repo.all/1, fn query ->
query
|> order_by([desc: :popularity])
|> limit(^min(Map.get(args, :top, 10), 100))
end)
end
end
end
In the above object
, the :albums
field is resolved automatically within the query using a :join
. The :tracks
field will require a 2nd query (using where: [artist_id: id]
or where: a1.artist_id in ^ids
).
Resulting in executing two SQL queries for the following GraphQL:
{
artists {
name
tracks(top:5) {
title,
duration
}
albums {
title
tracks {
title
}
genres {
name
}
}
}
}
2016-11-26 02:57:28.093 [debug] QUERY OK source="artists" db=2.1ms
SELECT a0."id", a0."name", a1."id", a1."title", t2."id", t2."title", g3."id", g3."name" FROM "artists" AS a0 INNER JOIN "albums" AS a1 ON a1."artist_id" = a0."id" INNER JOIN "tracks" AS t2 ON t2."album_id" = a1."id" INNER JOIN "albums_genres" AS a4 ON a4."album_id" = a1."id" INNER JOIN "genres" AS g3 ON a4."genre_id" = g3."id" ORDER BY a0."name" []
2016-11-26 02:57:28.095 [debug] QUERY OK source="tracks" db=1.6ms
SELECT t0."artist_id", t0."id", t0."title", t0."duration" FROM "tracks" AS t0 WHERE (t0."artist_id" = $1) ORDER BY t0."popularity" DESC LIMIT $2 [1, 5]
You can customize your queries using the optional transform
parameter both functions provide.
For example, to fetch albums sorted by :release_date
and album tracks sorted by :track_nr
, one can write two new resolver functions:
def resolve_artist_albums(artist, _args, info) do
batch_query(Album, artist, info, &order_by(&1, [desc: :release_date]))
end
def resolve_album_tracks(album, _args, info) do
batch_query(Track, album, info, &order_by(&1, [asc: :track_nr]))
end
And update the schema types like this:
object :artist do
field :albums, list_of(:album), do: resolve &resolve_artist_albums/3
end
object :album do
field :tracks, list_of(:track), do: resolve &resolve_album_tracks/3
end
Is there a way to use this with absinthe_relay? Specifically, how would assoc and Absinthe.Relay.Connection work together?
Are there any plans to implement support for many_to_many
associations? I would guess implementing this is a little bit harder?
It appears that the association is stitching together the field name and custom @primary_key
, but ignoring foreign_key
.
Now that there's a test suite, it would be helpful to add some sort of CI to the project to ensure the tests are passing.
With the introduction of the query fun support to tailor the batch query, I am wondering if it shouldnt be used to compose the batch id.
Suppose we have 2 fields which use the same ecto field but with 2 different queries. My understanding is that because they share the same batch id, only one of those 2 queries will run and those 2 fields will end up with the same value.
@benwilson512 what do you think ?
Lets say an event
has many attendees (events_users
), and we have a unique index so that a user can't attend the same event twice.
Is there a way using absinthe_ecto
to have a field on event
called e.g. current_user_attendance
which checks if the current user attended the event, but instead of returning a list with a single 'attendance', it returns the field directly?
object :event do
field :current_user_attendance, list_of(:event_user) do
resolve assoc(:events_users, fn query, args, %{current_user: current_user} ->
query
|> where([eu], eu.user_id == ^current_user.id)
end)
end
end
The above works fine, except it'll return the aforementioned list, when we know be definition there will be either one or zero results.
I appreciate I could write a custom resolver, but it would be very elegant to do it in absinthe_ecto
, as when you're fetching many events it very cleanly takes care of n+1 issues.
Getting this error with the following:
field :orders, list_of(:order), resolve: assoc(:orders)
Am I doing something wrong here? Fairly new to Elixir but think that's a fairly accurate copy from the docs. :)
Thanks.
Using this package was so awesome to avoid N+1, auto-preload associations, and do not mess with complex Dataloader. Why you deprecated it?
Hi,
Some of my tables have fields that serialize very large values in Postgres hstore or jsonb columns. Will batching with absinthe_ecto always include those columns in queries, and if so, how can I control that? There are many situations where I wouldn't want those columns included in a query unless their respective GraphQL field is included in the query, especially for has_many associations where there could be a lot.
SQL call ends up being just the one for the root query type when doing the following:
query{
profiles{
firstName
profileImages{
url
}
logins{
cellNumber
}
}
}
[debug] QUERY OK source="Profile" db=152.8ms decode=0.2ms
SELECT P0."sm_id", P0."first_name" FROM "Profile" AS P0 []
[info] Sent 200 in 156ms
The graphiql query is then of course missing the lists
...
{
"profileImages": null,
"logins": null,
"firstName": "Thomas"
},
{
"profileImages": null,
"logins": null,
"firstName": "Jonathan"
},
...
graphql types are set up just like examples
and ecto models as well
Could there be an incompatibility with the mssql-ecto driver ?
Failed to use "absinthe" because
mix.exs specifies ~> 1.3.1
mix.exs specifies ~> 1.3.1 to mix.exs specifies ~> 1.3.0 run
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.