I’m working with Ecto.Changesets in a Phoenix LiveView form and having trouble displaying validation errors from a nested struct (Company) in the UI.
Context:
I have a user_company_changeset/3
function that creates changesets for both User
and Company
, and I am manually copying errors from the Company
changeset to the User
changeset when company_changeset.valid?
is false
.
Issue:
Without manually adding the inner errors to the user changeset, the validation error inside company
is not showing in the UI. Here’s how the changesets look in each case:
Without copying errors (UI does not display nested errors correctly)
#Ecto.Changeset<
action: :validate,
changes: %{
name: "John Rambo",
password: "**redacted**",
email: "jrambo@cocacola.com",
company: #Ecto.Changeset<
action: :insert,
changes: %{company_name: "COCACOLA", tenant: "cocaCOLA"},
errors: [
tenant: {"must contain only lowercase letters", [validation: :format]}
],
valid?: false,
...
>
},
errors: [], # No top-level errors
valid?: false
>
With manually copying errors (UI displays validation errors properly)
#Ecto.Changeset<
action: :validate,
changes: %{
...
company: #Ecto.Changeset<
..
errors: [
tenant: {"must contain only lowercase letters", [validation: :format]}
],
valid?: false,
...
>
},
errors: [
tenant: {"must contain only lowercase letters", [validation: :format]} # Copied manually
],
valid?: false
>
- Is manually copying the errors from the
Company
changeset to theUser
changeset the best approach? - Is there a more idiomatic way in Phoenix/Ecto to ensure nested errors are properly propagated and displayed in the LiveView form?
Reference Code (user_company_changeset/3)
def user_company_changeset(%Company{} = company, %User{} = user, attrs \\ %{}) do
user = Repo.preload(user, :company)
company_changeset = change_company(company, attrs)
user_changeset = change_user_registration(user, attrs)
user_changeset =
if !company_changeset.valid? do
Enum.reduce(company_changeset.errors, user_changeset, fn {field, {msg, opts}}, changeset ->
add_error(changeset, field, msg, opts)
end)
else
user_changeset
end
put_assoc(user_changeset, :company, company_changeset, [])
end
Any insights would be appreciated!