state-machines / state_machines

Adds support for creating state machines for attributes on any Ruby class
https://github.com/state-machines/state_machines
MIT License
808 stars 91 forks source link

Dynamically pick state machine? #98

Open pendletons opened 10 months ago

pendletons commented 10 months ago

Hello,

We have a multi-tenant application and would like to hard-code our state machine definitions, but be able to dynamically pick which state machine gets selected based on our tenant. I think the dynamic definition in the docs is overkill (and focuses primarily on dynamic transitions while we want the entire thing to be dynamic). Is there a nice way to say, for this class, use this state machine under condition A and use that state machine under condition B?

Ideally, we would like something along the lines of:

class Vehicle
  def initialize
    super
    state_machine = if tenant_a?
                      StateMachineA
                    else
                      StateMachineB
                    end
  end
end

module StateMachineA
  state_machine :state, initial: :parked do
    event :start_car do
      # put in gear
    end
  end
end

module StateMachineB
  state_machine :state, initial: :working do
    event :repair_car do
      # mark as broken
      # fix car
    end

    event :start_car do
      # mark as working
    end
  end
end

So that we can change the behaviour of vehicle.start_car based on which definition file we're using.

Is something like this possible?

princetechs commented 8 months ago

you can make multiple instance of your state machine or as per you,

It seems like you want to conditionally select a state machine definition for a class based on a tenant-specific condition. While the Ruby StateMachine gem doesn't provide built-in support for this specific use case, you can achieve your goal by using dynamic class methods to define state machines. Here's one way to do it:

class Vehicle
  def initialize
    super
    state_machine = if tenant_a?
                      StateMachineA.new(self)
                    else
                      StateMachineB.new(self)
                    end
  end
end

class StateMachineBase
  def initialize(vehicle)
    @vehicle = vehicle
  end
end

class StateMachineA < StateMachineBase
  def self.define
    state_machine :state, initial: :parked do
      event :start_car do
        # put in gear
      end
    end
  end
end

class StateMachineB < StateMachineBase
  def self.define
    state_machine :state, initial: :working do
      event :repair_car do
        # mark as broken
        # fix car
      end

      event :start_car do
        # mark as working
      end
    end
  end
end

In this approach:

  1. The Vehicle class initializes an instance of a state machine based on the tenant condition. Each state machine instance is associated with the specific vehicle.

  2. The StateMachineBase class is introduced to provide a common interface for state machines. It takes the vehicle as a parameter during initialization.

  3. StateMachineA and StateMachineB classes define their state machines as class methods (i.e., define). This allows you to conditionally select the state machine based on the tenant and associate it with the vehicle instance.

This way, you can dynamically select and use a state machine based on your tenant conditions without relying on a single, static definition for all instances of Vehicle. let me know if it works for you?