Every repository with this icon (
Every repository with this icon (
| Description: | A rich DSL for describing state, and state-related behaviour, in your ruby classes / models. edit |
-
This is a known issue, but not really a bug - just a side effect of the class binding mechanism employed.
To reuse the default machine on subclasses, simply do:
Parent.machine.bind!( Child )
or for named machines:
Parent.machine(:my_machine).bind!( Child, :my_machine )
Comments
-
http://apidock.com/rails/ActiveRecord/Base/write_attribute
Apparently this is scheduled for deprecation. Look into replacing it in the AR adapter with something which isn't.
Comments
-
If I define a machine with a custom name, accessors for the states that I declare are implemented on the object itself, and not just inside the machine.
machine(:status) do
state :pending endI now have: o.pending? and o.status.pending?
Maybe this is by design, but it seems a little confusing since you can define multiple machines for a given class. What happens in the event that you have two machines in one class that have the same state?
I would think that if you have a machine with a default name then those state accessors get implemented on the main object, but for a custom named machine it would only be within the machine.
Comments
That's a valid point, and one I've thought about a little bit.
The current behaviour is that methods are never overwritten, so the
first machine defined / bound "has" the method.I didn't consider your idea of non-default machines not receiving any
methods, but let me weigh it up versus the existing implementation:Off the top of my head ... pros:
- easily described, easily understood
- no confusion over which method / machine will be triggered given a particular method call (less chance of shooting yourself in the foot)
- less methods to generate on multi-machine instances (i.e., improved performance)
cons:
- arguably less intuitive until explained - two similar things, default and named machines, act quite differently
- a substantial amount of extra typing for each method invocation (the name of the machine plus a dot)
- no helper methods available on the instance if you define a "named" / non-default machine as the only one
One way out (at the expense of adding more code to StateFu) is to
provide some additional options for machine / binding / method
definition. I may want:- to assign the default machine's binding an alias, so i can call it via e.g. .status, but still have it be the default machine (although I can just use alias_method to accomplish this manually)
- to assign an event or state a method name other than the default, especially in case of conflicts
- to prevent, or force, definition of methods for a given machine, or to give them a prefix
I'm still undecided. When I wrote the 'PokerMachine' spec in
state_fu_spec.rb, which has an identical machine for each spinning
wheel, I came across some of these issues - perhaps take a look at
that example and let me know what you think.cheers,
DavidRegarding the cons,
- I actually was surprised by the current behavior. I had not expected methods to be injected outside of the machine.
- The extra typing is kind of moot to me. It's not like lines of code are required, and it's not magic. You know exactly what you're working on.
- As long as the helper methods stay on the machine, then that would seem best, as it's contained to where it's used.
- Like you said, the field won't be created if it already exists. That seems like a lot of confusion. Am I calling a field previously defined on the object? am I calling an event/state of another machine on the object? am I calling the event/state that I actually want?
Isn't the default machine already defined as both .default, and .state_fu? Probably should be only one or the other. state_fu is probably the better name to be implemented on the object as default seems likely to be a somewhat popular field particularly in dealing with a database model.
I don't think that giving a prefix would matter much, unless maybe you had a really long machine name. Otherwise you would probably just end up with something like:
o.status_current_state vs o.status.current_state
I was able to alter the behavior by changing method_factory:181 to
define_event_methods_on( @binding.object ) if @binding.method_name == DEFAULTThree tests failed(one chameleon, and two singleton ones), but they were easily fixed as they were based on assuming that those methods would be applied to the object. So it didn't seem to break anything. You know the code better than I do, so you may know something I don't.
I don't have time right now, but I'll look at the PokerMachine spec tonight.
If you want, I can commit my changes and send you a pull request.
After looking at the PokerMachine specs I think I'm even more firmly of the opinion that only the default machine should be allowed to inject itself into the object. Any of the states/events that the wheels could put on there are made moot by the fact that there are three of them. All of which are identical Thus if you weren't familar with the class, you would have no idea which wheel's methods were the ones that were implemented. It is definitely not the method of least surprise in my mind.
I can see the desire to have the states and events bound to the object though. Maybe this is a hybrid solution...
What if the machine method took an option :bind_methods_to_object defaulting to false. Even the default machine would not automatically bind its methods to the object. I think #bind! would also need to take in the option. That would enable you to have a machine that is defined in a module or something to selectively bind the helper methods to the implementing object.
Hi,
I just pushed 0.12, and the default behaviour is now as you suggested: only events / states on the default machine will get methods defined on the object.
I called the option :define_methods ... hopefully that's a suitable compromise between being succinct and & descriptive (?). I've also added a shorthand for creating an alias for the default machine, so you can go:
machine(:as => :status) { ... }
which will define the default machine (and hence define methods on the object, unless you specify :define_methods => false), and allow you to access it via #status(). I've found myself wanting this as a convenience anyway, but the new behaviour made it more important.
Thanks for the feedback.
best regards,
David












This doesn't seem to work with modules. Is there a way to move the machine definition out into a module and load it back in?
Hi,
Hopefully this answers your question - if not, please provide a code sample so I understand better what you're intending.
It's not possible to extract a machine from a class into a module at runtime. I do have plans to make machines serializable to eg json or yml in the future, which would allow for this kind of possibility, however.
best regards,
- David
That did it. Thanks.