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.
Last Post!
joram
There is not. Ecto internally uses Ecto.Embedded.prepare with embeds from schema.__schema__(:embeds) but that is private and requires a repo adapter. I’ll note that Ecto actually reduces over the reversed list of prepares, but with one function the order doesn’t matter.
The internals where Ecto calls these functions are private and very much in the context of a repo. I’ll concur with @LostKobrakai that this is using prepare_changes for something it isn’t meant for. You should just be making changes to your changeset directly before calling apply_changes.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #elixirconf-us
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









