Alternative name for association fields in Ecto Query

Hi Everyone,

I need to query a has_many association field with a different name than the one in the original schema. When the field is not an association, it’s rather simple, but I can’t figure out a solution to do it with associations.

I started with the following code:

query
|> preload(:image_labels)
|> select([d], %{d | labels: d.image_labels})

The field :image_labels is defined like this in the schema:

has_many(:image_labels, Labels.Image, foreign_key: :datapoint_id)

This gives an error(“field image_labels in select is a virtual field”), so I tried using select_merge to select it first, I also tried injecting SQL code with fragment, but didn’t find a solution…

Do you have any idea how I can do this?

Thanks a lot for you time!

You might have a typo…

has_many(:label_image, Labels.Image, foreign_key: :datapoint_id)

I think has_many should be set with plural… has_many(:label_images…)

It’s a typo indeed :slight_smile: I have shorten the name of the field to make the example more concise, but the field name in my schema is in plural.

I edited the question, thanks!

Not that this solve your problem, but I would do shape data more inside a view, less inside the query.

I am not sure what you mean by “shape data”?
Also, my application is a GraphQL API, so I don’t have views.

I thought here is some kind of data transformation…

I describe association like this in graphql, in my schema types.

field :labels, :image, resolve: assoc(:image_labels)

Assuming You have an Image object defined.

The reason I want to do this is because I am resolving my image_labels in an union type, and so I have several field names that I need to rename with the same name.

I am trying to do it without using the has_many field. Here is what my query looks like:

  from(
    d in datapoints,
    join: l in assoc(d, :label_image_classes),
    on: l.datapoint_id == d.id and l.dataset_id == ^dataset.id,
    preload: [:label_image_classes]
  )
  |> Absinthe.Relay.Connection.from_query(&Repo.all/1, pagination_args)

Thing is, it resolves to a map with a field :label_image_classes, whereas I need to have a field :labels.

I could modify the map after the query, but I figured it would be very error-prone…

Using this rename the field to labels… In case I don’t want to use assoc, I would use a custom resolver, like this

    connection field :comments, node_type: :comment do
      arg :order, :sort_order, default_value: :asc
      resolve &CommentResolver.post_comments/3
    end

connection is for Relay, arg are for query parameters.

I am sorry, but I don’t get the full picture of your use case.

This is exactly what I am trying to do actually! The only thing is that I have several associations that resolve to the labels field, so I need to write a custom resolver.

But now in my resolver, I need to rename the association field to labels, because Absinthe won’t take care of it for me.

Totally my bad if my use case is not clear enough, let me know if there is still something unclear.

Ok I missing a detail here…

Because I can’t resolve the labels field automatically, I need to write the logic in a resolver. But I don’t want to write a custom resolver for the labels, because then I would have to deal with the batching of the queries. Instead, I am trying populate the labels field one level higher: in my datapoint resolver.

In my datapoint resolver, the labels can be of different types, but I need to merge everything in labels to let Absinthe resolve it.

Does it make sense?

What I do not understand is why You need to rename anything… the resolver points to :labels and it would resolve only this attribute.

I would use a query only on image_labels… for a given master id.

I would not create a join, nor preload. just a where clause on master id, on the image_labels.

Does my previous message answer this? I am not writing the :labels resolver, but the :datapoint resolver and I try to populate the :labels field directly inside this resolver. The reason I am doing it this way is because I don’t want to deal with query batching.

Ok, I see… the query is on level above :slight_smile:

In this case I would not use preload, but some sort of join instead, or define a postgresql view.

Any idea how you would write this join? I’ve tried but haven’t been successful :confused:

You might not want to do batching, but it seems You just transfer the problem to the query You want to build…

Because You want to retrieve image_labels for each datapoint, which will be n+1 query any way. Even if You transform the join to a list in the final result.

1 Like

I think I am going to go with query batching… I did an implementation of what I want with a resolver for :labels and it simplifies the code.

Thanks for your input, really appreciated :smiley: