Reputation: 29
I am attempting to perform a Changeset validation on either an email OR phone number, and I found a nifty OR changeset function from @Dogbert here here - but I cannot get my OR validation flow to work correctly.
Does anyone mind taking a quick look on why the email or phone validation is always returning a nil
changeset?
@doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :phone])
|> validate_required_inclusion([:email, :phone])
|> validate_required_inclusion_format([:email, :phone])
end
defp validate_required_inclusion(changeset, fields) do
if Enum.any?(fields, &present?(changeset, &1)) do
changeset
else
# Add the error to the first field only since Ecto requires a field name for each error.
add_error(changeset, hd(fields), "One of these fields must be present: #{inspect fields}")
end
end
defp present?(changeset, field) do
value = get_field(changeset, field)
value && value != ""
end
## TODO - this doesnt work
defp validate_required_inclusion_format(changeset, fields) do
if Enum.member?(fields, :email) do
value = get_field(changeset, :email)
if value && value != "" do
IO.inspect(value, label: "email found: ")
changeset
|> email_changeset()
end
end
if Enum.member?(fields, :phone) do
value = get_field(changeset, :phone)
if value && value != "" do
IO.inspect(value, label: "phone found: ")
changeset
|> phone_changeset()
end
end
changeset
end
defp email_changeset(changeset) do
changeset
|> validate_required([:email])
|> validate_format(:email, ~r/.+@.+/)
|> unique_constraint(:email)
end
defp phone_changeset(changeset) do
changeset
|> validate_required([:phone])
|> valid_phone(:phone)
|> unique_constraint(:phone)
end
defp valid_phone(changeset, field) do
phone = get_field(changeset, field)
IO.inspect(phone, label: "phone: ")
{:ok, phone_number} = ExPhoneNumber.parse(phone, "US")
IO.inspect(phone_number, label: "ExPhoneNumber: ")
if ExPhoneNumber.is_valid_number?(phone_number) do
changeset
else
changeset
|> add_error(field, "Invalid Phone Number")
end
end
Thanks in advance!
Upvotes: 1
Views: 1115
Reputation: 222080
You're not returning the modified changesets properly in validate_required_inclusion_format
. In Elixir, the last value in a block is its return value. In if statements, the last value of both its true and false branch is its return value. If you don't have an else branch and the condition is false, the return value is nil
.
Here's the simplest way to fix the problem: join the two top level if
and the fallback changeset
return with a ||
:
defp validate_required_inclusion_format(changeset, fields) do
if Enum.member?(fields, :email) do
value = get_field(changeset, :email)
if value && value != "" do
IO.inspect(value, label: "email found: ")
changeset
|> email_changeset()
end
end || # <- note this
if Enum.member?(fields, :phone) do
value = get_field(changeset, :phone)
if value && value != "" do
IO.inspect(value, label: "phone found: ")
changeset
|> phone_changeset()
end
end || # <- and this
changeset
end
Now if the first or second if
conditions are not met, you'll get a nil
and the third if
will be evaluated. If the third or fourth also not met, the final fallback changeset
will be returned.
Note: the naming of this function is misleading. Unlike the function you used from my previous answer, you're not using fields
at all here. You're better off not passing fields
to this function and calling it something like add_email_or_phone_changeset
, e.g.
if value = get_field(changeset, :email) do
...
end ||
if value = get_field(changeset, :phone) do
...
end || ...
Upvotes: 2