GithubHelp home page GithubHelp logo

Comments (21)

ifandelse avatar ifandelse commented on August 10, 2024

@mnichols thanks for asking about this. I have a few thoughts, so bear with me :-)

Passing values on transition is (in my limited experience), something I've seen done more often in purely functional setups. I actually debated about this early on in machina, and opted to roll with the current approach simply because the reality is that state is all kinds of mutable goo in JavaScript, and I felt machina would be more helpful if it simply provided utilities to organize how to act on it, rather than prevent you from creating instance level props, etc. Since machina has no opinions at all on the developer creating instance level props/methods, I lean towards not passing values on transition in order to keep the API surface area small (i.e. - there's already 2 or 3 ways to accomplish the above scenario you gave as an example, depending on your needs, and I'm hesitant to add another unless I can see a very compelling reason to do so).

Ideally, an _onEnter handler should take care of any setup/housekeeping that's internal to the FSM. For example, you might start a polling action, or subscribe to a particular stream of events (and unsubscribe in the _onExit, etc.). If I run into a situation where I think I need arguments fed to the _onEnter handler, I usually think:

1.) If the data I need as args is truly transient (and effectively originating from/dependent on outside input) it should be its own input handler.
2.) If the data I need is something that typically lives long on the FSM (through multiple states), and I really want to utilize it in an on_Enter and _onExit handler, than I stash the data locally as instance props and pull it from there.

With that in mind, there are three alternatives that come to mind (the first is along the lines of what you described:

var Machine = machina.Fsm.extend({
    initialState: 'bar',
    states: {
        bar: {
            foo: function (options) {
                this.myState = options;
                this.transition("foo");
            }
        },
        foo: {
            _onEnter: function () {
                this.thing = new MyThings(this.myState);
            }
        }
    }
};

It's also possible for the FSM to invoke one of it's own handlers after a transition:

var Machine = machina.Fsm.extend({
    initialState: 'bar',
    states: {
        bar: {
            baz: function (options) {
                if (someCondition) {
                    this.transition("foo");
                    this.handle("qux", options);
                }
            }
        },
        foo: {
            qux: function (options) {
                this.thing = new MyThings(options);
            }
        }
    }
};

Perhaps a more interesting option is the deferUntilTransition approach:

var Machine = machina.Fsm.extend({
    initialState: 'bar',
    states: {
        bar: {
            foo: function (options) {
                this.deferUntilTransition(/* optional state name arg here */);
                this.transition("foo");
            }
        },
        baz: {
            foo: function (options) {
                this.thing = new MyThings(options);
            }
        }
    }
};

One other "rule of thumb" that I find helpful is to remind myself that the _onEnter hander will be executed any time we enter that state. Setting up instances/local props in _onEnter could be a code smell (not always!). At the very least, it would be best to check if the prop is undefined before we this.something = new Thing(); - that's often where I stop and think "well, I'm obviously not going to need to new this up every time we enter the state....so maybe this belongs in a handler or the init of the FSM.

Ok - I'm done rambling for now. Hopefully that helps shed light on the opinions behind the implementation...

from machina.js.

mnichols avatar mnichols commented on August 10, 2024

Thanks Jim for your thoughtful reply.
I understand the desire to keep the api lean. So many projects get unwieldy by trying to cover every scenario.
I had considered the approaches you detailed to support our requirement.

The second example you gave is I think the most discoverable and also exposes approach to the state not only internally, but publicly (which may be good or bad depending on context).

Here are some (friendly) objections I had to these approaches:

  1. the defer approach isn't very obvious and I have (juniorish) devs that may not understand why they need to invoke two functions to make a transition to a state just to pass arguments into it.
  2. I see handle as being more the public api for a machine, while transition is for internal control; eg it is evil for a caller to invoke transition on a machine directly instead of handle.Having a machine handle itself smells funny to me unless it is for purpose of code reuse. Requiring two invocations for a transition to be made in a consistent state seems odd as a client too.
  3. I think by having _onEnter and _onExit a lifecycle of a state is implied (and supported) so it still seems odd to prevent inputs to that lifecycle's entrypoint.

Item #3 in my objections is my primary concern. Here is an realworld example that illustrates this lifecycle. This machine is modeling activities and instantiates child machines while exposing activity publicly. Here is what I want:

var Routes = machina.Fsm.extend({
    initialState: 'home'
    ,states: {
        'sign-in': {
              _onEnter: function(){
                     this.activity = new SignIn()
              }
              ,'reset-password': function(args) {
                    this.transition('reset-password',args)
               }
              ,_onExit: function(){
                     //this cleans up event listeners, etc
                     this.activity.destroy()
              }
        },
        'reset-password': {
              _onEnter: function(args) {
                   this.activity = new ResetPassword(args)
              }
              ,'sign-in': function(args) {
                    this.transition('sign-in',args)
               }
               ,_onExit: function(){
                    this.activity.destroy()
               }
         }
}

In order to achieve something similar without simply passing args into the entrypoint for a state, I am required to do this:

var Routes = machina.Fsm.extend({
    initialState: 'home'
    ,states: {
        'sign-in': {
              _onEnter: function(){
                     this.activity = new SignIn()
              }
              ,'reset-password': function(args) {
                    this._previousActivity = this.activity
                    //so temporarily i have a `different` activity than what `sign-in` implies
                    //...this is bad for clients watching `activity` for changes. 
                    //For example, consider a polling mechanism that is watching 
                    //this `activity` attribute. (angular $scope as a realworld example)
                    this.activity = new ResetPassword(args)
                    this.transition('reset-password')
               }
              ,_onExit: function(){
                     //this cleans up event listeners, etc
                     this._previousActivity.destroy()
              }
        },
        'reset-password': {

              ,'sign-in': function(args) {
                    //again, I am having to interleave `activity`
                    this._previousActivity = this.activity
                    this.activity = new SignIn(args)
                    this.transition('sign-in')
               }
               ,_onExit: function(){
                    this._previousActivity.destroy()
               }
         }
}

Another alternative is to expose an input on the target state but now I am forcing my child states/activities to be initializable in invalid states (eg, without arguments) so I can get their reference.

My primary issue though is simply clarity (the previous sample is far clearer).

If you read this far...thanks. Our team has gained alot of traction using this library and I appreciate your work and responsiveness on it. If you still decide transition shouldnt be patched to support arguments, I understand. If you decide it would be useful please let me know and I'll contribute the tested PR.

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

@mnichols quick reply just to say thanks for a detailed response. The real-world use case will def help me grok the context better. I'll try and carve out some time today or tomorrow to think through this and ping you with any other questions.

from machina.js.

arobson avatar arobson commented on August 10, 2024

Hi @mnichols, you didn't ask for my opinion or advice, but I thought I'd offer up some thoughts and suggestions for how to approach your particular use case. I don't believe any changes are necessary to machina to support your scenario and that you could gain a great deal of clarity by using the available APIs and avoid treating the onEnter method as an implicit event handler.

In reply to your points:

  1. This is a great opportunity to teach your junior developers the entire API and demonstrate how your particular use case warrants some slightly advanced functionality. Having states that receive events that they shouldn't be processing is exactly why a defer option exists.
  2. Handle is simply a way to raise an event in a consistent way. I don't quite understand why you feel it's improper for a state machine to fire events that it responds to based on the state it's in. The events being fired internal or external shouldn't really matter and it's nice that there's a single way to trigger an event (vs. having to keep internal vs. external event APIs in mind)
  3. @ifandelse should chime in, but I use onEnter and onExit for doing internal things prep or clean-up only. In your example, you need to handle an event in a different state than it occurred in (this happens a lot in my experience). Making the onEnter an implicit event handler is an opportunity to introduce confusion. This not only makes the handler for an event in state X implicit, it means that any event that causes a transition will end up using the same implicit handle for state X. The advantage of an FSM is largely clarity due to explicit handling of events per state making it much easier to think through/reason about behavior. Explained another way, at first glance, it would appear that the 'sign-in' state does not handle the 'sign-in' event. I would have to read through the entire FSM to understand what other states handle that event and transition back causing the onEnter function to handle it.

I wasn't entirely clear about your 'what I want' example. In your onEnter for the 'sign-in' state, you don't take arguments, but in your 'reset-password' state, you have a 'sign-in' event handler that passes arguments to transition back to 'sign-in' state. I don't mention this to be pedantic, but only to explain why my suggested solution to your use case might be slightly incorrect. I've also made some minor changes to how you've named things. I suggest using gerund verb-noun phrases for your states and imperative verb-noun phrases for events. This can make it easier to read through FSM code.

var Routes = machine.Fsm.extend( {
    initialState: 'signing-in',
    'signing-in': {
        'sign-in': function( args ) {
            this.activity = new SignIn();
        },
        'reset-password': function( args ) {
            this.deferUntilTransition();
            this.transition( 'resetting-password' );
        },
        _onExit: function() {
            this.activity.destroy();
        }
    },
    'resetting-password': {
        'sign-in': function( args ) {
            this.deferUntilTransition();
            this.transition( 'signing-in' );
        },
        'reset-password': function( args ) {
            this.activity = new ResetPassword( args );          
        },
        _onExit: {
            this.activity.destroy();
        }
    }
} );

I hope that's helpful or provocative enough to help the conversation and resolution along.

from machina.js.

mnichols avatar mnichols commented on August 10, 2024

Thanks @arobson for your input...more the merrier! And I always welcome advice.
The names for the machine are :

  1. actually meaningful outside from the machine (we use convention state names for features that get activated)...they map to /feature-directories (conventional coding)
  2. we use '-ing' state names to be transient states. So 'resetting-password' would perhaps be explicitly identifying an async operation and 'reset-password' is the command that is being composed. The final state might be 'password-reset'. A nice workflow is exposed that way.

I'll digress on that.

The approach you took to my problem is closer ... thanks for your time working on that. I like that it avoids nitty transient arguments. I still have an objection to requiring two invocations to transition to a state since I (the machine) 'own' the transition. Even teaching the api doesn't eliminate the fuzziness IMO.

Also it would have an odd event being exposed on the state it is targeting. So now I need to handle whether I am transitioning into this state or not (eg,should I activate a new 'activity'?):

var Routes = machine.Fsm.extend( {
    initialState: 'signing-in',
    'signing-in': {
        'sign-in': function( args ) {
            this.activity = new SignIn();
        },
        //this _is_ a valid input while 'sign-in' is active....
        'reset-password': function( args ) {
            this.deferUntilTransition();
            this.transition( 'reset-password' );
        },
        _onExit: function() {
            this.activity.destroy();
        }
    },
    'reset-password': {
        'sign-in': function( args ) {
            this.deferUntilTransition();
            this.transition( 'signing-in' );
        },
        'reset-password': function( args ) {
            this.activity = new ResetPassword( args );          
        },
        _onExit: {
            this.activity.destroy();
        }
    }
} );

As far as avoiding self-subscription, it comes down to contractual clarity and context boundaries on a component. The events (imo) are really about exposing meaningful public happenings, and not for execution flow. Usually if a instance requires some kind of self-subscription then it is a smell that behaviors need to be decomposed. Also understandability in a large system is dramatically reduced if 'events can just come from anywhere' without any boundaries.

Finally, I don't think passing args into an _onEnter makes it an 'input' handler or is any different than the machine taking arguments and getting them marshalled into the 'initialize' function. It's just a contract for the state that is explicit. 'If you wanna transition into this state, then these args are supported/required'.

from machina.js.

mnichols avatar mnichols commented on August 10, 2024

Hi I issued a PR for matter of demonstration : #41
Please don't take it to be overly assertive :)

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

@mnichols - no worries at all. I look forward to checking out the PR in a bit.

Overall - I'm still thinking this over. I want to answer a couple of points and then I'll circle back after I've done some additional research tonight.

On Code Smells...

I definitely don't think the FSM invoking handle internally is a smell. In fact, in several real-world scenarios, an entry action (_onEnter) has involved kicking off a polling or asynchronous process (for example - checking for connectivity, or initiating a remote handshake, etc.) - but the operation it kicks off really should be it's own input handler since it could in theory be invoked again while still in the same state (or another). In those cases the entry action is invoking this.handle('someInput'), etc.

Self-Subscription & Making Two Calls

I wanted to ask...when you say "self subscription" are you referring to to an FSM deferring an input to be replayed at a later state? Given my work in eventing/messaging concepts, I'm unfortunately(?) carrying conceptual baggage so that when I hear that phrase I initially thought you were referring to an FSM having to listen to its own events. Assuming you mean deferring/replaying in new state (pls correct me if I'm wrong), I'd also argue that this isn't a smell either, given the reality that events that the FSM is interested in may show up while it's in a state that shouldn't handle them, but they need to be handled at a point where the FSM can deal with them.

However - I can understand the annoyance of have to do this:

'reset-password': function( args ) {
    this.deferUntilTransition();
    this.transition( 'reset-password' );
},

At the very least, one potentially productive outcome of our discussion could be the addition of a deferAndTransition helper call that can be used when you want that sequence of actions:

// defers the input and transitions to 'resetting-password' state
// once transitioned, it replays the 'reset-password' input
'reset-password': function( args ) {
    this.deferAndTransition( 'resetting-password' );
},

I feel like @arobson's FSM example is expressive using that approach as well:

var Routes = machina.Fsm.extend( {
    initialState: 'signing-in',
    'signing-in': {
        'sign-in': function( args ) {
            this.activity = new SignIn();
        },
        'reset-password': function( args ) {
            this.deferAndTransition( 'resetting-password' );
        },
        _onExit: function() {
            this.activity.destroy();
        }
    },
    'resetting-password': {
        'sign-in': function( args ) {
            this.deferAndTransition( 'signing-in' );
        },
        'reset-password': function( args ) {
            this.activity = new ResetPassword( args );          
        },
        _onExit: {
            this.activity.destroy();
        }
    }
} );

Contractual Clarity

I actually love that we're talking along these lines (and appreciate how detailed and respectful you and @arobson have been). It's an important, and yet controversial area (b/c of the often-subjective nature of it). I won't claim to have arrived an an impartial measure of what makes something clear, but I recently watched Rich Hickey's Simple Made Easy talk, and he lays out an argument for an objective definition of "simple" that effectively boils down to "Simple means no interleaving" (and doesn't necessarily mean "easy", in the sense of "familiar"). I think the main concern at the moment is that it appears like the entry action is doubling as an input handler (I think @arobson mentioned this also) - which would be interleaving the concepts of entry action and explicit input handler.

Anyway - I'm going to do some reading and thinking. I certainly want machina to be flexible in its opinions, I just need to settle the question for myself as to whether or not introducing this change won't be in conflict with the roadmap of where things are headed...more in a bit. 😄

from machina.js.

mnichols avatar mnichols commented on August 10, 2024

Thanks for your remarks. I listened to the talk you linked to and I
understand y'all's consternation on this better. This might be a case of me
using machines for different purposes than you are.
I definitely understand your remark about avoiding 'easy' at the cost of
complecting :) the api.

On Thu, May 15, 2014 at 7:00 PM, Jim Cowart [email protected]:

@mnichols https://github.com/mnichols - no worries at all. I look
forward to checking out the PR in a bit.

Overall - I'm still thinking this over. I want to answer a couple of
points and then I'll circle back after I've done some additional research
tonight.
On Code Smells...

I definitely don't think the FSM invoking handle internally is a smell.
In fact, in several real-world scenarios, an entry action (_onEnter) has
involved kicking off a polling or asynchronous process (for example -
checking for connectivity, or initiating a remote handshake, etc.) - but
the operation it kicks off really should be it's own input handler since it
could in theory be invoked again while still in the same state (or
another). In those cases the entry action is invoking
this.handle('someInput'), etc.
Self-Subscription & Making Two Calls

I wanted to ask...when you say "self subscription" are you referring to to
an FSM deferring an input to be replayed at a later state? Given my work in
eventing/messaging concepts, I'm unfortunately(?) carrying conceptual
baggage so that when I hear that phrase I initially thought you were
referring to an FSM having to listen to its own events. Assuming you mean
deferring/replaying in new state (pls correct me if I'm wrong), I'd also
argue that this isn't a smell either, given the reality that events that
the FSM is interested in may show up while it's in a state that
shouldn't handle them, but they need to be handled at a point where the
FSM can deal with them.

However - I can understand the annoyance of have to do this:

'reset-password': function( args ) {
this.deferUntilTransition();
this.transition( 'reset-password' );
},

At the very least, one potentially productive outcome of our discussion
could be the addition of a deferAndTransition helper call that can be
used when you want that sequence of actions:

// defers the input and transitions to 'resetting-password' state
// once transitioned, it replays the 'reset-password' input
'reset-password': function( args ) {
this.deferAndTransition( 'resetting-password' );
},

I feel like @arobson https://github.com/arobson's FSM example is
expressive using that approach as well:

var Routes = machina.Fsm.extend( {

initialState: 'signing-in',
'signing-in': {
    'sign-in': function( args ) {
        this.activity = new SignIn();
    },
    'reset-password': function( args ) {

        this.deferAndTransition( 'resetting-password' );

    },
    _onExit: function() {
        this.activity.destroy();
    }
},
'resetting-password': {
    'sign-in': function( args ) {

        this.deferAndTransition( 'signing-in' );

    },
    'reset-password': function( args ) {
        this.activity = new ResetPassword( args );
    },
    _onExit: {
        this.activity.destroy();
    }
}} );

Contractual Clarity

I actually love that we're talking along these lines (and appreciate how
detailed and respectful you and @arobson https://github.com/arobsonhave been). It's an important, and yet controversial area (b/c of the
often-subjective nature of it). I won't claim to have arrived an an
impartial measure of what makes something clear, but I recently watched
Rich Hickey's Simple Made Easyhttp://www.infoq.com/presentations/Simple-Made-Easytalk, and he lays out an argument for an objective definition of "simple"
that effectively boils down to "Simple means no interleaving" (and doesn't
necessarily mean "easy", in the sense of "familiar"). I think the main
concern at the moment is that it appears like the entry action is doubling
as an input handler (I think @arobson https://github.com/arobsonmentioned this also) - which would be interleaving the concepts of entry
action and explicit input h andler.< /p>

Anyway - I'm going to do some reading and thinking. I certainly want
machina to be flexible in its opinions, I just need to settle the question
for myself as to whether or not introducing this change won't be in
conflict with the roadmap of where things are headed...more in a bit. [image:
😄]


Reply to this email directly or view it on GitHubhttps://github.com//issues/40#issuecomment-43284230
.

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

✋ (highfive) for the use of "y'all" 😄

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

@mnichols Ok I've thought this over. I'm willing to add a deferAndTransition for those instances where you're both deferring and transitioning in a handler (I've run into that use case a lot myself), but I'm not going to add support for passing args on transition at this time. I've researched FSM implementations in other languages for reference, and none of the transitions take arguments beyond from/to state (and one FSM implementation in python, IIRC, took a callback since the transition was asynchronous). If you don't mind, I'd offer one encouragement - and that is to not let concerns about lines-of-code trump the importance of the explicit nature of the API. In other words - even though I'm adding the helper method in the next version to save you from having to write the two lines of code, being explicit in both operation and API nearly always trumps opting for terseness at the expense of 'complecting'. I learned this the hard way in 2009 when I was cleverly writing C# loaded with Reflection and AOP 'magic'. (Thank God I had a good mentor or two at that job!) I can empathize with the difficulties of teaching jr devs while also still expecting them to be contributing members who aren't hand-held all day. If an extra line of code or two (or more) maintains clarity of intent in an API, they will be all the better for it - as they will understand more and more to not trade off the health of the design in favor of what appears to be developer convenience in the moment.

OK - I'll come down off the soapbox! Keep us posted on how things are going - sounds like you're working on some interesting stuff!

from machina.js.

mnichols avatar mnichols commented on August 10, 2024

Sounds good Jim. I trust your judgment on it and concede. It might be handy to update docs with either a reference to this discussion surrounding the callbacks for state lifecycle. I'd be interested in knowing the other implementations you peeked at so I can learn too.
We are using these Fsm's for a significant portion of our modeling on our quite large SPA all the way up to our routing mechanism. I see a door open towards HSM's hierarchical machines for this but haven't driven that out yet...JS extensibility along with a lib like should make it a no-brainer and solver at least a partial gripe I've had with JS 'frameworks' routing putting the cart before the horse...
Anyways, thanks for the dialog.

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

@mnichols great to hear you're thinking through potential hierarchical fsm scenarios. I've been doing the same as well, and have been playing around with possible implementation ideas...starting with an optional add on to machina, and if it worked well and made sense I'd move it to core. If you ever feel like chatting on those lines, feel free to open another issue or ping me on twitter etc.

from machina.js.

WishMasterGit avatar WishMasterGit commented on August 10, 2024

Just in case someone still need state args, I did the fork and implemented this functionality for myself here (https://github.com/WishMasterGit/machina.js). I added new kind of machine called DynamicFsm, you can instantiate it through var fsm = new machina.DynamicFsm(... . Then you can use it like this - fsm.transition("myState",data) and then in states _onEnter:function(fsm,data) or _onExit:function(fsm,data). While I agree with previous comments that this way is not best and in many scenarios goes against good practices, I found some scenarios when passing arguments to state quite good and useful and more over intuitive for developers.

from machina.js.

MichaelJCole avatar MichaelJCole commented on August 10, 2024

Hey, I'm a bit frustrated by this choice.

After reading this I still don't know what deferUntilTransition() or deferAndTransition() do. Is the defer() pattern similar to a Promise()? Would renaming them .passArgumentsToTransition() be easier to learn?

I already know how to pass arguments as parameters to a function, so preventing/reinventing that is kinda whack.

My FSM is backed by a database. The transitions create and pass MongoDB update operators to _onEnter() so it can be saved along with the state. Breaking that information into two database writes creates deadlocks. The information can't be rebuilt from just the object. If I was using another FSM implementation that didn't support passing parameters, it would be a problem there too.

Passing them on the object is clever if a bit hacky. Debugging and researching this issue was much faster than building my own FSM, but seems less than optimal.

Here's a first pass at a monkey-patch:

OpMachine = new machina.BehavioralFsm({ /* all that */});

OpMachine.changeState = function() {
  // Why not use slice()?  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
  // Why not use [] and push?  See waring on above page and https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
  if (arguments.length < 2) throw new Meteor.Error('changeState() needs 2+ arguments');
  arguments[0].__args = new Array(arguments.length-2);
  for(var i = 2; i < arguments.length; i+=1) {
    arguments[0].__args[i-2] = arguments[i];
  }
  OpMachine.transition(arguments[0], arguments[1]);
  delete arguments[0].__args;
};

Then changeState:

OpMachine.changeState(op, newState, update, select);

Then _onEnter:

OpStates = _.extend(OpStates, {
  startCountdown: {
    _onEnter: function(op) {
      var update = op.__args[0] || {};
      var select = op.__args[1] || { _id: op._id, state: op.status };  // don't clobber other changes
      delete op.__args;
      //  all the rest
    }
  }

from machina.js.

sfrooster avatar sfrooster commented on August 10, 2024

I stumbled on this thread while looking for a solution to my particular issue. I haven't read the whole thing because it diverged from my need which, as I see it, is best served by the original question - passing arguments to _onEnter.

Specifically, I have an end state "Final" at/during which no more events should be accepted. However, I do want to do some things whenever/however I arrive at this state ---> _onEnter!

Is there a best/better way to handle this? If not, I'd like to cast my vote for _onEnter args (so I don't have to add "AdditionalInfo" fields to my state)...

from machina.js.

MichaelJCole avatar MichaelJCole commented on August 10, 2024

@sfrooster I've been using the code in my previous comment for almost 2 months without problem.

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

I will finally have some time off in about a week to look into this. I've had enough folks ask about _onEnter args that I'm open to looking again :-)

Apologies for the silence...I will def follow up! It's been a crazy few months. Really appreciate the feedback and questions!

Sent from my iPhone, so just assume that auto-correct mangled my words...

On Dec 13, 2015, at 1:55 PM, Michael Cole [email protected] wrote:

@sfrooster I've been using the code in my previous comment for almost 2 months without problem.


Reply to this email directly or view it on GitHub.

from machina.js.

mnichols avatar mnichols commented on August 10, 2024

I'm the instigator of this so I'll weigh back in. :)
I think @ifandelse is still right about the decision to not include args
in this kind of handler. I didnt include args on my own FSM impl that is
based on his api (https://github.com/mnichols/possum) for the same reasons
he pushed back...it keeps things simpler especially with deferrals, etc.
machina supports separation of state from behavior now so the issue to me
is minimized now.
So I still agree the burden is on the implementing code, not the framework,
to handle it. Just my $0.02.

On Sun, Dec 13, 2015 at 12:05 PM, Jim Cowart [email protected]
wrote:

I will finally have some time off in about a week to look into this. I've
had enough folks ask about _onEnter args that I'm open to looking again :-)

Apologies for the silence...I will def follow up! It's been a crazy few
months. Really appreciate the feedback and questions!

Sent from my iPhone, so just assume that auto-correct mangled my words...

On Dec 13, 2015, at 1:55 PM, Michael Cole [email protected]
wrote:

@sfrooster I've been using the code in my previous comment for almost 2
months without problem.


Reply to this email directly or view it on GitHub.


Reply to this email directly or view it on GitHub
#40 (comment)
.

from machina.js.

MichaelJCole avatar MichaelJCole commented on August 10, 2024

@ifandelse No problem, it's open source, so same team :-) I made a fork, but it was easier to use the hack above.
@mnichols awesome that works for you! It does not work well for me. Parameters would be optional anyways, so they are win-win for everyone.

Here's my use case: my state machines are backed by a MongoDB. Because the copy in memory may have changed, I pass a select and update clause to _onEnter(). This let's the state machine selectively overwrite changes. E.g. update when { _id: 'someId', status: 'someStatus' }

To get that to onEnter, I think I need to pass parameters. Without that, I'd need to put this logic in each input function. It's hacked up using the code above. I'm open to other implementations, but parameters seemed the most logical.

OpStates = _.extend(OpStates, {
  complete: {  // a state
    _onEnter: function(op) {
console.log('===> cancelled._onEnter');
      op.__args = op.__args || [];
      var update = op.__args[0] || {};
      var select = op.__args[1] || { _id: op._id, status: op.status };  // don't clobber other changes
      delete op.__args;

      console.log('-------- FIXME charging money w00t!');

      // Run Update
      var id = op._id;
      op = OpStates.saveStatus(op, update, select);
      if (!op) throw new Meteor.Error('complete state not activated: ' + id);
    },
  },
});

from machina.js.

ifandelse avatar ifandelse commented on August 10, 2024

Bumping this thread to say that I've put together a release candidate here that introduces support for arguments being passed to _onEnter (via the transition call) in addition to some other changes and fixes. I've been asked about adding this behavior a number of times from many different people - and I'd rather err in this case on the side of providing useful functionality, especially since it's not terribly prescriptive (you don't have to use it if you don't want to). I'm giving folks a day or two to check out the new branch and then I plan to release it as a breaking 2.0 change ("breaking" because of other changes included in that branch).

from machina.js.

MichaelJCole avatar MichaelJCole commented on August 10, 2024

w00t! Thanks :-D

from machina.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.