Change Ecto validation according to action taken by the user

Hi all,

We are very new to elixir and the phoenix framework and we are trying to figure out how the phramework and embedded libraries have been set up. As the title suggests, what we are looking for is a way to apply different validations for a changeset according to the action taken.
For example lets assume that we have a Users struct and we have a username, a date of birth and an alterations field. Both username and date of birth are required fields when we define a new user but the alterations field is not. Now when the user attempts to make a change to lets say the date of birth field then we want to make the alterations field required so that the user will need to discuss the reason for this change.
What would be the way to achieve this in ecto?
Many thansk,

What about having multiple changesets?

Thank you very much for your suggestion.
We thought about this solution but it kind of sounds like there is going to be a lot of code replication and also we are not exactly sure how this would be implemented, Would you invoke the changeset according to the action?
Is this the suggested way to handle this task? Can you point to any guide describing such a scenario. Our search did not produce any results. It may be that we are searching for this in not the best way.

I’m not sure if there’s a specific name for the approach, but it’s common.

This article has a good summary of the approach; it’s not the original source of the idea, but it was a popular result for googling ecto "update_changeset".

Module attributes (a common name to see is @required_attributes) can be used to reduce repetition between multiple changeset-building functions.

BUT

I’d also encourage not trying to reduce repetition in those functions too much; it can make them hard to maintain (since changes to the common configuration always change both) and understand.

3 Likes

Yes. If you’re doing Phoenix, then your create controller action would call User.create_changeset, your update action would call User.update_changeset and so on. Basically, you should have a changeset per use case (creating a user, updating a user, updating a user by admin, etc.)

You combat duplication by extracting private functions.

Yes, this is the way. It’s described in the Programming Phoenix book.

Hi @PhoenixLRCurve, maybe you are looking for something like this:

defmodule User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "user" do
    field :username, :string
    field :birth_date, :date
    field :alterations, :string
  end

  @required_fields [:username, :birth_date]
  @optional_fields [:alterations]

  def create_changeset(user, attrs \\ %{}) do
    user
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
  end

  def update_changeset(user, attrs \\ %{}) do
    user
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    |> validate_required_by(:alterations, :birth_date) #if birth_date was changed, alterations will be required
  end

  # If there is a change on field_to_verify this function will make
  # optional_field required
  defp validate_required_by(changeset, optional_field, field_to_verify) do
    case get_change(changeset, field_to_verify) do
      nil -> changeset
      _field_changed ->
        validate_required(changeset, optional_field)
    end
  end
end

I did the validate_required_by generic but you can make it specific for your case if you want, it would be like this:

  defp birth_date_require_alterations(%Ecto.Changeset{changes: %{birth_date: _}}) do
    validate_required(changeset, :alterations)
  end
  
  defp birth_date_require_alterations(changeset) do
    changeset
  end
3 Likes

Thank you all very much for taking the time to look into this and for sharing these resources. Everything is a lot clearer now.