Reputation: 548
Is there a way to change only the first item in a relationship via a changeset? Aka if I had:
account
with N emails
s it possible to do something like:
schema "accounts" do
...
has_many(:emails, StockrtApi.Accounts.Email)
end
def changeset(account, params) do
account
...
|> cast_assoc(:emails, required: true, with: &Email.changeset(&1, &2))
|> put_change(first_email_only, {primary: true}) # <- might have to go in email schema
end
Or what's the best way to approach this type of problem?
Upvotes: 0
Views: 55
Reputation: 120990
One should not rely on the order of anything when dealing with databases (unless an explicit ORDER BY
clause is presented.) While most database drivers will return a predictably ordered set without an explicit ORDER BY
clause, it is by no means guaranteed. That said, it’s always better to prefer explicity over implicity: just introduce another through
association:
schema "accounts" do
...
has_many :emails, StockrtApi.Accounts.Email
has_one :primary_email, through: [:primary_email, :email]
end
And put this association with a usual Ecto.Changeset.put_assoc/4
.
As per a comment, when all you need is to ensure there is an email, do exactly this:
defp ensure_email(
%Ecto.Changeset{errors: errors, changes: %{emails: emails}} = changes
) do
case emails do
[] ->
new_errors = [{:emails, {"can’t be empty", [validation: :emails]}}]
%{changes | errors: new_errors ++ errors, valid?: false}
_ -> changes
end
end
and use it explicitly:
def changeset(account, params) do
account
...
|> cast_assoc(:emails, required: true, with: &Email.changeset(&1, &2))
|> ensure_email()
end
That way you ensure that at least one email is present—just use the first one as primary
.
Upvotes: 1