Correct way to use put_assoc

So I have this changeset:

  def changeset(object, params) do
    object
    |> cast(params, [:attr1, :attr2])
    |> put_assoc(:assoc, params[:assoc])
    |> validate_required([:attr1, :attr2, :assoc])
  end

It works very well when I create the object because assoc already exists, hence I call something like:

Object.changeset(object, %{attr1: "value", attr2: "value", assoc: %Assoc{with_data: true}})

But then I need to update attr2 only. I would expect to use the same:

Object.changeset(object, %{attr2: "new Value"})

But it fails because it tries to put_assoc(nil). So I need to write

  def put_assoc_if_present(changeset, nil), do: changeset
  def put_assoc_if_present(changeset, assoc), do: put_assoc(changeset, assoc)

  def changeset(object, params) do
    object
    |> cast(params, [:attr1, :attr2])
    |> put_assoc_if_present(:assoc, params[:assoc])
    |> validate_required([:attr1, :attr2, :assoc])
  end

Or |> put_assoc(:assoc, params[:assoc] || object.assoc)

It feels lame. I’m guessing this is not the elixir way of doing this, but on the other hand, cast is really fine with all attributes not being here, why shouldn’t put_assoc, cast_assoc, put_embed and cast_embed?

I mean the point of a changeset is to register all changes, but no change is very fine, it shouldn’t fail then, should it?

Thanks for your insights

You could clear the association if it is not required and the way to do this is passing nil on put_assoc.

I don’t like to put the association in params map, only if it is a map and I would like to cast the association (to create or update it). Instead I pass it as a method param like def changeset(struct, assoc1, assoc2, params), but it is only a way to do this.