Is it possible to define a schema as create only, or update only?

I’ve ran into a few situations when the schema I was creating will only ever be used for either creating a new record, or (more frequently), will only be used to update a record.

Is there a way to define the schema such that it will throw an error if you try and run Repo.create on an update-only repo?

For example if I try and create on a schema that has a very limited update scope the errors I will get back are because the database has rejected NULLs or some other requirement, I’d like the error to be much closer to “wrong Schema dum dum!”

I’ve looked in the docs and couldn’t see anything, but perhaps there is some terminology to describe this functionality that I’m unaware of.

It has to be in the changeset function since that is the only place we can define such rules for a Schema. There is no existing validator for this but you can write your own.

As for the basis of that function; I hope there is a better way.

The best I could come up with is you can check to see if the primary key in the data field is already set.

This is a quick hack which just checks if id is set - obviously it would need to be expanded to handle schemas with different PK.

  defp validate_new(changeset) do
    if changeset.data.id do
      Ecto.Changeset.add_error(changeset, :id, "Only new records may be inserted.")
    else
      changeset
    end
  end
1 Like

One way could be to set the action field on the changeset to :insert or :update - passing a changeset with such field to anything else will fail.

3 Likes

@michalmuskala picking up on what @jeremyjh wrote, my necessity is to find out if a changeset contains an existing or a new record. Is there a better way than to dig into the innards of the changeset?

def new_record?(%Ecto.Changeset{data: %{id: id}}), do: is_nil(id)

Maybe it’s worth creating a public function that does that? Or is there a better way?

You could also check the struct.__meta__.state field, that’s what we do internally in Ecto in couple places.

1 Like