0
votes

After inserting data to my database, I find it difficult to update the record because of required validation on two virtual fields (:password and :password_confirmation) which I do not need during updates. I only require this fields when creating the user.

I tried removing the validation on those fields, but with this I cannot validate data at creation of the user.

User schema

defmodule TodoBackend.User.Model.User do
  use Ecto.Schema
  import Ecto.Changeset
  import Argon2, only: [hash_pwd_salt: 1]

  schema "users" do
    field :email, :string
    field :name, :string
    field :phone, :string
    field :password_hash, :string
    # Virtual fields:
    field :password, :string, virtual: true
    field :password_confirmation, :string, virtual: true

    timestamps()
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :name, :phone, :password, :password_confirmation])
    |> validate_required([:email, :name, :phone, :password, :password_confirmation])
    |> validate_format(:email, ~r/@/) # Check that email is valid
    |> validate_length(:password, min: 8) # Check that password length is >= 8
    |> validate_confirmation(:password) # Check that password === password_confirmation
    |> unique_constraint(:email)
    |> put_password_hash
  end

  defp put_password_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: pass}}
        ->
          put_change(changeset, :password_hash, Argon2.hash_pwd_salt(pass))
      _ ->
          changeset
    end
  end

end

User controller function to update user

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = UserRepo.get_user!(id)

    IO.inspect(user)

    with {:ok, %User{} = user} <- UserRepo.update_user(user, user_params) do
      render(conn, "show.json", user: user)
    end
  end

Ecto repo def

  def update_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Repo.update()
  end

Actual result: I get this error {"errors":{"password":["can't be blank"],"password_confirmation":["can't be blank"]}}

Expected result: I want the validation to still be in place and only specified field should be updated

1

1 Answers

0
votes

You can have multiple changeset functions.

def changeset(user, attrs) do
  user
  |> cast(attrs, [:email, :name, :phone])
  |> validate_required([:email, :name, :phone])
  |> validate_format(:email, ~r/@/) # Check that email is valid
  |> unique_constraint(:email)
end

def registration_changeset(struct, params) do
  struct
  |> changeset(params)
  |> cast(attrs, [:password, :password_confirmation])
  |> validate_required([:password, :password_confirmation])
  |> validate_length(:password, min: 8)
  |> validate_confirmation(:password)
  |> put_password_hash()
end

notice how the registration_changeset references changeset. This is just an example of how you could have it setup. You can have as many changesets as you would like. They are just functions afterall!