addressalign-toparrow-leftarrow-leftarrow-right-10x10arrow-rightbackbellblockcalendarcameraccwcheckchevron-downchevron-leftchevron-rightchevron-small-downchevron-small-leftchevron-small-rightchevron-small-upchevron-upcircle-with-checkcircle-with-crosscircle-with-pluscontroller-playcredit-cardcrossdots-three-verticaleditemptyheartexporteye-with-lineeyefacebookfolderfullheartglobe--smallglobegmailgooglegroupshelp-with-circleimageimagesinstagramFill 1languagelaunch-new-window--smalllight-bulblightning-boltlinklocation-pinlockm-swarmSearchmailmediummessagesminusmobilemoremuplabelShape 3 + Rectangle 1ShapeoutlookpersonJoin Group on CardStartprice-ribbonprintShapeShapeShapeShapeImported LayersImported LayersImported Layersshieldstar-shapestartickettrashtriangle-downtriangle-uptwitteruserwarningyahooyoutube

Re: [ruby-83] Skinny Models and Skinny Controllers using the State Machine Gem

From: Michael L.
Sent on: Thursday, February 20, 2014, 3:09 PM
My response below is a little bit of a rant against having state machine gems in Rails projects rather than critique of your post per se, but hopefully some solid points to consider (and debate?):

I don't think I would break out the state machine logic into another model and then mix it right back in unless you need to do exactly the same thing in two or more model classes.  Basically, if you're not DRYing up code, you're just obscuring and all you've done is complicate your code design with little gain to show for it other than not having to look at the state machine code every time you open the Purchase class.  Skinny models is about single concerns for any given model more so than "small" files [1] that are pleasing to read.  In the scenario demonstrated in the blog, it feels like you're just hiding "ugly DSL" from every day viewing and I'd also be concerned about the implementation being susceptible to the Anemic Domain Model anti-pattern [2] where you start separating data and processes from the object that should be responsible for it.  Do this enough throughout your whole project and you'll have a hard time piecing everything together in a debugging session when something goes sour and the whole program's no longer in your head.

I think I would reduce the Purchase model's concern to nothing more than making the payment request to the gateway and recording the state thereof.  To track what's actually being bought, I'd introduce an Order model which gets updated as "paid" upon successfully capturing the funds.  In general, you should have a rich supporting cast of models around your ordering and purchasing activities that are concerned with their own single purposes. If you have a complex set of objects to orchestrate for making a purchase, use a Builder [3] class to handle these actions as appropriate.  This approach, to me, is a lot easier to test and follow than reading a lot of DSL describing states and transitions.

One of the problems I have with state machine gems in Rails projects in general is that they tend to be injected into projects that don't need them, thus making the code a lot harder to read and follow than it needs to be.  Their use is also further complicated by also using ActiveRecord callbacks and validations on the models that make use of the model's states and this can play havoc on the state machine's callbacks, guards, and transitions.  In more than one project, I've seen this at its worst with around filters in controllers *also* doing something on the model's states where a developer essentially gave up on understanding the state-machine's DSL and pounded the proverbial square peg into the round hole to get his objective accomplished.  When all three are used together to orchestrate state transitions and actions to take transitioning to new states, it gets to be a huge testing and debugging nightmare very, very quickly, esp. for the developers that didn't put it together in the first place.



On Thu, Feb 20, 2014 at 1:20 PM, Lance Gleason <[address removed]> wrote:
Thanks for the feedback.

The constant require is a typo in the example…that should be an include…..fixing that.  Please do send out any other typos you see directly to me.

With the eval versus include,  it is wonky,  but because of how the state_machine gem is implemented it was the only way we could break out the state machine logic and then include it into the model object.  The reason for this is that when the interpreter hits the state machine dsl code it starts to modify the class it is in to add methods etc..  When you do a standard require in the module,  as soon as the module is loaded via a require the dsl has executed on the wrong object and never gets included in when you try to do the include.  I’m going to add that point to the article because that is an important point.

Lance


On Feb 20, 2014, at 12:46 PM:
Your blog post has a handful of typos that I can't comment on in the blog post. Are you sure you want to require a constant?
Why eval instead of including the state methods directly from the module? Can you effectively include more than one state machine into a class?

~Johnneylee 




--
Please Note: If you hit "REPLY", your message will be sent to everyone on this mailing list ([address removed])
This message was sent by Lance Gleason ([address removed]) from Atlanta Ruby Meetup Group.
To learn more about Lance Gleason, visit his/her member profile
To report this message or block the sender, please click here
Set my mailing list to email me As they are sent | In one daily email | Don't send me mailing list messages

Meetup, POB 4668 #37895 NY NY USA 10163 | [address removed]



--

People in this
group are also in: