So I have found a pothole and I am not sure what the solution is.
I have a pretty standard JSON api in phoenix, I am using ecto, and I am mapping those to maps inside the ThingJSON
this is all pretty standard.
Now depending on the route, the controller might decide it doesn’t want to preload some data. For example
%Aos.Schema.Shipyard{
__meta__: #Ecto.Schema.Metadata<:loaded, "shipyard">,
id: "103b0451-7396-4f11-9cf8-7081c1829f5e",
port_id: "a748b9d2-40ff-4f14-8c8c-258b343145eb",
port: %Aos.Schema.Port{
__meta__: #Ecto.Schema.Metadata<:loaded, "port">,
id: "a748b9d2-40ff-4f14-8c8c-258b343145eb",
name: "London",
shortcode: "lond",
destinations: #Ecto.Association.NotLoaded<association :destinations is not loaded>,
ships: #Ecto.Association.NotLoaded<association :ships is not loaded>,
shipyard: #Ecto.Association.NotLoaded<association :shipyard is not loaded>,
agents: #Ecto.Association.NotLoaded<association :agents is not loaded>,
inserted_at: ~U[2024-07-01 02:02:57Z],
updated_at: ~U[2024-07-01 02:02:57Z]
},
ships: [
%Aos.Schema.ShipyardStock{
__meta__: #Ecto.Schema.Metadata<:loaded, "shipyard_stock">,
id: "8117b318-b0ef-4aa7-be31-88437acbe8fc",
shipyard_id: "103b0451-7396-4f11-9cf8-7081c1829f5e",
shipyard: #Ecto.Association.NotLoaded<association :shipyard is not loaded>,
ship_id: "0a238468-1cf0-47b4-b891-d5936d6554a8",
ship: #Ecto.Association.NotLoaded<association :ship is not loaded>,
cost: 100,
inserted_at: ~U[2024-07-01 02:02:57Z],
updated_at: ~U[2024-07-01 02:02:57Z]
}
],
inserted_at: ~U[2024-07-01 02:02:57Z],
updated_at: ~U[2024-07-01 02:02:57Z]
}
On one route it might want to preload the :ships
but on another it might not want to.
I have some pretty standard view code that looks like this
def render("shipyards.json", %{page: page}) do
%{
data: for(yard <- page.entries, do: shipyard(yard)),
meta: %{
page_number: page.page_number,
page_size: page.page_size,
total_entries: page.total_entries,
total_pages: page.total_pages
}
}
end
def shipyard(shipyard) do
%{
id: shipyard.id,
port: PortJSON.port(shipyard.port)
}
end
Now I want to modify the shipyard
function to also display the ships
but only if the assoc is actually preloaded.
Okay so I think
def shipyard(shipyard) do
%{
id: shipyard.id,
port: PortJSON.port(shipyard.port),
ships:
if Ecto.assoc_loaded?(shipyard.ships) do
for(entry <- shipyard.ships, do: stock(entry))
else
# ???
end
}
end
Problem is, if ships
is not loaded it will export json of ships: null
which is misleading IMO. I’d rather it just omitted the key.
I could do something like this
def render_if(map, key, assoc, render) do
if Ecto.assoc_loaded?(assoc) do
Map.put(map, key, render.(assoc))
else
map
end
end
def shipyard(shipyard) do
%{
id: shipyard.id,
port: PortJSON.port(shipyard.port)
}
|> render_if(:ships, shipyard.ships, &stock/1)
end
But it feels kinda wrong.
Also why is Ecto.assoc_loaded?
not available as a guard.
Anyone got any ideas?