As I write this article, Texas and Oklahoma are thawing out from a long ice storm. Drivers, fearing not just the ice but other brash Texans driving on it, are beginning to emerge again. My life is approaching some semblance of normalcy after three days of hibernation. I experienced a brief freeze of a different kind after switching from the Java language to Ruby. When I worked with Java projects, I could always find that special Spring library or Eclipse component to solve some niche problem. When Ruby on Rails was new, I'd often need to write it myself. Happily, that freeze too is beginning to thaw nicely, thanks to an effective plug-in architecture that thousands of people have used to extend Rails.
If you've spent any time at all with Rails, you've doubtless noticed the acts_as commands in ActiveRecord. Though ActiveRecord deals with persistence, you often want to add behavior to your classes beyond database storage and retrieval. For example, by using acts_as_tree, you can add tree-like behavior to a class with a parent_id attribute. By saying nothing more than acts_as_tree in your ActiveRecord model, you can dynamically add methods to manage the tree, such as methods that retrieve the parent record or child record. Over the past month, I've been able to find Rails plug-ins to handle voting, versioning, Ajax, composite keys, and all manner of features that basic Rails doesn't support.
Extension models in Rails, which build on top of features in the Ruby language, look dramatically different from those in the Java language. In this article, I crack open acts_as plug-ins so you can see the extension model from the inside. I provide a partial production example rather than build a toy end-to-end scenario to cover more ground and give you the flavor of real plug-ins and how they're used in real production code.
As many of you know, a state machine is a mathematical expression of a system's state. A state machine has a mixture of nodes representing states and transitions between them. At any given time, a state machine has one active state, also called a current state. Events trigger transitions between states. To illustrate the concept, I'll show an example from my current day job: development and maintenance of ChangingThePresent.org (CTP), a marketplace for nonprofits and donors (see Resources). CTP lets nonprofits submit information about their organizations and a series of gifts -- such as one hour of a cancer researcher's time or books for one student -- letting donors use a simple shopping cart to make a charitable contribution as a gift in another's name. Collecting all of that information leads to logistical problems, so I chose to simplify the workflow with a state machine.
To solve this problem, I use a third-party plug-in, written by Scott Barron, called acts_as_state_machine (see Resources). Like many Rails plug-ins, acts_as_state_machine uses a combination of Ruby capabilities and exclusive Rails features to provide not just a library, but also a domain-specific language (DSL), providing a nice experience for users.
A customer submits content to CTP (submitted state). A CTP administrator then receives the content possibly to edit (processing state). If CTP makes edits, the nonprofit should be able to approve those changes (nonprofit_reviewing state). When CTP or the nonprofit approves the content, CTP can show the content on the site (accepted state). Figure 1 is a pictorial representation of the state machine:
Figure 1. State machine for CTP
Using the plug-in, I can decorate my class object directly, using a DSL to represent the various states, transitions between them, and the events that fire those transitions. Listing 1 shows a simplified version of the state machine that I use to manage nonprofits at CTP:
Listing 1: Example state machine
class Nonprofit < ActiveRecord::Base
acts_as_state_machine :initial => :created, :column => 'status'
# These are all of the states for the existing system.
state :submitted
state :processing
state :nonprofit_reviewing
state :accepted
event :accept do
transitions :from => :processing, :to => :accepted
transitions :from => :nonprofit_reviewing, :to => :accepted
end
event :receive do
transitions :from => :submitted, :to => :processing
end
# either a CTP or nonprofit user edits the entry, requiring a review
event :send_for_review do
transitions :from => :processing, :to => :nonprofit_reviewing
transitions :from => :nonprofit_reviewing, :to => :processing
transitions :from => :accepted, :to => :nonprofit_reviewing
end
|
You might not have seen all of these Ruby features before, but the language describing the state machine flows nicely. You see descriptions of each of the states, followed by the events supported by the state machine. After each event, you see a series of transitions that each event will fire.
Each statement represents valid Ruby syntax. After the class definition, you see acts_as_state_machine :initial => :created, :column => 'status'. As a Java developer, you may find it strange to find a method invocation instead of a method definition. Ruby refers to these method invocations at the class level as macros. Ruby often uses macros to add capabilities to each class as it's loaded. In fact, method definitions -- def -- are no more than Ruby macros.
Next, you see a series of states, such as state :submitted. These are method invocations, each taking a symbol as a single parameter. (A symbol is a user-defined name.) The event command is also a method invocation, taking a symbol (which defines the event's name) and a closure, which defines the transitions.
Each transition is a method invocation followed by a hash table. In Ruby, you represent a hash map as key => value pairs, separated by commas, and {enclosed in braces}. When you use a hash map as a function call's last parameter, the braces are optional. You can see that the methods -- state, transition, and event -- combined with closures and hash maps, make a nice DSL.
To use the state machine, I can instantiate a Nonprofit object and call methods on it for each event, followed by a !, as in Listing 2:
Listing 2. Manipulating the state machine
>> np = Nonprofit.find(2) => ... >> np.current_state => :submitted >> np.receive! => true >> np.accept! => true >> np.current_state => :accepted |
The ! is a Rails convention for methods that modify and save an attribute in one step. So the requirements for the state machine plug-in are clear. I need:
- A convenient place to put the state machine code.
- A way to specify my class methods, which are required for the DSL.
- A way to attach my instance methods to
Nonprofitor any other target class.
The rest of this article walks you through the plug-in. If you want to pull down the code and follow along, download the acts_as_state_machine plug-in. (See Resources for a link to Scott Barron's site and follow his directions for getting the plug-in through Subversion.) Navigate to trunk/lib. You'll find the acts_as_state_machine.rb file. Find the initialization code in trunk/init.rb. These are the only two files you need.
In principle, all acts_as plug-ins work the same way. You always follow these steps to build an acts_as module:
- Create a module. Begin the name of a class method (your initialization macro) with
acts_as_. - In some initialization code, open the
ActiveRecordbase class and add youracts_as_module. - Extend the behavior of the target class in an
acts_as_function (for example,acts_as_state_machine).
Take a quick look at the initialization code in init.rb, shown in Listing 3:
Listing 3. Initialization code for acts_as_state_machine
require 'acts_as_state_machine' ActiveRecord::Base.class_eval do include ScottBarron::Acts::StateMachine end |
This code opens the core ActiveRecord class (ActiveRecord::Base) and adds acts_as_state_machine. The class_eval method opens the class and runs the following closure in the context of the class. Whew. That's a mouthful. In practice, the concept is simple: the code opens up the ActiveRecord base class and mixes in the ScottBarron::Acts::StateMachine module. In Ruby, you can open up any class and redefine it quickly.
This capability is one of Ruby's greatest strengths because of the increased flexibility. But the capability is a weakness too. Too much flexibility can lead to code that's difficult to understand and maintain, so be careful. Now, open the acts_as_state_machine.rb file to see what code gets mixed in.
At this point, I'm going to steer away from the details of implementing the state machine. Instead, I concentrate on exposing the interface to the state machine through the plug-in. Listing 4 shows the module definition and some of the interface of the state machine itself:
Listing 4. The module structure
module Acts #:nodoc:
module StateMachine #:nodoc:
class InvalidState < Exception #:nodoc:
end
class NoInitialState < Exception #:nodoc:
end
def self.included(base) #:nodoc:
base.extend ActMacro
end
module SupportingClasses
class State
attr_reader :name
def initialize
...
end
def entering
...
end
...
end
class StateTransition
attr_reader :from, :to, :opts
def initialize
...
end
def perform
...
end
...
end
class Event
...
def fire
...
end
def transitions
...
end
...
end
|
At the top of Listing 4, you see a nested module definition. A module has method definitions, but no base inheritance hierarchy. Instead, you can attach modules to any existing Ruby class. If the concept is new to you, think of a module as an interface plus the implementation for that interface. The nice thing about a module is that you can attach its functionality to any existing Ruby class, and you can attach as many as you want. You can also leverage a class's existing capabilities. This technique is called mixing in. C++ uses multiple inheritance to provide a similar capability, but with ugly complications. The Java founders eliminated multiple inheritance to address those complications. With modules, you can get some of the benefits of multiple inheritance without the sticky complications. Languages such as Smalltalk and Python also support mix-in inheritance.
The rest of Listing 4 shows some of the mundane details that go into implementing the state machine. You just need to know that these classes provide a stand-alone implementation of a state machine. The rest of the code is far more interesting because it deals with exposing that state machine interface to the plug-in's clients.
Recall that a plug-in author needs three things: a place to put the implementation, a way to expose the DSL (the class methods), and a way to expose the instance methods for the state machine. These include the event methods that you saw in action in Listing 3. Listing 4 provided the place to put the implementation. The next slice of code handles the DSL.
The acts_as plug-in architecture has one anchor point: the acts_as macro. Clients of the acts_as plug-in introduce this method with a method invocation in the target class. In my case, I invoke the acts_as in Listing 1 from the Nonprofit class with this line of code:
acts_as_state_machine :initial => :created, :column => 'status' |
Now take a look a Listing 5, which provides the ActMacro for acts_as_state_machine. This class handles the attributes for the module and introduces the various class and instance methods.
Listing 5. Adding acts_as
module ActMacro
# Configuration options are
#
# * +column+ - specifies the column name to use for keeping the state (default: state)
# * +initial+ - specifies an initial state for newly created objects (required)
def acts_as_state_machine(opts)
self.extend(ClassMethods)
raise NoInitialState unless opts[:initial]
write_inheritable_attribute :states, {}
write_inheritable_attribute :initial_state, opts[:initial]
write_inheritable_attribute :transition_table, {}
write_inheritable_attribute :event_table, {}
write_inheritable_attribute :state_column, opts[:column] || 'state'
class_inheritable_reader :initial_state
class_inheritable_reader :state_column
class_inheritable_reader :transition_table
class_inheritable_reader :event_table
self.send(:include, ScottBarron::Acts::StateMachine::InstanceMethods)
before_create :set_initial_state
after_create :run_initial_state_actions
end
end
|
The module in Listing 5 has a single method: acts_as_state_machine. The method does five tasks:
- Introduces class methods
- Handles state machine exceptions
- Manages attributes
- Introduces instance methods
- Handles before and after filters
The acts_as_state_machine method first introduces class methods. (You can see a precise listing of these methods in Listing 6.) Next, the method handles exceptions. In this case, the only exception occurs when the client doesn't specify an initial state. Skip the inheritable attributes briefly -- I dive into those next. The self.send method introduces instance methods. (Listing 7 shows those in detail.) Finally, the before and after filters are ActiveRecord macros that call the set_initial_state and run_initial_state_actions before and after ActiveRecord creates a record.
Go back to the write_inheritable_attribute and class_inheritable_reader macros. You may be wondering why the module doesn't use simple inheritance. The reason is simple: The module has an inheritance hierarchy of its own. These macros allow the module to project these attributes onto the target class -- Nonprofit in this example. The most important attributes are state_column and a series of transition tables containing the states, events, and transitions. Now it's time to add the class methods that form the DSL.
Adding class and instance methods
In Listing 6, you can finally see the magic introducing the DSL:
Listing 6. Class methods for acts_as_state_machine
module ClassMethods
def states
read_inheritable_attribute(:states).keys
end
def event(event, opts={}, &block)
tt = read_inheritable_attribute(:transition_table)
et = read_inheritable_attribute(:event_table)
e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, &block)
define_method("#{event.to_s}!") { e.fire(self) }
end
def state(name, opts={})
state = SupportingClasses::State.new(name.to_sym, opts)
read_inheritable_attribute(:states)[name.to_sym] = state
define_method("#{state.name}?") { current_state == state.name }
end
...
|
The event and state macros, as promised, are simple methods, defined in a module called ClassMethods. The event method reads the transition table attribute, followed by the event table attribute. The method adds the event to the event table and then dynamically defines a method for the event, wiring the new method to the fire method on event.
After the event method, the module defines the state method. This method reads the state table and adds the new state. Then, it adds a convenience method to the target class, returning true if the instance is in the current state. For example, nonprofit.submitted? would return true if the status flag were submitted. Now, the DSL is completely supported.
Instance methods work exactly like class methods. Listing 7 shows the instance method:
Listing 7. Instance methods for acts_as_state_machine
module InstanceMethods
def set_initial_state
write_attribute self.class.state_column, self.class.initial_state.to_s
end
...
def current_state
self.send(self.class.state_column).to_sym
end
...
end
|
ActMacro opens the class and adds them. There's no need to go through the read_inheritable_attribute macro to use attributes because these are class instance variables defined by ActiveRecord. I show only the methods that set the initial state and return the current state. The rest work in the same way.
The first method in Listing 7 sets the initial state, updating an existing ActiveRecord column. Recall that I set the column's name when I invoked ActMacro. The current_state method simply returns the instance variable's value. The send method invokes the method named by a single symbol parameter, which in this case is the name of the state_column.
You might think it would be simpler just to build a state machine and use it as a library. The acts_as plugin is much nicer. It lets you effectively add a state-machine column to your database. Other plug-ins let you do versioning, build in audit histories, handle images, and perform hundreds of other simple tasks, just as if those tasks were a seamless integration between the Rails environment and the database.
You may have used the Java language to integrate Eclipse plug-ins, Ant tasks, or Spring libraries into your code base or to introduce EJB components. Many ideas from the Java community changed the way that developers think about extension. This whirlwind tour of Rails acts_as plug-ins shows a new way to think about it. The flexibility of the Ruby language has changed my thinking about extensions. The acts_as plug-ins allow a new generation of developers to try their hand at writing extensions. The result is a new wave of extensions for Rails. Many of these techniques are available to Java developers too, through aspect-oriented programming or bytecode enhancement.
Next time, I'll wrap up the series with an in-depth comparison between solving a difficult problem using Ruby and my experiences on the Java platform. Until then, keep crossing borders.
Learn
- Java To Ruby: Things Every Manager Should Know (Pragmatic Bookshelf, 2006): The author's book about when and where it makes sense to make a switch from Java programming to Ruby on Rails, and how to make it.
- Beyond Java (O'Reilly, 2005): The author's book about the Java language's rise and plateau and the technologies that could challenge the Java platform in some niches.
- Plugins in Ruby on Rails: Check out the documentation for the Rails plug-in architecture.
- acts_as_state_machine: The Rails plug-in that allows an ActiveRecord model to function as a state machine.
- Changing The Present: The nonprofit marketplace, built in Ruby on Rails, that provided the example for this article.
- "Class and instance variables in Ruby" (John Nunemaker, RailsTips.org, November 2006): Dealing with class and instance variables, when you're possibly dealing with multiple inheritance, can be tricky. This article walks you through an example of a technique called inheritable attributes.
- The Java technology zone: Hundreds of articles about every aspect of Java programming.
Discuss
- Check out developerWorks blogs and get involved in the
developerWorks community.

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby and Rails: Up and Running. He spent 13 years at IBM and later formed the RapidRed consultancy, where he specialized in lightweight development strategies and architectures based on Ruby, and in the Ruby on Rails framework. He is now the CTO of WellGood LLC, a company that is forming a marketplace for nonprofit organizations and charitable giving.





