graphqlelixirphoenix-frameworkectoabsinthe

Elixir Ecto query multiple tables with args from Absinthe


I'm currently working in a project using GraphQL with Absinthe. I have a problem with the query of multiple tables which has an arg (recieve from GraphQL variables) in the WHERE clause.

I want my SQL to be like this:

SELECT p.*
FROM posts p 
JOIN categories c 
ON p.category_id = c.id 
WHERE c.name = <the-GraphQL-arg> AND p.deleted_at IS NULL AND p.is_published IS TRUE

And here is my Elixir codes: (the Post model) post.ex

schema "posts" do
  field :body, :string
  field :deleted_at, :date
  field :description, :string
  field :image_url, :string
  field :is_published, :boolean, default: false
  field :title, :string
  belongs_to :category, MyPrj.Post.Category

  timestamps()
end

(the Category model) category.ex

schema "categories" do
  field :name, :string
  field :image_url, :string
  field :title, :string
  has_many :posts, MyPrj.Post.Post

  timestamps()
end

schema.ex

query do
  @desc "Get a list of posts by category"
  field :posts_by_category, list_of(:post) do
    arg :order, type: :sort_order, default_value: :desc
    arg :category_name, non_null(:string)
    arg :limit, :integer
    arg :offset, :integer
    resolve &Resolvers.Posts.posts_by_category/3
  end
end

resolvers/post.ex

def posts_by_category(_, args, _) do
  {:ok, Post.list_posts_by_category!(args)}
end

(the Phoenix context) post.ex

def list_posts_by_category!(criteria) do
  query = from p in Post,
    join: c in Category,
    on: p.category_id == c.id,
    where: p.is_published == true and is_nil(p.deleted_at)

  Enum.reduce(criteria, query, fn
    {:limit, limit}, query ->
      from q in query, limit: ^limit

    {:offset, offset}, query ->
      from q in query, offset: ^offset

    {:order, order}, query ->
      from q in query, order_by: [{^order, :inserted_at}, {^order, :id}]

    {:category_name, category_name}, query ->
      from q in query, where: q.name == ^category_name
  end)
  |> Repo.all()
end

My GraphQL query:

query postsByCategory {
  postsByCategory(categoryName: "family", order: DESC) {
    body
    category {
      id
      title
    }
    id
    title
  }
}

But it returns an error like this:

[error] #PID<0.580.0> running MyPrjWeb.Endpoint (connection #PID<0.579.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: POST /api
** (exit) an exception was raised:
    ** (Ecto.QueryError) lib/my_prj/post.ex:185: field `name` in `where` does not exist in schema MyPrj.Post.Post in query:

from p in MyPrj.Post.Post,
  join: c in MyPrj.Post.Category,
  on: p.category_id == c.id,
  where: p.is_published == true and is_nil(c.deleted_at),
  where: p.name == ^"family",
  order_by: [desc: p.inserted_at, desc: p.id]

How can I get the field "name" (c.name) inside the Category into the query of the list_posts_by_category!(criteria) function?

Thank you very much!


Solution

  • Resolved it with these codes:

    (the Phoenix context) post.ex

    def list_posts_by_category!(criteria) do
      query = from p in Post,
        join: c in Category,
        as: :category,
        on: p.category_id == c.id,
        where: p.is_published == true and is_nil(p.deleted_at)
    
      Enum.reduce(criteria, query, fn
        {:limit, limit}, query ->
          from q in query, limit: ^limit
    
        {:offset, offset}, query ->
          from q in query, offset: ^offset
    
        {:order, order}, query ->
          from q in query, order_by: [{^order, :inserted_at}, {^order, :id}]
    
        {:category_name, category_name}, query ->
          from [q, category: c] in query, where: c.name == ^category_name
      end)
      |> Repo.all()
    end