0

I am using absinthe with elixir (phoenix 1.3). I have a blog app that has Users, Posts, and Likes, and the likes are joined with a many-to-many relationship between Users and Posts.

  schema "users" do
    field :email, :string
    field :handle, :string 
    many_to_many :liked_posts, MyApp.Content.Post, join_through: "likes"
  end

  schema "posts" do
    field :title, :string
    field :content, :string 
    many_to_many :liking_users, MyApp.Accounts.User, join_through: "likes"
  end

  schema "likes" do
    belongs_to :user, MyApp.Accounts.User
    belongs_to :post, MyApp.Content.Post
  end

Let's say I want to aggregate them on the backend rather than the front. I would like :liked_by to simply be a count of all the likes that exist, more like field :likes, :int, so that I can get back responses like this:

{
  "data": {
    "post" : {
      "title" : "Title",
      "content" : "This is the content",
      "likes" : 7
    }
  }
}

What would my object have to look like? I want to do something like this:

  object :post do
    field :id, :integer
    field :title, :string
    field :content, :string
    field :likes, :integer, resolve: assoc(:liking_users, fn query, id, _ ->
       query |> from like in MyApp.Content.Like, where: like.post_id == ^id, select: count("*")
    )
  end

EDIT #1: More specifically, I'd like to know how to parameterize an anonymous function in the absinthe object. I can get the object to return a non-parameterized value easily:

field :somenumber, :integer, resolve: fn (_,_) -> {:ok, 15} end

But adding a parameter like so

field :somenumber, :integer, resolve: fn (foo,_) -> {:ok, foo} end

returns the following:

...
"somenumber": {},
...

How can I pass in id of the object, or an implicitly associated query?

EDIT #2: I have found a solution to this, but it feels very hacky.

  object :post do
    field :id, :integer
    field :title, :string
    field :content, :string
    field :likes, :integer, resolve: fn (_,_,resolution) ->
      {:ok, Post.getLikeCount(resolution.source.id) }
    end
  end
Mark Karavan
  • 2,654
  • 1
  • 18
  • 38

1 Answers1

1

After following @mudasobwa's advice, I have this solution:

 object :post do
    field :id, :integer
    field :title, :string
    field :content, :string
    field :likes, :integer, resolve: fn (query,_,_) ->
      Post.getLikeCount(query.id)
    end
  end

resolution, the third argument of the arity 3 anonymous function for the resolver, is an Absinthe.Resolution object. resolution.source is of type MyApp.Content.Post, of which id refers to that Post.

Then I just added a function into Post.ex called getLikeCount/1 that gets the number of likes.

  def getLikeCount (post_id) do
    query =
      from l in MyApp.Content.Likes,
        where: l.post_id == ^post_id,
        select: count("*")

    case Repo.one(query) do
      nil -> {:error, "Error getting like count for post #{post_id}"}
      likes -> {:ok, likes}
    end
  end
Mark Karavan
  • 2,654
  • 1
  • 18
  • 38
  • 1
    [`Repo.one/2`](https://hexdocs.pm/ecto/Ecto.Repo.html#c:one/2) would be probably more suitable here. Also, I don’t get why do you think this solution is hacky. BTW, according to the documentation, the first param in a call to resolver should be `Post.id`, isn’t it? – Aleksei Matiushkin Dec 20 '17 at 06:48
  • 1
    Updated. Also it seems that the first variable in the arity 3 function contains exactly what I was looking for, so this no longer feels hacky. – Mark Karavan Dec 20 '17 at 16:19
  • 2
    looks like this would cause n+1 queries, wouldnt it be better to use dataloader or maybe batching so this query is not executed for every post returned? – nico Jul 31 '18 at 18:48