endymion
Changes not properly applied with Ecto.Changeset.apply_changes/1
I am wondering why when I ran Ecto.Changeset.apply_changes(changeset) the prepared changes function on that changeset did not run. I have a changeset that I am building from a Phoenix form; after validation occurs and user submits the form, I want to apply those params to my struct with a prepared change to increment a value of one of the inputs.
Simple example demonstrating the unexpected behavior of apply_changes/1
We have a sample struct. It casts some params then adds a prepare_changes function to that changeset. When calling apply_changes that prepare_changes function is not run so our resulting struct is incorrect.
Example:
defmodule InputData do
use Ecto.Schema
@primary_key false
embedded_schema do
field :value, :integer
end
end
%InputData{value: nil}
|> Ecto.Changeset.cast(%{"value" => "1"}, [:value])
|> Ecto.Changeset.validate_number(:value, [greater_than: 0])
|> Ecto.Changeset.prepare_changes(fn changeset ->
incremented_value = Ecto.Changeset.get_field(:value, 0) + 1
Ecto.Changeset.put_change(changeset, :value, incremented_value)
end)
|> Ecto.Changeset.apply_changes()
The output of this is not the expected %InputData{value: 2} where the params casts the value to 1 then is incremented again by 1 in the prepare_changes/2. Why is this? Since apply_changes/1 results in a struct and not a changeset there is no risk of firing the prepared_changes functions again if the developer needed to to a Repo insert. In my case I am never inserting this struct into a database and thus need these prepared_changes to run when calling apply_changes.
In summary this feels like a bug or missing implementation in Ecto Changesets that I seem unable to come up with a convincing argument as to why it shouldn’t behave like this.
Most Liked
LostKobrakai
The first sentence of the documentation states:
Provides a function executed by the repository on insert/update/delete.
For Ecto.Changeset.apply_changes there’s no repository involved and it’s neither of those mentioned actions either.
Usually prepare_changes is used to execute code within the same transaction as what the changeset applies to the database, which needs to be provided as a callback as that transaction is only started later (by the repo). If you don’t have a transaction you can directly apply the changes to the changeset in place. No need to delay that.








