davidlee / state-fu
- Source
- Commits
- Network (2)
- Issues (1)
- Downloads (16)
- Wiki (1)
- Graphs
-
Branch:
master
-
If you go out of your way to tell your model to use a database column with the same name as the state machine, model#changes will include a StateFu::Binding:
>> t.changes => {"updated_at"=>[Sun Dec 13 01:14:35 UTC 2009, Sun, 13 Dec 2009 01:14:43 UTC +00:00], "state"=>["new", # current_state=:assigned events=[:start] machine=#>]}which can't be serialized to YAML, and will break e.g. vestal_versions.
The solution is either to accept the default field name for your model (machine name with a '_field' suffix), or apply the following patch to your model / ActiveRecord:
def attribute_change(column) change = super if self.class.respond_to?(:machines) && self.class.machines.keys.include?(column.to_sym) change[1] = read_attribute(column) end change endMy advice is to stick with the default approach and use a different column name than the accessor you'll be using to get at the state machine.
Comments
-
Not sure if this is a load order problem or ActiveSupport Lite is not loading at all.
I commented out this include and it seems to load StateFu completely.
#'support/vizier',Here was the Error Trace:
/usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/support/vizier.rb:6:in `require': no such file to load -- activesupport (LoadError)from /usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/support/vizier.rb:6:in `rescue in <top (required)>' from /usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/support/vizier.rb:3:in `<top (required)>' from /usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/state-fu.rb:45:in `require' from /usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/state-fu.rb:45:in `block in <top (required)>' from /usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/state-fu.rb:19:in `each' from /usr/local/lib/ruby19/gems/1.9.1/gems/davidlee-state-fu-0.12.1/lib/state-fu.rb:19:in `<top (required)>'Can fix it by installing ActiveSupport gem, but StateFu is only thing that uses ActiveSupport in my system.
Or I can fix it by commenting out that one line. the rest of the state engine seems to be running fine, with that require of support/vizier commented out.
I am on the latest version davidlee-state-fu-0.12.1.
I was able to write a patch for my other bug, but I am not sure how to patch this one. :)
Comments
That actually turned out to be a lot simpler to fix than I thought it might be. Just a bad path in vizier after I refactored the lib dir a while ago.
This should be fixed in 0.12.3 - thanks for catching this. Not something I have test coverage for ... hmm ...
cheers,
David -
4 comments Created 3 months ago by dndState accessors are mapped onto the main object?InfoxIf 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 -
[trivial] link to rdoc.info in README needs trailing slash removed
1 comment Created 5 months ago by benkimballWith the trailing slash in place, I get a 404 from rdoc.info. Without it, I get your rdoc.
Cheers!
BenComments
-
eg:
class Foo machine(:status, :field_name => "status") do # we are waiting for someone to upload a file state :file_pending do event :upload_file, :to => :file_uploaded end # the file is shit and needs to be re-uploaded state :file_rejected do event :upload_file, :to => :file_uploaded end end endthe event :upload_file will originate only from :file_rejected, resulting in an error if it is triggered from :file_pending.
To work around this, declare the event outside a state block like so:
event :upload_file, :from => [ :file_pending, :file_rejected], :to => :file_uploaded
TODO: fix? document? warn on re-declaration?
Comments
-
This is true, and is by design. If you wish to implement auto-save behaviour, you can do so like so:
class Foo
machine do# define states here ... states do accepted { object.save! } endend end
TODO: look at implementing this behaviour as an option to the machine definition, so that you can simply declare:
machine(:save_on_transition => true) do
# ... end
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
-
eg, this is ok:
class Module::Foo
include StateFu set_table_name 'foo' machine() { state :new } endthis however, may cause a ActiveRecord::StatementInvalid to be raised:
class Module::Foo
include StateFu machine() { state :new } set_table_name 'foo' endcause yet to be determined: it may be due to interactions with other plugins / libraries in the application I'm testing it with.
Comments
-
transition requirements need to provide better information when unsatisfied
1 comment Created 7 months ago by davidleecurrently, attempting to transition when the requirements are not met raises a StateFu::RequirementError, with the only useful information being the symbol name of the failed requirement.
If for example we have an ActiveRecord model which has an event which requires :valid? (which will be true if model.valid?), the failure message will be <StateFu::RequirementError [:valid?]> - not too helpful. What we'd really like to see in this case is likely the specific validation errors/messages themselves.
We'd also like the requirements to be able to know the state being transitioned to, and have access to any arguments/options/runtime data which would be available at the time of an actual transition (ie, they should be passed a Transition context object, as event hooks are).
This is going to require some work through transition, binding and various support classes, and some spec love.
Comments
-
How do I install this gem? It seems impossible to me by looking at the Rakefile. Also you could use github to build the gem so we don't need to build it every time. Thanks
Comments
Hi oscar,
Thanks for taking the time to post a ticket.
I recently disabled the previous gem build script (which was a quick'n'dirty copy from a previous project) because it broke the rakefile on some environments. I'll redo it the Github way in the next day or two (and have github build it while i'm at it).
I've been using it not as a gem but as a git submodule / vendor lib, which I guess is why I managed to overlook the importance of a working gem for most end users ;) thanks for pointing it out.
Hi Oscar,
I've set it up to build with jeweler (http://github.com/technicalpickles/jeweler/tree/master). Please give it a whirl and let me know if you have any issues.
Github should now be building it as a gem, but I'm still waiting for it to build so i can confirm this.
cheers,
David
oscardelben
Mon May 04 00:21:16 -0700 2009
| link
Thank you davidlee ;)
- Feature▾
- Info▾
- Workaround▾
- pending▾
- Apply to Selection
-
Change Color…
Preview:preview
- Rename…
- Delete





I've added a spec & patch to cover this - it's in the 0.13.4 release.