Making an Ecto field both required/non-null and computed

I have an Ecto schema with a field that I want to make required but potentially calculated from another field if it’s nil. Here’s a simplified version:

schema "users" do
  field :email, :string, null: false
  field :username, :string, null: false
end

I want to enable specifying a username but for the username to default to the value of the email field if no username is specified. My changeset function is currently looking like this:

def changeset(changeset, attrs \\ %{}) do
  changeset
  |> cast(attrs, [:email, :username])
  |> validate_required([:email, :username])
  |> set_username()
end

def set_username(changeset) do
  case get_field(changeset, :username) do
    nil ->
      put_change(changeset, :username, changeset.email)
    _username ->
      changeset
  end
end

But this is throwing errors that I’m violating the non-null constraint for username. I’ve also tried putting the set_username function before the validate_required and cast functions but that hasn’t worked either. Any ideas?

I think there is no need to validate :username. Just validating :email is enough because you are setting the :username if it is not setted yet.

1 Like

Yeah, that makes sense. I think I was wrong to conceptualize “requires” here as “needs to be present after all transformations to the changeset” when it really should be more like “needs to be present in the initial changeset.”

I also think it’s important to make a distinction between non-null and required. Setting null: false in the schema doesn’t necessarily entail that it should be in your validate_required list.