AshStateMachine transitions from/to multiple states and no default initial state
Is there a way I can pass an array to
from:
and to:
? Right now for the latter case it only makes sense if I can explicitly pass the state to transition to in the change builtin.
Can I not specify a default initial state, and simply require that it be one of a set of initial states and not be nil
?
Tagging this Core
for now.76 Replies
from
and to
should support an array or a single item
do you get an error if you do that?Excellent
I've just pushed something up to
ash_state_machine
Nope, seems ok. Then I'm for
change transition_state(:executing, :state)
?So by default it adds and manages the
state
attribute for you
sets the default
and I just pushed the enforcement of that attribute being allow_nil? false
Alright, grabbing the update now
It determines the possible values of the state attribute by looking at all states that can be transitioned
from
and to
, as well as anything in the deprecated_states
key in the DSL (just in case)
like if you remove a possible state transition but there is still something in that state.
change transition_state(:state)
you don't specify the source state
And that change
will validate that you are making a possible state transition according to the rulesRather than
default_initial_states
, I'm hoping to provide it a list of initial_states
and have it enforce that an item must be created with one of those states.ah, yeah you can do that to
default_initial_state
just sets the default value for :state
for convenience.I was actually looking for the case of multiple resultant states, like if I had passed
Fantastic
We basically just look for any transition that allows from the current state to the next state for that action
This example should work
Aha, I see, you're not naming the transition in
transition_state
at all, rather the resultant state.
Right on, that works fine then 🙂well, in
transition
you are referring to an update action
means "the :execute
update action can move from state :waiting
to state :completed
"
It will raise an error at compile time if there is no :execute
update actionAha...ok. Is it possible to specify
transition
without naming the action?
Generally that's all I want, to be able to enforce that any state transition in any update
follows the allowed paths.🤔 not currently
but we could add it
?
Don't get me wrong, I see the value in being able to specify what each action is allowed to do, and will most likely use that as well.
Yup, that syntax looks good to me.
Yeah, FWIW I'd suggest making it explicit, but I can also see cases where that just becomes cumbersome
Just
transition from: [...], to: [...]
is nice as well, but maybe it's good to make it a bit more explicit.Yeah, I tend to avoid optional arguments in the DSL
makes things weird
We could add like
transition_any from: :waiting, to: :completed
but I think its better to be up front about the fact that transitions all happen in actions, and you're picking one/any (and maybe eventually a list)Either way is good, don't mind the asterisk.
like
transition [:action1, :action2], from: :from, to: :to
That would be excellent for sure. Think that covers most of the bases, unless you want to start having some fun and having subtractive lists as well 😉
okay, pushed up to
main
Rebuilding
Maybe, if I can see a really good use case. Wold probably have something like
prevent_transition from: :foo, to: :bar
but even still, that probably doesn't make sense
oh, I guess alongside :*
it might 😆Yeah, kind of, but I think you're probably safe to wait until somebody comes by with a compelling use-case.
Usually the utility comes from being explicit about what is allowed 😄
yeah, agreed.
Gotta scrub the duplicate checking for
:*
.ah
actually damn, thats interesting
since you can duplicate an action name, it shouldn't even require unique action names, but that also causes problems 😆
need a few to fix
I'm rewriting a 30-state tree, take your time 😄
okay, should be fixed
Right on time 🙂
One other shorthand we might consider: wildcard for states as well.
For me this would be nice for transition to terminal states, especially error states.
Oh, yeah I feel like maybe I already added something to that effect, but I may not have finished it?
generates a mermaid flow chart of your states 🙂
Noice 😄
This should be fun.
oh, thats right
So if you just don't specify
from
or to
then its wildcarded
transition :*, to: :errored
would let any action transition from any state to the errored state for exampleI'll put together a little mix task to do this for all my resources with the extension.
I'll put it in the repo actually and push it up
Right on, default wildcard works for me 🙂
Pushed.
AshStateMachine.Charts.mermaid_flowchart(Resource)
gets you the chart. You can look at how we do it in ash to generate flow charts for resources, we actually have it set up to take a format and actually produce images and things like that.
If you want to PR that to the repo if you make it that would be awesome 🙂Will do, halfway there
Alright, had to play with it a bit on my stuff 😄
Anything interesting pop up?
Did what it was supposed to
Flowcharts look good though 😄
How do you feel about adding
terminal_states
or is that somewhere?🤔 I think we could derive
terminal_states
I guess terminal_states
would be a sort of negative filter on transitions, where if you have nil
from
we don't include that state in the list of from
, and if you do include it in a from
we raise an error
I'd be open to itYep, it could be derived for sure. By definition, it shouldn't change the functionality, since any state that is terminal won't have any exit transitions.
Thinking more from a validation perspective.
Could give 'em a different box as well in Mermaid.
I think the change it would have is for things like this:
What we do under the hood is replace
:from
with every single possible state
But if you had terminal_states [:foo, :bar, :baz]
we would replace it with every single possible state -- [:foo, :bar, :baz]
Yep, think it should definitely be in then
The same applies to
initial_states
already I'm guessing.
Although that's a little bit more finicky...
A state can be initial and transitioned tohmm....
initial_states
doesn't necessarily mean...yeah what you said 🙂Although I think there's a fine line there perhaps...but right now as soon as we explicitly specify
from
anywhere it stops being wildcard, from what we've discussed.
In some ways this makes me think it would be better to have to explicitly specify from: :*
and to: :*
.Yeah, I mean the main problem is that if you have a state like
:errored
that you expect to be terminal
And you do transition :*, to: :pending_approval
you have no way to control what is in :*
in that instanceI mean, my instinct would be that it makes more sense for
initial_states
to only allow transition from nil
by default, not :*
. Whereas, right now if I don't define any from:
for my initial state it would allow transition from any state, which is not so good.Yeah, I mean maybe it just means that
from
and to
should be required?To me that makes a lot more sense. That seems more in-line wth principle of least surprise.
You can always do:
Yeah, that's true for sure. I'm less convinced about the need for
from: :*
than I am for from:
and to:
defaulting to nil
, though I still think it's a useful thing to have.🤔 what would it mean if it defaulted to
nil
?
That would effectively be a noop
because it doesn't allow any transitionsWe can also raise on unreachable states when
initial_states
and terminal_states
are specified explicitly. If they are not, it's fine to derive them, I suppose, being states whose aggregate is from: nil
and to: nil
respectively.
So, initial_states
would have from: nil
and terminal_states
would be to: nil
.
Initial states can only be transitioned into on create
by default, and terminal states can't be transitioned out of.🤔
initial_states
don't have from
and to
They just express what states are allowed for a newly created record
I'm not sure about initial_states
not being transitioned into, I think that should be done by the transition
rules
Honestly, I think at least for now I'm just going to make from
and to
required
and not support any kind of wildcarding (except on the action)
Then it just all has to be explicit, and even though slightly more verbose, it eliminates all of these confusinos
I've pushed it up to make them required. I know it might be inconvenient, but we want to start this thing at a stable place. Its easy to make things optional later, hard to make them required once people are using itI'm all good with having
from:
and to:
be required. So my thinking on this was that a resource that defines state_machine
is always in a defined, non-nil
state, meaning it would be mandatory for it to enter one of the initial_states
on create
. Is this not the case?
100% it should be possible to transition into an initial state, but only if it is explicitly specified in from:
. My point was that from:
and to:
should default to nil
(or []
), not :*
.Yeah, agreed. That’s effectively the same thing as requiring them
Requiring them to be specified explicitly of course has the same effect.
👍
So if you use the
transition_state
helper in a create action it will verify that you are transitioning to a valid initial stateOr put you in
default_initial_state
, if specified.And if you use
default_initial_state
and don’t specify one, it sets it to that and we validate at compile time that it the default initial state is also an initial stateRight on. In reality, we could not require explicit
initial_states
or terminal_states
and derive them from the states which have no incoming edges or outgoing edges, respectively. They could be specified to aid in validation. I'm not extremely in favor of this, but it's a thought since it would be possible now without defaulting to wildcard.
You would obviously need to specify initial_states
explicitly if you wanted to be able to have states with incoming edges also be initial states.
For the reasons mentioned above, I'm cool if you want to hold off on this at the beginning, particularly so because I plan to always specify them.Yeah, let’s see how it feels for a while and we can iterate. At the moment initial states is optional, but actually defaults to all states
Not states with no incoming edges
We should probably just require that too for now
Thinking it might make sense to just require it/have it default to
[]
.Have any cool flow charts you wouldn't mind sharing?
Its cool if they are private 🙂
Just curious, could be good to put in the readme to show what you can do
These ones I can't unfortunately 😦
No worries 🙂
okay, pushed up the latest
Well, this was very productive 🙂
I'm going to take the rest of the weekend off, keep in touch w/ how it goes! Its a relatively simple extension, so I'll probably cut a release of it and get it up on ash-hq in the next week or two, I don't foresee it needing that much more than it currently has, at least not for v1
Yup, looks good so far to me 🙂 Will be converting a bunch of things over the next couple days, so should have some feedback when you're back. Have an excellent rest of the weekend!
GitHub
feat: add mix task
ash_state_machine.generate_flow_charts
by bcks...Add a Mix task for generating flowcharts:
mix ash_state_machine.generate_flow_charts
Contributor checklist
Bug fixes include regression tests
Features include unit/acceptance tests
we can get that merged, see my comment
😆
Fixed
GitHub
feat: add mix task
ash_state_machine.generate_flow_charts
by bcks...Add a Mix task for generating flowcharts:
mix ash_state_machine.generate_flow_charts
Contributor checklist
Bug fixes include regression tests
Features include unit/acceptance tests
Long day
0.1.0
released with docs and example chart 🙂