many_to_many with non-default attribute not preloading...

So, I have a many to many relationship I'm strugling with. It must be something silly, but I just can't make it work. I have a resource called Game, which is actually for a table called assets, so in the join table one of the attributes is asset_id and not game_id. The join table is called playlist_entries Any help would be greatly appreciated. Basically it is not loading the games correctly for the playlist. Addng the full resources made the post too long, so hopefully this will shed enough light... Playlist
defmodule McFun.Games.Playlist do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [
AshGraphql.Resource
]

alias McFun.Games.PlaylistEntry
alias McFun.Games.Game

postgres do
schema("games")
repo(McCore.Repo)
table("playlists")
end

relationships do
many_to_many :games, Game do
through(PlaylistEntry)
source_attribute_on_join_resource(:playlist_id)
destination_attribute_on_join_resource(:asset_id)
end
end
end
defmodule McFun.Games.Playlist do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [
AshGraphql.Resource
]

alias McFun.Games.PlaylistEntry
alias McFun.Games.Game

postgres do
schema("games")
repo(McCore.Repo)
table("playlists")
end

relationships do
many_to_many :games, Game do
through(PlaylistEntry)
source_attribute_on_join_resource(:playlist_id)
destination_attribute_on_join_resource(:asset_id)
end
end
end
PlaylistEntry
postgres do
schema("games")
repo(McCore.Repo)
table("playlist_entries")
end

relationships do
belongs_to :playlist, Playlist do
source_attribute(:playlist_id)
primary_key?(true)
allow_nil?(false)
attribute_type(:integer)
end

belongs_to :game, Game do
source_attribute(:asset_id)
primary_key?(true)
allow_nil?(false)
attribute_type(:integer)
end
postgres do
schema("games")
repo(McCore.Repo)
table("playlist_entries")
end

relationships do
belongs_to :playlist, Playlist do
source_attribute(:playlist_id)
primary_key?(true)
allow_nil?(false)
attribute_type(:integer)
end

belongs_to :game, Game do
source_attribute(:asset_id)
primary_key?(true)
allow_nil?(false)
attribute_type(:integer)
end
Game
postgres do
schema("games")
repo(McCore.Repo)
table("assets")
end
end
postgres do
schema("games")
repo(McCore.Repo)
table("assets")
end
end
10 Replies
ZachDaniel
ZachDaniel2y ago
when you say you can't get it to work, what is going wrong?
drumusician
drumusicianOP2y ago
The associated entries are not there for some reason
No description
ZachDaniel
ZachDaniel2y ago
What does that read action look like on playlist? Are the join records there looking like you’d expect? Also try updating to latest release candidate of ash
drumusician
drumusicianOP2y ago
this is the read action:
read :read do
primary?(true)
argument(:id, :integer)

get?(true)

prepare(fn query, _context ->
query
|> Ash.Query.load([:games])
end)

filter(expr(id == ^arg(:id)))
end
read :read do
primary?(true)
argument(:id, :integer)

get?(true)

prepare(fn query, _context ->
query
|> Ash.Query.load([:games])
end)

filter(expr(id == ^arg(:id)))
end
I'll see if updating helps If I add this:
join_relationship(:playlist_entries)
join_relationship(:playlist_entries)
And load this instead of the :games it does preload the join table correctly so upgrading didn't help. When I load it like this: |> Ash.Query.load([playlist_entries: :game]) the game is nil and if I change the naming to asset which is the actual table/field name, it is still nil. Is there anything specific I need to set in the join resource?
ZachDaniel
ZachDaniel2y ago
Oh, know what Nah nvm It’s something to do with the attributes/relationships defined. How does the data look in the database? Does it look right? Right values in the right columns? Double check the ids/foreign keys
drumusician
drumusicianOP2y ago
yeah the data and tables are all correct. It is already used in an existing system. I'm pretty sure it must be something dumb. Let me share the full resource files. Maybe you can spot it. Here is a gist with the full resource modules: https://gist.github.com/drumusician/3d831de382ee480e2f5f161429fb93ca The only thing I can think of is that it breaks because I am calling the asset a game, but that should be possible by defining the attributes correctly. Thanks a lot for your help!
Gist
Many to Many with Ash
Many to Many with Ash. GitHub Gist: instantly share code, notes, and snippets.
ZachDaniel
ZachDaniel2y ago
I really don't think that would be the problem 🙂 This is your problem
read :read do
primary?(true)
argument(:id, :integer)

get?(true)

prepare(fn query, _context ->
query
|> Ash.Query.filter(type != "podcast_episode")
end)

filter(expr(id == ^arg(:id)))
end
end
read :read do
primary?(true)
argument(:id, :integer)

get?(true)

prepare(fn query, _context ->
query
|> Ash.Query.filter(type != "podcast_episode")
end)

filter(expr(id == ^arg(:id)))
end
end
The primary read is used to load the relationship so thats filtering id == nil I should probably make it warn or raise if get? true is used on the primary read If you do defaults [:read] to use the default read implementation of a primary read, and call your action something else (like :by_id) then it should resolve your issue
drumusician
drumusicianOP2y ago
Thanks a lot @Zach Daniel that was indeed the problem. Works like a charm. I'll try to remember to be careful with get? true ok, one last question. The join table for this relationship has a position field. How do I go about sorting based on that field?
ZachDaniel
ZachDaniel2y ago
Do you mean you want each related value sorted by its position? That might actually be harder than it should be 😓 there are definitely ways to do it Are you also applying a limit to the number of related records? Probably the easiest way to do what you want to do is to actually go through the underlying has_many relationship.
many_to_many :games, Game do
through(PlaylistEntry)
join_relationship_name(:playlist_entries) # give it a nice name, the default is `games_join_relationship` which isn't so nice
source_attribute_on_join_resource(:playlist_id)
destination_attribute_on_join_resource(:asset_id)
end

# in your read action
prepare(fn query, _ ->
playlist_entries_query =
McFun.Games.PlaylistEntry
|> Ash.Query.load(:game)
|> Ash.Query.sort(:position)

McFun.Games.Playlist
|> Ash.Query.load(playlist_entries: playlist_entries_query)
end)
many_to_many :games, Game do
through(PlaylistEntry)
join_relationship_name(:playlist_entries) # give it a nice name, the default is `games_join_relationship` which isn't so nice
source_attribute_on_join_resource(:playlist_id)
destination_attribute_on_join_resource(:asset_id)
end

# in your read action
prepare(fn query, _ ->
playlist_entries_query =
McFun.Games.PlaylistEntry
|> Ash.Query.load(:game)
|> Ash.Query.sort(:position)

McFun.Games.Playlist
|> Ash.Query.load(playlist_entries: playlist_entries_query)
end)
Then if you run that action, you'll have playlist.playlist_entries # [list, sorted, by, position] and each playlist entry will have it's game The other way, a bit harder, is to build a manual relationship
drumusician
drumusicianOP2y ago
ok, cool. Thanks a lot again for the elaborate examples! Really appreciate all the effort you put into this! I figured it wasn't going to be straightforward, but this makes total sense. 😎

Did you find this page helpful?