spacebat

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.

Showing Posts 1 to 10

zachdaniel

zachdaniel

Creator of Ash

Do the before action hooks you’re using have side effects/do anything expensive? If not your best bet is to to just move the code into the body of the cnange.

zachdaniel

zachdaniel

Creator of Ash

Alternatively for actions that have to set values in before_action hooks you can set the validations to be done themselves in before action hooks with before_action?: true

Then they will happen after your logic to change attributes despite that logic being in a before_action hook :slight_smile:

spacebat

spacebat OP

No, at worst they might lazy load something, for the most part it’s things like the run_mode attribute depends on the type of another argument.

zachdaniel

zachdaniel

Creator of Ash

Right, so in that case there is likely no need for a before_action hook at all.

i.e

def change(changeset, _, _) do
  Ash.Changeset.before_action(changeset, fn changeset, _ -> 
    if changeset.arguments.whatever == "foo" do
      Ash.Changeset.change_attribute(changeset, :is_foo, true)
    else
      changeset
    end
  end)
end

can be

def change(changeset, _, _) do
  if changeset.arguments[:whatever] == "foo" do
    Ash.Changeset.change_attribute(changeset, :is_foo, true)
  else
    changeset
  end
end

The major difference you may notice is that the body of changes runs on both valid and invalid change sets, so you may want to add change YourChange, only_when_valid?: true, or an if changeset.valid? in your change implementation, or simply handle the fact that the changes may be invalid (so don’t rely on something like changeset.arguments.whatever, instead use Ash.Changeset.get_argument(changeset, :whatever), and be aware that it can be invalid.

This is good though as it allows knowing up front an action will fail and producing multiple errors at a time.

spacebat

spacebat OP

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_attribute anyway: Changes — ash v3.29.3

zachdaniel

zachdaniel

Creator of Ash

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.Change

spacebat

spacebat OP

All my actions are of the form:

actions do
  create :create do
    accept [:foo]
    argument :whatever, :integer
    change before_action(fn changeset, _ ->
      if Ash.Changeset.get_argument(changeset, :whatever) > 0 do
        Ash.Changeset.change_attribute(changeset, :foo, "positive")
      else
        changeset  
      end  
    end)
  end
end
zachdaniel

zachdaniel

Creator of Ash

To keep that form

actions do
  create :create do
    accept [:foo]
    argument :whatever, :integer
    change fn changeset, _ ->
      if Ash.Changeset.get_argument(changeset, :whatever) > 0 do
        Ash.Changeset.change_attribute(changeset, :foo, "positive")
      else
        changeset  
      end  
    end
  end
end
spacebat

spacebat OP

That’s a bit confusing - the warning is about calling change_attribute in a change before_action, which is post validation. Isn’t change even more post validation?

zachdaniel

zachdaniel

Creator of Ash

Nope. Change happens first. It’s the very first thing that happens. It happens on every valid

The general formula looks like this:

defmodule AChange do
  use Ash.Resource.Change


  def change(changeset, opts, context) do
    changeset
    |> Ash.Changeset.before_action(fn changeset ->
      # does something before the action is invoked
    end)
    |> Ash.Changeset.after_action(fn changeset, result ->
      # does something after
    end)
    |> …other hooks or modifications
  end
end

Anything that you do in the change function happens at the very beginning after basic input validation, when you run functions like Ash.Changeset.for_create(…).

change before_action(function) is just a built in shorthand that looks like this

defmodule BeforeAction do
  use Ash.Resource.Change

  def change(changeset, opts, context) do
    Ash.Changeset.before_action(changeset, fn changeset ->
      opts[:function].(changeset, context)
    end)
  end
end

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews