Search code examples
elixirphoenix-frameworkecto

Ecto preload nested


I am learning phoenix and ecto associations but I have stumbled upon a problem I can't seem to solve while trying to preload nested associations. I have 3 schemas

Offer that has

schema "offers" do
    ...
    has_one :albumMariageSmall, Album, on_delete: :delete_all, on_replace: :delete
    has_one :prewedding, Prewedding, on_delete: :delete_all, 
    on_replace: :delete
    has_one :next_day, Prewedding, on_delete: :delete_all, on_replace: :delete
end

Now, the Prewedding looks like

schema "preweddings" do
    ...
    has_one :album, Album, on_delete: :delete_all, on_replace: :delete
end

and the album looks like

schema "albums" do
    ...
    belongs_to :offer, Offer
    belongs_to :prewedding, Prewedding
end

In other words, an offer can have an Album and two Prewedding which Prewedding can have an Album.

Database wise, the :preweddings is referencing the offer through offer_id and the :albums references the offers through offer_id and the preweddings through prewedding_id.

I have trouble loading any saved offer as both :prewedding and :next_day seem point to the same row when I am inspecting.

I am trying to preload using

offer=Repo.one from(o in Offer, preload: [{:prewedding, [:album]},{:next_day, [:album]}], select: o, where: o.id==^id)

I assume I can make it work with joins, but i am stubborn enough to want to do preload.

Can someone assist?

Migration of preweddings is just this

add :included, :boolean, default: false, null: false
add :offer_id, references(:offers)

Album has

add :offer_id, references(:offers)
add :prewedding_id, references(:preweddings)

Solution

  • By default, when using has_one, the foreign key is inferred from the name of the current table, e.g. offers -> offer_id. Since you have two has_one and you don't specify a custom foreign key for either, both will use offer_id as the foreign key and will therefore always have the same value. You'll need to specify the correct foreign keys to the two has_one. For example, if your preweddings table has this migration:

    add :offer_prewedding_id, references(:offers)
    add :offer_next_day_id, references(:offers)
    

    You need to do:

    has_one :prewedding, Prewedding, on_delete: :delete_all, on_replace: :delete, foreign_key: :offer_prewedding_id
    has_one :next_day, Prewedding, on_delete: :delete_all, on_replace: :delete, foreign_key: :offer_next_day_id