Hi!
Just to be sure that I’ve understood you problem well : you have defined a Ecto.Schema
for a given model (let’s call it Feature
), and in that %Feature{}
struct, you want the field type
to be set once and for all at creation, and never change on further updates (which can change either image_id
or description_id
depending on the value of type
)
Is that it ?
If so…
I can remove the (type) from the cast, but the thing is, is this the correct way to make a field (constant) and prevent it from changing or should I use a constraint?
Not an expert myself but I would say that the best way to prevent your type
field from changing is to remove it from the cast fields indeed.
The way you ask your question makes me want to add - if that’s helpful to you - that it took me a long time to figure out that you don’t need to always use the same changeset/2
function for all the operations you need to perform on your data (which is what appears e.g. in the simple examples from the Ecto documentation). That even seems totally counter-productive to me now.
So in your case I would have 2 functions e.g. create_cs
and update_cs
:
@create_params [:type, :description_id, :image_id]
@update_params [:description_id, :image_id] # No type here !
def create_cs(params \\ %{}) do
%Feature
|> cast(params, @create_params)
|> validate_required([:type])
end
def update_cs(feature = %Feature{}, params) do
feature
|> cast(params, @update_params)
end
And then : create_cs(params) |> Repo.insert
or feature |> update_cs(params) |> Repo.update
Actually since it also seems important to check consistency, you can even have two distinct clauses in update_cs
, e.g. :
def update_cs(feature = %Feature{type: :image}, params) do
feature
|> cast(params, [:image_id])
|> validate_required([:image_id])
end
def update_cs(feature = %Feature{type: :description}, params) do
feature
|> cast(params, [:description_id])
|> validate_required([:description_id])
end
create_cs
function could also have different clauses or even be split into 2 functions if the value of type
can be determined from the context of the call (and not from what is passed within params
).
You might also want to simply use your RDBMS capabilities and add a CHECK constraint (e.g. "type" = 'image' AND "image_id" IS NOT NULL OR "type" = 'description' AND "description_id" IS NOT NULL
) and a TRIGGER run on UPDATE operations that would check e.g. that OLD.type = NEW.type
and raise an exception otherwise ; the later one would be a bit more invasive in your app code, though, since last time I checked, those SQL exceptions are not caught by Ecto
and will trigger an exception that you need to rescue
.