{@thread.name}

Eduardo B. Alexandre
2023-06-06

Eduardo B. Alexandre:

Is there any way to run a create/update action without persisting it to the db (I’m using postgres)?

frankdugan3:

Are you looking to just get a changeset to inspect, or do you want to persist the data in some custom way? A manual action might be what you’re looking for, as it will give you the changeset and leave what to do with it up to you: https://hexdocs.pm/ash/manual-actions.html

_ahey:

There are also generic actions available that don’t persist. https://www.ash-hq.org/docs/guides/ash/latest/topics/actions#generic-actions

Eduardo B. Alexandre:

Sorry for taking too much time to reply.

So, basically I have the following create action:

    create :create do
      alias Actions.Create.Changes

      primary? true

      change Changes.CalculateRentFields
      change Changes.CalculateFlipFields
    end

The idea is that I will pass some inputs to it and the action will calculate a bunch of other fields (via the changes) before persisting that to the DB.

The thing is that I want to show the result of these calculations before I actually persist it into the DB. I tried to just call AshPhoenix.Form.validate but that will just say that the changeset is valid, it will not actually call my changes, so I don’t get the calculations back.

In other words, I want to call that create action so I can get back the calculated fields, but I don’t want to actually persist into the DB yet since I want the user to check if the calculated fields are what they expect before actually persisting it (they do that via a LiveView page with a AshPhoenix.Form btw).

Eduardo B. Alexandre:

About using a generic or manual action, I believe that would be possible, but at the same time AFAIK I would not be able to actually get a changeset that I can send to my change modules, meaning that I would need to do some “workaround” to actualy use the same calculations in both places. I would prefer to keep using the change API if possible.

Eduardo B. Alexandre:

So, I tried to go with the manual route, this is what I have so far:

    create :create do
      manual MyManualAction
    end

defmodule MyManualAction do
  @moduledoc false

  alias Marketplace.Invoices.ProForma.Actions.Create.Changes

  use Ash.Resource.ManualCreate

  def create(changeset, opts, context) do
      changeset
      |> Changes.CalculateRentFields.change(opts, context)
      |> Changes.CalculateFlipFields.change(opts, context)
  end
end

The thing is that I’m not sure exactly how to implement that create function, I need a function that will actually apply the changeset attributes plus run the two changes I added to it (both add before_action hooks). I tried running Ash.Changeset.apply_attributes() but that one will not run my change hooks at all.

Eduardo B. Alexandre:

So, I think I figured out a workaround for it. I’m gonna be honest, I’m not very happy with it, but at least it allows me to do what I want until I can figure out a better way.

First, in my changes, I splitted my change function into 2 functions, one change/1 and another one change/3 :

defmodule Marketplace.Invoices.ProForma.Actions.Create.Changes.CalculateRentFields do
  @moduledoc """
  Calculate all fields needed for rent information
  """

  alias Ash.Changeset

  use Ash.Resource.Change

  def change(changeset, _opts, _context),
    do: Changeset.before_transaction(changeset, &change/1, append?: true)

  def change(changeset) do
    changeset
    |> calculate_rent_cost_basis()
    |> calculate_vacancy()
    ...
  end

...
end

With that, I can keep my create action the same way as before:

    create :create do
      alias Actions.Create.Changes

      primary? true

      change Changes.CalculateRentFields
      change Changes.CalculateFlipFields
    end

And I can create a new manual action:

    create :create_without_persisting do
      transaction? false

      manual MyManualAction
    end

defmodule MyManualAction do
  @moduledoc false

  alias Marketplace.Invoices.ProForma.Actions.Create.Changes

  use Ash.Resource.ManualCreate

  def create(changeset, _opts, _context) do
      changeset
      |> Changes.CalculateRentFields.change()
      |> Changes.CalculateFlipFields.change()
      |> Ash.Changeset.apply_attributes()
  end
end

Now I can just run the create_without_persisting action directly to get all the computed fields and show it in my liveview page in realtime everytime the form validation is called and the form is valid.

\ ឵឵឵:

You can make this generic with an ApplyAtrributesForAction manual action, which accepts the action to be simulated as an option.