Hey @dhony, if you’ve used phx.gen.auth then you should already have the user schema and context. The way I solved was by adding cast_assoc/3
to the registration_changeset
:
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password])
|> validate_email(opts)
|> validate_password(opts)
|> cast_assoc(:profile, required: true, with: &Profile.registration_changeset/2) <--- new
end
I have my Profile schema where I created the registration_changeset
function which just does validation on the :username
field (no ‘required’ constraint on user_id since it does not exist yet).
That cast_assoc
basically adds into the User changeset a new field: profile: %Ecto.Changeset{...profile stuff...}
so I tied together the creation of the user with also the creation of the profile. In my case I don’t care about users without a profile but if you want profiles to be an optional thing then adding the cast_assoc
during the creation of the user registration changeset is not the way to go.
Then in the user_registration_live.ex I changed the simple_form
that was generated with phx.gen.auth into a <.form> component
<.form
:let={f}
for={@form}
id="registration_form"
phx-submit="save"
phx-change="validate"
phx-trigger-action={@trigger_submit}
action={~p"/users/log_in?_action=registered"}
method="post"
>
<div class="space-y-8 mt-10">
<.error :if={@check_errors}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={@form[:email]} type="email" label="Email" required />
<.inputs_for :let={p} field={f[:profile]}>
<.input field={p[:username]} type="text" label="Username" required />
</.inputs_for>
<.input field={@form[:password]} type="password" label="Password" required />
<div class="mt-2 flex items-center justify-between gap-6">
<.button phx-disable-with="Creating account..." class="w-full">Create an account</.button>
</div>
</div>
</.form>
As you can notice in the inputs_for
, the field is f[:profile]
where f is the name of the form/changeset, created from the function at the top, registration_changeset
(via the context). That cast_assoc
adds :profile
in the changeset, so we can just extract it for the username input.
I didn’t have to change anything else in the event handlers or mount function as ecto just takes care of doing the validation of nested changesets.
What broke where all the tests about the registration as the user_fixtures weren’t working anymore. They were expecting the user registration changeset to have a “profile: %{}” in them (cause of the cast_assoc there). With some trial and error it’s easy to fix them tho.
Hope this helps!