I love this, thank you!
After playing around different JS frameworks I'm back to Rails and I've been able to iterate quickly. That gem solves a few pain points.Reply
A lot of the comments here are rails oriented and anti state machine. In the context of rails that may make sense, but I do a lot of non rails ruby, and non object oriented Dev style, and state machines are a great way to write solid systems where the allowed state and transitions are explicit and easy to reason with.Reply
Seems to be a lot of anti-state machine comments here. Are these people trying to use state machines to model their entire application state? The example used in the article is a very practical finite state machine centered around order processing. It's very unlikely these steps in an order lifecycle are going to change significantly. I suspect a lot of organizations jumped on all-in with state machines when they got a resurgence in popularity with x-state library etc. Trying to model your entire application as a state machine just so you can show off an incomprehensible state chart or something is a recipe for disaster.Reply
I worked at a business (in ruby) where the entire core logic of the app was a state machine, as an idea the thought was good, a state machine modeled order flows pretty well, but in practice it was very slow and the amount of side effects that had to be considered as the business grew just compounded this.
It got to the point where this state machine approach was a real bottleneck and though I left before it was resolved last I heard they had to put real engineering time in to move off of it.
Be very careful when deciding to go this route.Reply
State machines are harder to update than regular code as your understanding of the domain changes, which often leads to suffering.Reply
I get how these state machine gems can sound like a great idea if what you need is taming some state changes that are just a little too much to handle by hand, but they can also delay the realization that you're doing something wrong.
State changes are hard and most involve more than object, there are usually associations that need to be taken care of, validated or maybe also transitioned to a different state, there can be a myriad of conditions and things can go wrong at any moment. And I feel that being able to define state transitions this easily, almost as an afterthought, with a simple DSL, doesn't do all this justice (just like those friendly before/after callbacks can turn into massive spaghetti abominations).
The way I do it is that any state transition more complex than checking two attributes and updating a third one is an object. Transitioning a Campaign object from :reservation to :order? That's a new PORO called CampaignOrder, with a #perform! public method or something. All the conditions, assumptions and side effects have their private methods (check_showings!, update_occupancy!). Passed a dirty record into something delicate? That's a raise, I want something clean and valid to work with. Is this something where we expect a dirty record? Go right ahead, we'll save it later in the transition, plus it will be documented right in the constructor. Does it need to raise? It will. Custom error messages on any record? Right here in this private method. Does any model need to know anything about the state transition or own any related code? Nope.Reply
Nice article. Are state machines used widely in Rails apps?Reply
I've seen a few large Rails codebases that included a state machine library like mentioned in the article. Every single one was worse because of it. It pushes the code down a path where hidden hooks run when magic methods are called. At this point in my career I'm just done with that type of "clever" code.
The article starts with examples that just define the state machine by hand. This is a much better approach and scales to larger code much better. You can grep "def start!" and just read what the method does. A state machine DSL is really not providing much value and eventually just gets in the way.Reply
Counterpoint: State machines in Ruby should be used sparingly, and only when necessary, because they start out quick and easy, but almost always grow into things that are big, ugly, and difficult to refactor or remove.
Here are somebody's notes from a Railsconf talk that made this point, plus a link to the video:
But the short version is the takeaway quote:
> State machines are magnetic : they attract more states, transitions, events, callbacks and they accumulate callback logic where our bugs hideReply