CherryMan
Automatically creating has_one child on parent creation
Say I have the following models
import Ecto.Changeset
import Ecto.Schema
defmodule Parent do
schema "parents" do
has_one :child, Child
end
def changeset(%Parent{} = ch, attrs) do
ch
|> cast(attrs, [])
end
end
defmodule Child do
schema "childs" do
belongs_to :parent, Parent
end
def changeset(%Child{} = ch, attrs) do
ch
|> cast(attrs, [:parent_id])
|> foreign_key_constraint(:parent_id)
end
end
Whenever I create a Parent, I want a Child to be created and associated with the Parent automatically. Whenever I update Parent, I only want the Child to be updated if it is a part of the attrs or changeset given to Parent.changeset/2. Otherwise, the changeset should silently work.
Basically, Child should only be part of the changeset if it is being updated or if Parent is being created.
I’ve tried playing around with cast_assoc and put_assoc but neither seems to work the way I want it to.
Marked As Solved
CherryMan
Thanks for your help @dimitarvp. Final solution:
defmodule Parent do
schema "parents" do
has_one :child, Child, on_replace: :update
end
def changeset(%Parent{} = ch, attrs) do
ch
|> cast(attrs, [])
|> cast_assoc(:child)
|> changeset_preload(:child)
|> put_assoc_nochange(:child, %{})
end
def changeset_preload(ch, field),
do: update_in(ch.data, &Repo.preload(&1, field))
def put_assoc_nochange(ch, field, new_change) do
case get_change(ch, field) do
nil -> put_assoc(ch, field, new_change)
_ -> ch
end
end
end
Also Liked
dimitarvp
I suggest you use get_change. If you use get_field then you will not detect if a child has been supplied in the attrs variable.
dimitarvp
Yes it is:
defmodule Parent do
# ...
def changeset(%Parent{} = ch, attrs) do
cs =
ch
|> cast(attrs, [])
|> cast_assoc(:child, required: true)
# Conditionally set `child` unless it's already set in the parameters.
case Ecto.Changeset.get_change(cs, :child) do
nil ->
Ecto.Changeset.put_assoc(cs, :child, %Child{"your_desired_default_child_fields_go_here"}
_ ->
cs
end
end
In short, no magic. You can make it implicit by encoding that behaviour in the default changeset function.







