pluginaweek / state_machine

Adds support for creating state machines for attributes on any Ruby class
http://www.pluginaweek.org
MIT License
3.74k stars 507 forks source link

Make state machines customizable with modules / aka overridable #300

Open suung opened 10 years ago

suung commented 10 years ago

It seems (to us here), that there is really no way to override / customize a state machine in order to for example remove a state and a transition.

I would not really want to open an issue, rather ask the question if there is an intended way, known tricks or what is the perspective on adding support.

I would now imagine, that adding things (states, transitions) will always work (right?) and removing not?

so maybe some dsl support for this would be possible? what would be the lower level api to be used?

thanks

the8472 commented 10 years ago

to override / customize a state machine

That's trivial. You can add new states, events, transitions, before/after handlers in a subclass. If you implement the event handlers as methods you can also use inheritance to extend/override their behavior.

remove a state and a transition.

That on the other hand is a thorny issue due to all the inter-dependence of the various elements in the state-machine

StateMachines essentially are single-inheritance. Or more specifically: copy-from-parent-class-on-modification. So generally you want to implement common behavior in an abstract superclass and add what you need in subclasses.

If you really want multiple inheritence you could easily instance-eval a proc from in the state-machine object and essentially have a 2nd call to the DSL.

Always try to add, never delete. If you have to remove something from a subclass you need to split out more behavior.

class MachineExtender

 attr_accessor :dsl

 def initialize &block
  self.dsl = block
 end

 def apply(clazz, machine = :state)
   clazz.state_machines[machine].instance_exec(&dsl)
 end
end

MyExtensionLibrary::SomeExtender = MachineExtender.new do
  state :foo
  event :bar do
   transition ...
  end
end

class MyClass

 state_machine do
   ...
 end

 MyExtensionLibrary::SomeExtender.apply(self)
end
suung commented 10 years ago

Always try to add, never delete. If you have to remove something from a subclass you need to split out more behavior.

thanks,

I fully agree with you, in case i build the application.

In fact the problem we have here is in a spreecommerce environment. In a newer version, than we use, the problem is probably solvable in a different way.

The usecase:

Remove a certain state from the order model (deliver).

When i googled 'state_machine override remove state" or something like this, i found nothing except exactly this spree commerce issue here: https://groups.google.com/forum/#!topic/spree-user/nsvxY_ISRjc

so what we will do, is propose an upgrade of spreecommerce to the client and the problem will be solved for us, but i think this example makes clear, that you cant always fully rely on a workflow like you propose.

i would still propose to have 'remove_state' 'override_state_transition' or similar

anyway thanks for the great answer! :)

chris

the8472 commented 10 years ago

Remove a certain state from the order model (deliver).

Why remove a state? Shouldn't it be sufficient to make it unreachable through transitions? Personally i have extended transitions with a few custom configuration fields, one of them allows me to disable them/mark them for internal use only.

suung commented 10 years ago

On 01/21/2014 11:44 AM, the8472 wrote:

Remove a certain state from the order model (deliver).

Why remove a state? Shouldn't it be sufficient to make it unreachable through transitions? Personally i have extended transitions with a few custom configuration fields, one of them allows me to disable them/mark them for internal use only.

This might be enough, yes, probably depends on if someone queries them.

How would that be done? Making unreachable in transitions?

the8472 commented 10 years ago

Like i said, it's custom code to do that. Alternatively you can also remove the branches from an Event object pointing to that state:

https://github.com/pluginaweek/state_machine/blob/master/lib/state_machine/event.rb#L41