Pass create changeset or inputs from it to Flow?

In my resource I have these two create actions:
create :create do
primary? true

accept [:price, :payment_type]

argument :property_id, :uuid, allow_nil?: false

change fn changeset, %{actor: actor} ->
Ash.Changeset.force_change_attribute(changeset, :offeror_id, actor.id)
end

change set_attribute(:property_id, arg(:property_id))
end

create :place_offer do
accept [:price, :payment_type]

argument :property_id, :uuid, allow_nil?: false

manual fn changeset, %{actor: actor} ->
%{
attributes: %{price: price, payment_type: payment_type},
arguments: %{property_id: property_id}
} = changeset

with %Ash.Flow.Result{valid?: true, result: result} <-
Flows.MakeOffer.run!(price, payment_type, property_id, actor: actor) do
{:ok, result}
else
{:error, error} ->
{:error, error}
end
end
end
create :create do
primary? true

accept [:price, :payment_type]

argument :property_id, :uuid, allow_nil?: false

change fn changeset, %{actor: actor} ->
Ash.Changeset.force_change_attribute(changeset, :offeror_id, actor.id)
end

change set_attribute(:property_id, arg(:property_id))
end

create :place_offer do
accept [:price, :payment_type]

argument :property_id, :uuid, allow_nil?: false

manual fn changeset, %{actor: actor} ->
%{
attributes: %{price: price, payment_type: payment_type},
arguments: %{property_id: property_id}
} = changeset

with %Ash.Flow.Result{valid?: true, result: result} <-
Flows.MakeOffer.run!(price, payment_type, property_id, actor: actor) do
{:ok, result}
else
{:error, error} ->
{:error, error}
end
end
end
As you can see, the place_offer action will call my flow Flows.MakeOffer. Inside that flow, I have this step:
create :make_offer, Offer, :create do
input %{
price: arg(:price),
payment_type: arg(:payment_type),
property_id: arg(:property_id)
}
end
create :make_offer, Offer, :create do
input %{
price: arg(:price),
payment_type: arg(:payment_type),
property_id: arg(:property_id)
}
end
This step calls the createaction and creates the offer. Now, is this the best/correct way to do it? What I see as probably wrong is that I'm already receiving a changeset in the place_offer create action, but I'm just getting the inputs from it and ignoring the rest because the create action will generate a new changeset later. I was thinking that maybe I should pass the changeset to the Flow, but then I'm not sure how I would pass it to the create action. What is your suggestion?
12 Replies
ZachDaniel
ZachDaniel3y ago
I think in your case you can run the flow in a before_action hook and then let the action actually submit itself
Blibs
BlibsOP3y ago
I'm gonna be honest, I'm not sure I got what you meant 😅
ZachDaniel
ZachDaniel3y ago
create :place_offer do
accept [:price, :payment_type]

argument :property_id, :uuid, allow_nil?: false

change fn changeset, %{actor: actor} ->
changeset
|> Ash.Changeset.before_action(fn changeset ->
case do_flow_stuff do
%Ash.Flow.Result{valid?: true} ->
changeset

%Ash.Flow.Result{errors: errors} ->
Ash.Changeset.add_error(changeset, errors)
end
end)
create :place_offer do
accept [:price, :payment_type]

argument :property_id, :uuid, allow_nil?: false

change fn changeset, %{actor: actor} ->
changeset
|> Ash.Changeset.before_action(fn changeset ->
case do_flow_stuff do
%Ash.Flow.Result{valid?: true} ->
changeset

%Ash.Flow.Result{errors: errors} ->
Ash.Changeset.add_error(changeset, errors)
end
end)
Errors in a before_action step will abort the transaction
Blibs
BlibsOP3y ago
So, the idea is that with this is that I can remove the create action and the step that calls it from my flow because the place_offer action will create the offer?
ZachDaniel
ZachDaniel3y ago
yep!
Blibs
BlibsOP3y ago
I see, in that case, I don't need to pass all the attributes, only the ones needed to the other steps of the flow And, in case adding the offer should be done after some specific step, then the way I did is correct?
ZachDaniel
ZachDaniel3y ago
Yeah, although there are also after_action hooks
Blibs
BlibsOP3y ago
For example, I have step 1, 2, 3 inside a transaction in my step, and step 2 is the step that should add the offer But to use the after_action in this case I would need to have 2 Flows correct? One for after and one for before
ZachDaniel
ZachDaniel3y ago
changeset
|> before_action(fn changeset ->
# stuff before
end
|> after_action(fn changeset, result ->
# stuff after
end)
changeset
|> before_action(fn changeset ->
# stuff before
end
|> after_action(fn changeset, result ->
# stuff after
end)
yes, you would 🙂 Although I don't tend to use flows just for things that wrap an action (i.e setup + side effects) But thats a perfectly valid way to use them
Blibs
BlibsOP3y ago
Got it! And, just to confirm, even if I use this, the transaction would be the same both in that action and inside the flow when using before_action and after_action correct? I thought that was the whole point of flows 😅 What do you use for that? a bunch of before/after_action calls in a changecall inside the create action?
ZachDaniel
ZachDaniel3y ago
Yes If you run the flow in a transaction already, the whole flow will be in that transaction. I use flows for like…long series of events with complex ordering of operations. Or that do “fanning out” to operate on many elements
Blibs
BlibsOP3y ago
Ah, I see, that makes sense, I will try to rewrite what I have now using change

Did you find this page helpful?