Ricardo Tealdi blog

Yet another tech blog

Decoupling state machines from models

| Comments

Sometimes when we’re dealing with asynchronous processing with more than one step, we need to control its flow by storing states for each step to avoid processing the same step twice if it stops during the processing. Depending on the complexity of the flow, we need to create a state machine to control it.

In Ruby, I like to use the aasm gem to define state machines. It has a great DSL what makes its implementation very simple and intuitive.

However, its implementation examples force the model to be tightly coupled to the state machine, in other words, we’re adding more responsibility to our models.

Looking at this, I’ve decided to try to figure out a way to implement it without adding it to the model. The result was the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Task = Struct.new(:name, :state)

class TaskStateMachine
  require 'aasm'

  include AASM

  INITIAL = :new

  aasm do
    state(INITIAL, initial: true)
    state(:step1)
    state(:step2)

    event(:do_step1, after: :update_task) do
      transitions(from: INITIAL, to: :step1)
    end

    event(:do_step2, after: :update_task) do
      transitions(from: :step1, to: :step2)
    end
  end

  attr_accessor :task

  def initialize(task)
    @task = task
    aasm.current_state = state
  end

  def state
    task.state || INITIAL
  end

  def update_task
    task.state = aasm.to_state
    # task.save!
  end
end

Applying this idea to a model, you will have a complete implementation of a state machine, which uses the model, without being coupled to it.

Comments