Search code examples
elixirassociationsecto

Create Ecto changeset for deeply nested struct with nested associations


Please consider the following schema:

schema "businesses" do
  ...
  has_many :contacts, Contact
  has_many :conversations, Conversation
  ...
end
schema "contacts" do
  ...
  belongs_to :business, Business
  has_one :conversation, Conversation
  ...
end
schema "conversations" do
  ...
  belongs_to :business, Business
  belongs_to :contact, Contact
  has_many :messages, Message
  ...
end
schema "messages" do
  belongs_to :conversation, Conversation
  belongs_to :contact, Contact
end

Given that I have a business struct, how would I create the contact, conversation and message structs in a way that handles associations?

The following works when excluding the messages part.

business
|> Ecto.build_assoc(
  :contacts,
  %Contact{name: name, phone: phone}
)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(
  :conversation,
  %Conversation{business: business}
  |> Ecto.Changeset.change()
  |> Ecto.Changeset.cast_assoc(:business)
)
# |> Ecto.Changeset.put_assoc(:messages, [%Message{body: "hello"}])
|> Repo.insert()

With the second to last line uncommented, PostgreSQL is letting me know that message should have a conversation_id and that is true. How could that be set?

Adding an IO.inspect():

business
|> Ecto.build_assoc(
  :contacts,
  %Contact{name: name, phone: phone}
)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(
  :conversation,
  %Conversation{business: business}
  |> Ecto.Changeset.change()
  |> Ecto.Changeset.cast_assoc(:business)
)
  |> IO.inspect()
# |> Ecto.Changeset.put_assoc(:messages, [%Message{body: "hello"}])
|> Repo.insert()

shows that we're working with the following changeset right before attempting to set the messages association:

#Ecto.Changeset<
  action: nil,
  changes: %{
    conversation: #Ecto.Changeset<action: :insert, changes: %{}, errors: [],
     data: #Conversation<>, valid?: true>
  },
  errors: [],
  data: #Contact<>,
  valid?: true

Is there a way for the newly created message to become associated with both the newly created contact and the newly created conversation?


Solution

  • This is doable with Ecto.Multi.

    https://hexdocs.pm/ecto/Ecto.Multi.html