Hi,
On this page there is an example of an embedded schema if you scroll down a bit to the “SignUp” part: Ecto.Schema — Ecto v3.11.1
It mentions the following:
The SignUp
schema can be cast and validated with the help of the Ecto.Changeset
module, and afterwards, you can copy its data to the Profile
and Account
structs that will be persisted to the database with the help of Ecto.Repo
.
But it doesn’t show how to convert SignUp
to Profile
and Account
once it is validated? Once I do SignUp.changeset/2
I get back a changeset that is validated etc… but then how would I pass it into Account
and Profiles
? Sorry if this is a basic question, I’m quite new to this.
Thanks
1 Like
Let’s say you have a changeset for %Signup{}
in signup_changeset
. You could write something like this:
case Ecto.Changeset.apply_action(signup_changeset, :insert) do
{:ok, signup} ->
# use signup.email etc to populate Profile and
# Account records and save to the DB
{:error, changeset} ->
# do something to tell the caller about the errors in `changeset`
end
2 Likes
Thanks for the reply - so would I do something like this for example:
case Ecto.Changeset.apply_action(signup_changeset, :insert) do
{:ok, signup} ->
Account.changeset(signup) |> Repo.insert()
Profile.changeset(signup) |> Repo.insert()
{:error, changeset} ->
# do something to tell the caller about the errors in `changeset`
end
1 Like
Maybe - you’d need to write Account.changeset
and Profile.changeset
to accept a Signup
struct.
The biggest thing that code is missing is the connection between Profile
and Account
.
A more “textbook” approach would be to build the structs explicitly:
account = %Account{email: signup.email}
profile =
%Profile{
name: signup.name,
age: signup.age,
account: account
}
Repo.insert(profile)
This assumes that the validations on Signup
are the same or stricter than the expectations for Profile
and Account
. That way it’s not necessary to use another round of changesets.
One place that assumption will fail is changeset functionality that depends on the database, like uniqueness validation or check_constraint
. Those can’t be written in Signup
! For those you’ll need to build a changeset from the data in signup
and pass that to Repo.insert
:
profile =
Profile.changeset(%Profile{}, %{
name: signup.name,
age: signup.age,
account: %{email: signup.email}
})
case Repo.insert(profile) do
{:ok, profile} ->
# return the created profile
{:error, changeset} ->
# extract errors from changeset and apply them to signup_changeset
end
and in Profile
:
def changeset(data, params) do
data
|> cast(params, [:name, :age])
|> cast_assoc(:account)
# other validations to taste
end
and in Account
:
def changeset(data, params) do
data
|> cast(params, [:email])
|> unique_constraint(:email)
end
Yet a third way to do this would be to break creating the Account
completely apart from the creation of Profile
- for instance, if you wanted to add a new Profile
for an existing Account
by email.
2 Likes