Transparent access to association's field

I am accessing an existing database with some polymorphic associations,

[email] <-> [customers_emails] <-> [customer]
 email
  id    <--      email_id
                customer_id    -->  id

I have %Email{email: _} as a :through association in my %Customer{}, but is there a way I can define my %Customer{} so that I can have customer.email map to customer.email.email?

You could use select to return a map of whatever structure you like. Have you tried preload to load them all?

1 Like

Thanks for the reminder about select—I don’t use it very often and it slips from memory.

I found a solution using select_merge that seems good enough™

defmodule MyApp.Customer do
  schema "customers" do
    has_one(:customer_email, CustomerEmail)
    has_one(:email, through: [:customer_email, :email])
    timestamps()
  end
end
defmodule MyApp.CustomerEmail
  schema "customers_emails" do
    belongs_to(:customer, Customer)
    belongs_to(:email, Email)
    timestamps()
  end
end
defmodule MyApp.Email
  schema "emails" do
    field(:email, :string)
    timestamps()
  end
end
from(customer in Customer, 
  where: customer.id == ^customer_id,
  join: email in assoc(customer, :email),
  select_merge: %{email: email.email}
)
|> Repo.one()

produces…

%MyApp.Customer{
  __meta__: #Ecto.Schema.Metadata<:loaded, "customers">,
  customer_email: #Ecto.Association.NotLoaded<association :customer_email is not loaded>,
  email: "test@example.com",
  id: "customer_01j0si846364bnah000z",
  inserted_at: ~U[2021-10-21 17:53:21Z],
  updated_at: ~U[2021-10-21 17:53:21Z]
}

edit: a simple select: %{customer | email: email.email} works too—thank you!

1 Like