fireproofsocks
Adding calculated field on save
I’m working on a table to store shipping addresses. I would like to add a fingerprint of each address so that I can more easily tell whether or not a given address has already been added. For simplicity, let’s assume that I’ve worked out a nice fingerprinting algorithm to normalize my addresses and identify them via a single string… let’s say this function is named simply fingerprint(address).
My question is about Ecto and changesets: how and where can I add this field to the database record when I save it? I’m unclear on how the cast and other functions are working inside the changeset function.
If I try to save a new address and its fingerprint matches an existing address, the save operation should fail.
If I try to edit an existing address and change it so it matches an existing address, the update operation should fail.
Any help on this would be appreciated… I’m having a hard time wrapping my head around where to add this and how to think about it in an idiomatic way. Thanks!
Most Liked
kokolegorille
Probably something like this…
@doc false
def changeset(whatever, attrs) do
whatever
|> changeset(attrs)
|> cast(attrs, @fields)
|> validate ...
|> fingerprint()
|> unique_constraint(:fingerprint, message: "Fingerprint already in use")
end
defp fingerprint(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{address: address}} ->
put_change(changeset, :fingerprint, create_fingerprint(address))
_ ->
changeset
end
end
defp create_fingerprint(address) do
# Implement your fingerprint here
end
It is not tested, and fields might not correspond to your structure, but You can get the idea… the 2 last lines from changeset are important.
kokolegorille
This should not work… as it should be a field
You should use
%Ecto.Changeset{valid?: true, changes: %{street1: street1, street2: street2...}}
# or
%Ecto.Changeset{valid?: true, changes: address}
In the example I gave, whatever is the shemas, and address is just a field of %Whatever{}
You might find ecto changeset structure here. From docs…
changes - The changes from parameters that were approved in casting
kokolegorille
You might show your schema, and migration file. Be sure :fingerprint field exists, as string…
You might also insert some IO.inspect in your pipeline to show the changeset, like so
def changeset(whatever, attrs) do
whatever
|> changeset(attrs)
|> cast(attrs, @fields)
|> validate ...
|> IO.inspect()
|> fingerprint()
|> IO.inspect()
|> unique_constraint(:fingerprint, message: "Fingerprint already in use")
end
The function fingerprint take a changeset, and if it is valid, it should persists the change.
If it is clearer, You could have used pattern matching, like so.
defp fingerprint(%Ecto.Changeset{valid?: true, changes: %{address: address}} = changeset) do
put_change(changeset, :fingerprint, create_fingerprint(address))
end
defp fingerprint(changeset), do: changeset
BTW Try to change the name of the function to generate_fingerprint(), just to be sure it does not conflict with the field name.
kokolegorille
Do not cast :fingerprint… it will be calculated 
axelson
I’d also recommend you store the fingerprint in the database and add a unique constraint and a non-null constraint on the column.







