Generate virtual field for Ecto relationship

Hey guys! How do you deal with virtual fields in relationship in Absinthe and Ecto? Say you want to fetch an Artist with their Tracks but each track as a virtual field, poster_url, to fetch to url of the poster column. What’s the easiest way to do that?

The way that Ecto loads to relationship it’s just not obvious for me how to do that. Because the transformer function that I have for populating the poster_url from the poster column is not called when relationships are preloaded wich kind of make sense.

1 Like

I think this is roughly the same question as What's the easiest way to populate ecto's virtual field with database data unless I’m misunderstanding. In that case, sorry to be a broken record, but I’d still say query and then decorate the records. One way with a relationship would be something like…

artist = Repo.get(from(Artist, preload: :tracks), artist_id)
tracks = Enum.map(artist.tracks, &with_poster_url/1)
artist = Map.put(artist, :tracks, tracks)

Depending upon what you’re doing, you might want to just query for the tracks separately and then decorate. This would be no different than preloading since both involve two queries. It’s just that preload will automatically add the tracks to the artist struct.

1 Like

@jgb-solutions keep in midn that just because you want to have a value sent to the client doesn’t mean that you need to back it by an ecto field, virtual or otherwise.

2 Likes

yes it’s almost the same thing except that now it’s for nested relationships. I’m guessing I could do it manually with different queries, but it’s tricky because of GraphQL. The client decides what they want to query, so I don’t always know what they want and mutate that data for that. But your response makes me see something I haven’t before. Thanks!

Yes I see what you mean. That’s mainly because I’m new to this coming from Laravel. Thanks for your input.

If it’s for GraphQL I would calculate the value dynamically in the resolver

It’s hard even in the resolver because I don’t know what relationship will be queried from the GraphQL API by the client. And Scrivener Ecto library that I’m using for pagination just doesn’t support pagination for relationships.

I fill the virtual fields recursively after fetching data, e.g.

User
|> # some Ecto query with dynamic preloads
|> # Repo call
|> fill_virtual_fields()

And the fill_virtual_fields/1 function will fill the virtual fields for the entity and all the nested entities; this allows the Context not having to care about which field is virtual or not, and how to fill it. fill_virtual_fields/1 will call an implemented behaviour on all entities that have a need to fill virtual fields.

I made a small lib out of it: GitHub - mathieuprog/virtual_fields_filler: Fill the virtual fields for your Ecto structs and nested structs recursively

See also this GitHub issue that shows that at one point there was a hook, but has been removed:

And also notice that it is a recurring topic with people finding this hook lacking, one example being:

1 Like

hey @mathieuprog that’s really interesting! I’ll definitely have a look at the links. Thanks!