Today I’m proud to announce that state_machine 1.0 has been officially released. This release marks the end of a long journey to build a library that brings the simplicity and power of state machines to Ruby and its major ORM frameworks. In case you missed it, we’re celebrating the release tonight at the Asgard Irish Pub & Restaurant in Cambridge, MA.
Individually, we are one drop. Together, we are an ocean.
In past announcements, I’ve always ended with a special thank you to everyone who contributed to the release. Today, however, I feel it’s important to highlight this project’s group of contributors first, not by name but as a whole. An enormous number of individuals (over 100!) have, directly or indirectly, contributed toward this effort. Moreover, many open-source projects have inspired several of the features now found in state_machine.
I can’t possibly express the gratitude we all have for everyone who has been involved.
A retrospective: Taking a look back
While much of what I’ve contributed to the community has come in the form of code, I thought it might be useful to take a few moments to reflect on what I’ve learned, as the core contributor of this project and a member of the Ruby community. If any of the following seems like a regurgitation of the same things other folks have spoken about before me, consider this at least a testimony to those experiences.
Fragmentation can lead to unity
Like most open-source projects, state_machine was initially built out of a need for specific functionality that was not offered at the time nor could be easily extended onto existing libraries. When it became a part of the PluginAWeek project, it was just a budding seed of the library it is today. At that time, there was a significant fragmentation of features both between the various Ruby state machine projects and within their own forks. They all had similar goals, but marched to a different beat.
To better understand the fragmentation of these libraries meant reading through Rubyforge / Github projects, project forks, and community forums to find out what features were important and how they were used. This research proved invaluable as it provided the information needed to unite of all of these fragmented ideas and assemble them under a single vision of how they could work together.
Success means sharing a single vision with an open mind
As I began to realize the potential for the project, I had a single, simple vision: to build a library that brought an array of complex state machine features into a simple, united interface and that could be officially endorsed by the major Ruby ORMs.
The former part of that vision is something that every project with even a little popularity will encounter with difficulty. As state_machine began to grow, so did the number of use cases and features that its users wanted to have. However, many of these suggestions were made with only a partial view of the world. My job was to filter through those and find out how they fit into the single vision I had for state_machine.
I discovered during this process that it was critical to address every problem or feature that a user reported. If I could not suggest the right way to address the issue, then there was a flaw in the library. That could sometimes mean spending hours, days, or weeks thinking about an issue before coming up with the right solution. Sometimes the solution was simply an explanation of why things work the way they do… other times it meant building a new feature that directly or indirectly solved the issue.
While I understand that being the single gatekeeper to this project slowed its progress significantly, I also attribute much of its success to the fact that every single piece of functionality that was added was rigorously scrutinized and made sense with the whole vision in mind.
Setting standards sets expectations
I mentioned earlier that one of the goals for this project was to build it in such a way that it could be endorsed by the major Ruby ORMs. The primary reason for this was so that the community could be provided with a standard interface for building state machines which was consistent across ORMs, similar to how the i18n or activemodel libraries have been embraced.
To that end, I set four critical standards for this project:
- Every feature must be implemented to the highest degree of quality, both from a functional and design perspective
- Every change must be backed by well-written unit tests to ensure functional quality
- A significant amount of documentation must be provided for every feature, whether part of the public or private API
- Support must be provided, at the very least, in the earlier stages of the project
Everyone’s encountered their fair share of libraries that have failed to provide at least one of the above standards. It can be a frustrating, and unnecessary, experience. You can choose to control the expectations for your project if you set simple standards like this.
Not all ORMs are created equal
It was about two and a half years ago when I made the decision to rewrite state_machine with the ability to be used within a simple Ruby class or within an ORM library like ActiveRecord or DataMapper. Since then, integration support has been added for ActiveModel, Mongoid, MongoMapper, and Sequel. The upshot of having all of these integrations is twofold: new integrations can reveal flaws in the design of your project (such as a lack of interface hooks) and they can open up a bigger audience of users. The downside is that not only are these projects moving at a rapid rate of development (which means constantly keeping up with all of them), but they vary significantly in ease of integration.
Reading through the state_machine integration for each of these ORMs can reveal a great deal about their design for extensibility. First, I have to commend the Rails folks for building ActiveModel and helping the community move to a standardized interface. This has been a huge success (as I’m sure it has with others) in helping to simplify integrations. Second, John Nunemaker and the MongoMapper developers deserve an extra mention for building the easiest library to extend. ActiveRecord and Mongoid were not much harder, but they have room to improve. (addendum: I accidentally confused the Mongoid and MongoMapper integrations the first time. I’ve since updated to indicate that MongoMapper has been the easiest)
With praise, however comes criticism (in the constructive way, of course). Of all the integrations, I found Sequel to be the most frustrating library to integrate with. It lacks many of the hooks found in other libraries and will likely be the most difficult to maintain moving forward. (addendum: Please don’t take this criticism the wrong way… I purposefully didn’t go into details here. We’ll have to work together to improve things… that’s how these things go :))
Moving targets need to be managed
Every project moves at its own pace and, at least in the post-GitHub world, that pace can be at a breakneck speed. As ORMs make significant changes to their public APIs, updates often need to be made to the associated state_machine integration as a result. Supporting multiple versions of a library can be an minor inconvenience; supporting multiple versions of multiple libraries can be a major problem.
The only way to keep up with moving targets like this is to provide a framework for managing it. In addition, it should ideally be done in a way that keeps it simple for developers and allows them to continue to get new features even for older ORM integrations. By designing each version to act like an integration itself, this allowed for new features to be added and version-specific issues to be fixed with ease even as the ORMs continued rapid development.
Open-source projects require good management… and lots of time
Open-sourcing a small library you whipped up over the weekend or while building a feature at work is easy. Managing that project as it begins to get followers and takes on a grander vision can be difficult. More than anything, I’ve discovered that the hardest part is finding the time to work on it. Granted this is the beauty of open-source, and GitHub in particular, where the masses can help build new features and fix bugs… and all I have to hit is the one-click merge button. However, it’s not always that simple.
As I mentioned earlier, a project like state_machine can require a much more hands-on management style where every change is assessed for consistency, compatibility, quality, and more. After you take into account acting as the support contact, being a core developer, and more, the time adds up.
Despite the difficulties associated with managing a project like this, it’s been completely worth it. When developing software is a hobby, it should be fun… and it is.
I’m sure there are other things we could talk about, but I feel like I’ve yapped for long enough. As a Ruby / Rails engineer, I’ve been fortunate enough to have been able to be in a job where so much of the software we take advantage of every day was built and open-sourced by our fellow engineers. I can think of no better way to pay those gestures forward than by making my own contribution back to the community.
Oh, and before I forget… hitting 1.0 wasn’t the only milestone we hit this week. As of Monday, state_machine crossed the 1,000 followers mark over at GitHub thanks to Patrick Klingemann and the 999 people before him. Awesome.
As always, be sure to follow the project on GitHub! Happy state machining!