Here is my ash_state_machine :
# start -> send_next_question -> send_results -> stop
state_machine do
initial_states([:start])
default_initial_state(:start)
transitions do
transition(:begin_quiz, from: :start, to: :send_next_question)
transition(:continue_quiz, from: :send_next_question, to: :send_next_question)
transition(:process_results, from: :send_next_question, to: :send_results)
transition(:error, from: [:start, :send_next_question, :send_results], to: :stop)
end
end
attributes do
uuid_primary_key :id
# default id should be there.
attribute :quiz_id, :uuid, allow_nil?: false
attribute :user_id, :uuid, allow_nil?: false
attribute :quiz, :map, allow_nil?: true
attribute :user, :map, allow_nil?: true
# reponse for the question
attribute :current_response, :string, allow_nil?: true
attribute :previous_question, :uuid, allow_nil?: true
attribute :next_question, :uuid, allow_nil?: true
# show errors
attribute :error, :string
attribute :error_state, :string
# :state attribute is added for you by `state_machine`
end
Here is how I think it should work:
-
since there is a state send_next_question
which will transition to itself (with delay of 1s) until all the quiz.questions
becomes empty list.
-
when quiz.questions
becomes empty, send_next_question
goes to send_results
.
-
ash_oban triggers a job each time send_next_question
state is achieved.
-
ash_oban triggers a job when send_results
state is achieved.
a high level idea of implementing it wouldbe helpful.
1 Like
I think AshOban
will not be the best case for this. I think you will want a GenServer
handling the state of the resource, and transitioning from state to state. When a quiz is started (or perhaps resumed on reconnection) you can load up the relevant state machine and begin interacting with it. Generally speaking, less than one minute feedback loops aren’t the best fit for Oban/AshOban in my experience.
AshOban triggers can be triggered in one(or both) of two ways:
trigger :send_next_question do
scheduler_cron "* * * * * *" # trigger for any matches every one minute
where expr(state == :send_next_question)
end
...
# schedule the next invocation automatically
update :send_next_question do
change run_oban_trigger(:send_next_question)
# or in a custom change `AshOban.run_trugger`
end
For that latter case, we don’t have a run_trigger_later
, but we could add one or you could hand roll one pretty easily. When you combine both of these, typically the scheduler_cron
and where
act as a backup in case something goes wrong triggering the actions. You can also only use the second strategy, and pass scheduler_cron nil
to not automatically check for matches and trigger the action.
However, this is going to amount to a lot of database back and forth during what looks to be intended to be a pretty tight loop. Your user would likely have a better experience if the quiz flow was designed as a GenServer I think. I can see a case for having tooling for this kind of thing built into ash_state_machine
, i.e some state machines should be statable
, keep track of some in memory state, and have an accompanying module that handles state transitions in memory or something. Maybe
1 Like
I agree on the GenServer part since wait time is less than a minute.
Nonetheless, this gives a good idea on how ash_oban and ash_state_machine work together.
Will manage the genserver for now
2 Likes