Complex Custom Actions
How do you usually model complex operations over a model?
I have a model that needs to do some processing over their inputs, it handles everything related to subtitles, and after creation it goes over several stages of analysis of the context and such and I'm struggling to fit it inside of the Ash framework.
I was thinking to model it in two ways:
* Have an Oban job that contains that logic and operates over a
:update
or :analyzed
action.
* The problem in this case is that I cannot use AshOban to handle the scheduling, I'd need to do it manually
* Implement a manual action using Ash.Resource.ManualUpdate
, but in that case I'm a bit unsure how to handle everything, what are the best practices around ManualUpdate
and what am I giving up in exchange. I still have a bit of trouble reasoning about that.
* In that case do I need to deal with ecto directly? Or should I use other more generic actions as part of the manual action? Do you have an example of a ManualUpdate I could take a look at?Solution:Jump to solution
before_action hook or before_transaction hook depending on how costly the analyze_content() function is
13 Replies
To give a bit more context, I have this
Content
resource.
And so far I've added
The AnalyzeContent
basically does
Around this I have a couple of questions:
* if from the changeset I use change_attribute
it complains that it cannot do that since it has already been validated for the :analyze
action. What is the proper way to do this?
* Should I just create a new changeset?
* Do I give up on anything (apart from having to write things myself) by doing a custom action?
I see internally the set_attribute
change also does force_change_attribute
so I guess it's expected?
https://github.com/ash-project/ash/blob/v3.5.10/lib/ash/resource/change/set_attribute.ex#L71-L75
So, like this it worked properly
So one thing I discovered is that if I use the same changeset, then there is infinite recursion because when I call Ash.update
it seems like it calls itself, because it is ready for itself. (through for_update(:analyze))
The end result was that this function was being called indefinitely.
So I need to create a new Changeset
, however for some reason if I did
It didn't work, and I'm unsure why. I had to use the for_update
for it to work.
Another question I have is, can I not use other changes when I use a manual action?
I am using state machine, and tried to do
This never does the transition_state and it doesn't complain either, I guess I'd expect one or the other.For the last question, you're looking for run and a change module. Using the
manual
opt says you are delgating to your own Update action implementation, so no other changes are applied. While run
takes an anon function or change module to run a change on the changeset, so other changes steps can be applied when using run
why do you think you need a ManualUpdate?
As for the
Ash.Changeset.new
vs Ash.Changeset.for_update
, the typical pattern is to use for_update
and the name of the update action you are applying.I do not think I need a Manual Update, my question is "do I need a Manual Update?", what other ways could I have modeled this that are the most Ash-y
The warning you see because of change_attribute is only to warn you that the validtion aren't run against the changes you make to the changes at this point in the lifecycle
Solution
before_action hook or before_transaction hook depending on how costly the analyze_content() function is
and why do you think you can't do the scheduling with AshOban?
hmmm, it's quite costly. So you would model it as a beforeaction hook for the
:analyze
action?
With triggers I couldn't find a way to trigger a job I wanted instead of a job that wrapped an action, maybe I missed something
aha! I hadn't seen the whole hooks stuff yet, I was wondering how to run arbitrary code in the action
```elixir
defmodule AshChangesetLifeCycleExample do
def change(changeset, , _) do
changeset
# execute code both before and after the transaction
|> Ash.Changeset.around_transaction(fn changeset, callback ->
callback.(changeset)
end)
# execute code before the transaction is started. Use for things like external calls
|> Ash.Changeset.before_transaction(fn changeset -> changeset end)
# execute code in the transaction, before and after the data layer is called
|> Ash.Changeset.around_action(fn changeset, callback ->
callback.(changeset)
end)
# execute code in the transaction, before the data layer is called
|> Ash.Changeset.before_action(fn changeset -> changeset end)
# execute code in the transaction, after the data layer is called, only if the action is successful
|> Ash.Changeset.after_action(fn changeset, result -> {:ok, result} end)
# execute code after the transaction, both in success and error cases
|> Ash.Changeset.after_transaction(fn changeset, success_or_error_result -> success_or_error_result end
end
end
```
this makes it clearer
thanks @barnabasj and @Chaz Watkins !
does this work well with bulk actions? I've been burned before by hooks in Rails, does it share the same kind of footguns?You’ll need to add ‘batch_change/3’ callback and apply the change function to all changesets.
@Chaz Watkins we gotta get that complex actions guide done 😂
Okay, the end result is something like this, this is a lot more manageable, thanks 😄
😅