Better way to build this Ecto Changeset?

    user_id = if (Map.has_key?(changeset.changes, :user_id)), do: changeset.changes[:user_id], else: sales_agent.user_id
    changeset =
      if (!user_id) do
        validate_required(changeset, [:name])
      else
        changeset
      end

I want to validate that this sales_agent record has a name if they don’t have a user_id .
But if the user_id is being changed, the new value will be in the changeset.changes and not in the sales_agent struct, and if it hasn’t changed it’ll be in the struct but not in changeset.changes . So I have to write this user_id = if (), do: , else: thing, but that seems like something Ecto should already be handling and I just don’t know how it’s doing it, so I have code like this all over my project and I’d like to get rid of it if I can.

Can use get_field/3 here. We do not need to save it to a variable. Also get_field will look at the changes and then in checks the data if it is already set.

You could create a private function and just use that in other changesets to keep it dry.

defp validate_name(changeset) do
  if get_field(changeset, :user_id) do
    changeset
  else
    validate_required(changeset, [:name])
  end
end

If you needed to do something with the user_id you could also do it like this:

defp validate_name(changeset) do
  if user_id = get_field(changeset, :user_id) do
    # do something with user_id
    changeset
  else
    validate_required(changeset, [:name])
  end
end

And this will only go into the if if user_id is not nil

You could then just use it like so:

def changeset(sales_agent, params) do
  sales_agent
  |> cast(params, [:name, :start_date, :user_id])
  |> validate_required([:start_date])
  |> validate_name()
end

As long as you return a changeset, you can create pretty nice reusable functions to use in your changeset pipelines

1 Like