Run an create/update action without persisting the data

Is there any way to run a create/update action without persisting it to the db (I'm using postgres)?
4 Replies
frankdugan3
frankdugan32y ago
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
Alan Heywood
Alan Heywood2y ago
There are also generic actions available that don't persist. https://www.ash-hq.org/docs/guides/ash/latest/topics/actions#generic-actions
Blibs
BlibsOP2y ago
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
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). 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. 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
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. 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
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
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
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.
\ ឵឵឵
\ ឵឵឵2y ago
You can make this generic with an ApplyAtrributesForAction manual action, which accepts the action to be simulated as an option.

Did you find this page helpful?