spacebat
I recently updated from an early Ash 3.x to the most recent, and find at runtime a lot of warnings like:
Changeset has already been validated for action :create.
For safety, we prevent any changes after that point because they will bypass validations or other action logic.. To proceed anyway, you can use `force_change_attribute/3`. However, you should prefer a pattern like the below, which makes any custom changes *before* calling the action.
Resource
|> Ash.Changeset.new()
|> Ash.Changeset.change_attribute(...)
|> Ash.Changeset.for_create(...)
So far in my application I have a lot of actions that use Ash.Changeset.change_attribute/3 within change before_action(fn ....) blocks. This is usually to support computed defaults that depend on other attributes/arguments so that the call sites for the actions are nice and clean.
The simplest thing is to change all these occurrences to Ash.Changeset.force_change_attribute/3 but the warning advises against it.
I could write wrapper functions for all these actions that compute the defaults before calling the action, as advised by the warning, but then the actions themselves become far less convenient to call directly, and the functions will be located elsewhere in the module, not right next to the action they are related to.
Maybe wrapper actions would be better, so have a create :raw_create that defaults nothing, and create :create would accept no attributes, only arguments, compute the defaults and call the raw_create action, but that feels verbose and undesirable.
Validations could be removed from the attributes and applied explicitly within each action, but that would lose a lot of the attribute metadata and be more repetitive and error prone.
I don’t see a change before_validation hook but if it existed that would be a simple fix.
It feels like I’ve been using Ash wrong but it’s unclear what the best approach is, perhaps I’m missing something obvious that I didn’t notice in the docs.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #ai
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #security
- #performance










Showing Posts 14 to 5- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
spacebat
That’s a lot to chew on, thanks I’m sure re-reading this in the morning will be fruitful. I can’t wait for the Ash book
zachdaniel
There are also docs in each action showing what the action does w/ the changeset given to it: Create Actions — ash v3.29.3
zachdaniel
We should enhance the docs here to talk about the way that changes/validations are executed. It’s documented in the changeset functions that run that logic. We should probably but it in module docs or a guide somewhere.
When I said “basic input validation” I mean that we handle the types of each attribute/argument and validate them before setting them. So in the below example, you won’t ever see
fooas10. But the changes still run on an invalid changeset.Each change and validation is run in the order they appear. When we say that “ypass validations or other action logic”, we’re referring to validations in the action.
Here is an example:
With the above construction, if you passed in
%{foo: "fred"}to the function, it would setis_fredtotrue, and the validation would fail, because the validation comes after the change.With this construction, the order of events looks like this:
So all of the above happens when you do
In the second example you get back a changeset with a hook on it.
Then it can be passed to
Ash.createto actually run the action, which runs any associated hooks.When using things like
AshGraphqlorAshJsonApior code interfaces etc. this is kind of transparent to the caller because it both builds a changeset and runs it on the spot. But Ash actions are actually two phase things, with the idea that phase 1 is cheap and repeatable. If usingAshPhoenix.Formfor example, each change runs on every keypress because we build the changeset each time.spacebat
OK, so
changeis early on but still post “basic input validation”. Is that different from the validation that the warning was about? Is there somewhere the full sequence of events is documented?zachdaniel
Nope. Change happens first. It’s the very first thing that happens. It happens on every valid
The general formula looks like this:
Anything that you do in the
changefunction happens at the very beginning after basic input validation, when you run functions likeAsh.Changeset.for_create(…).change before_action(function)is just a built in shorthand that looks like thisspacebat
That’s a bit confusing - the warning is about calling
change_attributein achange before_action, which is post validation. Isn’tchangeeven more post validation?zachdaniel
To keep that form
spacebat
All my actions are of the form:
zachdaniel
I’m not sure what you mean. Do you mean that the changes are all in anonymous functions? You can put them in change modules that
use Ash.Resource.Changespacebat
I guess that only works with custom actions each in their own module? At the moment they are all just create and update actions within the resource modules.
I noticed that the example for a custom action uses
force_change_attributeanyway: Changes — ash v3.29.3