Permalink
Browse files

Completely rewritten from scratch

Renamed to state_machine
Removed database dependencies
Removed models in favor of an attribute-agnostic design
Use ActiveSupport::Callbacks instead of eval_call
Remove dry_transaction_rollbacks dependencies
Added functional tests
Updated documentation
  • Loading branch information...
1 parent 1c25749 commit f3565041379fdcfd94cce18438c361e942bcdddb @obrie obrie committed May 4, 2008
Showing with 1,921 additions and 3,013 deletions.
  1. +18 −0 CHANGELOG
  2. +1 −1 MIT-LICENSE
  3. +53 −46 README
  4. +11 −14 Rakefile
  5. +0 −32 app/models/event.rb
  6. +0 −42 app/models/state.rb
  7. +0 −36 app/models/state_change.rb
  8. +0 −14 db/migrate/001_create_states.rb
  9. +0 −14 db/migrate/002_create_events.rb
  10. +0 −16 db/migrate/003_create_state_changes.rb
  11. +1 −1 init.rb
  12. +0 −505 lib/has_states.rb
  13. +0 −137 lib/has_states/active_event.rb
  14. +0 −102 lib/has_states/active_state.rb
  15. +0 −70 lib/has_states/state_transition.rb
  16. +92 −0 lib/state_machine.rb
  17. +127 −0 lib/state_machine/event.rb
  18. +141 −0 lib/state_machine/machine.rb
  19. +75 −0 lib/state_machine/transition.rb
  20. +23 −13 test/app_root/app/models/auto_shop.rb
  21. +17 −17 test/app_root/app/models/car.rb
  22. +0 −9 test/app_root/app/models/message.rb
  23. +2 −2 test/app_root/app/models/motorcycle.rb
  24. +0 −6 test/app_root/app/models/project.rb
  25. +6 −48 test/app_root/app/models/switch.rb
  26. +0 −3 test/app_root/app/models/task.rb
  27. +2 −0 test/app_root/app/models/toggle_switch.rb
  28. +59 −59 test/app_root/app/models/vehicle.rb
  29. +0 −27 test/app_root/config/environment.rb
  30. +3 −6 test/app_root/db/migrate/001_create_switches.rb
  31. +4 −7 test/app_root/db/migrate/002_create_auto_shops.rb
  32. +2 −2 test/app_root/db/migrate/003_create_highways.rb
  33. +7 −10 test/app_root/db/migrate/004_create_vehicles.rb
  34. +0 −15 test/app_root/db/migrate/005_create_messages.rb
  35. +0 −15 test/app_root/db/migrate/006_create_projects.rb
  36. +0 −15 test/app_root/db/migrate/007_create_tasks.rb
  37. +61 −0 test/factory.rb
  38. +0 −11 test/fixtures/auto_shops.yml
  39. +0 −94 test/fixtures/events.yml
  40. +0 −3 test/fixtures/highways.yml
  41. +0 −4 test/fixtures/messages.yml
  42. +0 −4 test/fixtures/projects.yml
  43. +0 −142 test/fixtures/state_changes.yml
  44. +0 −94 test/fixtures/states.yml
  45. +0 −4 test/fixtures/switches.yml
  46. +0 −44 test/fixtures/vehicles.yml
  47. +443 −0 test/functional/state_machine_test.rb
  48. +4 −16 test/test_helper.rb
  49. +0 −297 test/unit/active_event_test.rb
  50. +0 −65 test/unit/active_state_test.rb
  51. +0 −7 test/unit/event_not_active_test.rb
  52. +0 −7 test/unit/event_not_found_test.rb
  53. +217 −23 test/unit/event_test.rb
  54. +0 −622 test/unit/has_states_test.rb
  55. +152 −0 test/unit/machine_test.rb
  56. +0 −7 test/unit/no_initial_state_test.rb
  57. +0 −7 test/unit/state_already_active_test.rb
  58. +0 −65 test/unit/state_change_test.rb
  59. +102 −0 test/unit/state_machine_test.rb
  60. +0 −7 test/unit/state_not_active_test.rb
  61. +0 −7 test/unit/state_not_found_test.rb
  62. +0 −50 test/unit/state_test.rb
  63. +0 −149 test/unit/state_transition_test.rb
  64. +298 −0 test/unit/transition_test.rb
View
@@ -1,5 +1,23 @@
*SVN*
+*0.1.0* (May 5th, 2008)
+
+* Completely rewritten from scratch
+
+* Renamed to state_machine
+
+* Removed database dependencies
+
+* Removed models in favor of an attribute-agnostic design
+
+* Use ActiveSupport::Callbacks instead of eval_call
+
+* Remove dry_transaction_rollbacks dependencies
+
+* Added functional tests
+
+* Updated documentation
+
*0.0.1* (September 26th, 2007)
* Add dependency on custom_callbacks
View
@@ -1,4 +1,4 @@
-Copyright (c) 2006 Scott Barron, 2006-2007 Aaron Pfefier & Neil Abraham
+Copyright (c) 2006 Scott Barron, 2006-2008 Aaron Pfefier
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
@@ -1,29 +1,24 @@
-== has_states
+== state_machine
-+has_states+ adds support for managing states, events, and transitions within a
-model.
++state_machine+ support for creating state machines for attributes within a model.
== Resources
-Announcement
-
-* http://www.pluginaweek.org
-
Wiki
-* http://wiki.pluginaweek.org/Has_states
+* http://wiki.pluginaweek.org/State_machine
API
-* http://api.pluginaweek.org/has_states
+* http://api.pluginaweek.org/state_machine
Development
-* http://dev.pluginaweek.org/browser/trunk/plugins/active_record/has/has_states
+* http://dev.pluginaweek.org/browser/trunk/state_machine
Source
-* http://svn.pluginaweek.org/trunk/plugins/active_record/has/has_states
+* http://svn.pluginaweek.org/trunk/state_machine
== Description
@@ -33,30 +28,53 @@ and deciding how to behave based on the values in those columns. This can becom
cumbersome and difficult to maintain when the complexity of your models starts to
increase.
-+has_states+ simplifies this design by introducing the various parts of a state
++state_machine+ simplifies this design by introducing the various parts of a state
machine, including states, events, and transitions. However, its api is designed
to be similar to ActiveRecord in terms of validations and callbacks, making it
so simple you don't even need to know what a state machine is :)
== Usage
-=== Running migrations
-
-To migrate the tables required for has_states, you can either run the
-migration from the command line like so:
-
- rake db:migrate:plugins PLUGIN=has_states
-
-or (more ideally) generate a migration file that will integrate into your main
-application's migration path:
-
- ruby script/generate plugin_migration has_states
-
-== Testing
-
-Before you can run any tests, the following gems must be installed:
-* plugin_test_helper[http://wiki.pluginaweek.org/Plugin_test_helper]
-* dry_validity_assertions[http://wiki.pluginaweek.org/Dry_validity_assertions]
+=== Example
+
+ class Vehicle < ActiveRecord::Base
+ state_machine :state, :initial => 'idling' do
+ before_exit 'parked', :put_on_seatbelt
+ after_enter 'parked', Proc.new {|vehicle| vehicle.update_attribute(:seatbelt_on, false)}
+
+ event :park do
+ transition :to => 'parked', :from => %w(idling first_gear)
+ end
+
+ event :ignite do
+ transition :to => 'stalled', :from => 'stalled'
+ transition :to => 'idling', :from => 'parked'
+ end
+
+ event :idle do
+ transition :to => 'idling', :from => 'first_gear'
+ end
+
+ event :shift_up do
+ transition :to => 'first_gear', :from => 'idling'
+ transition :to => 'second_gear', :from => 'first_gear'
+ transition :to => 'third_gear', :from => 'second_gear'
+ end
+
+ event :shift_down do
+ transition :to => 'second_gear', :from => 'third_gear'
+ transition :to => 'first_gear', :from => 'second_gear'
+ end
+
+ event :crash, :after => :tow! do
+ transition :to => 'stalled', :from => %w(first_gear second_gear third_gear), :unless => :auto_shop_busy?
+ end
+
+ event :repair, :after => :fix! do
+ transition :to => 'parked', :from => 'stalled', :if => :auto_shop_busy?
+ end
+ end
+ end
== Tools
@@ -67,23 +85,12 @@ events for your models. It is cross-platform, written in Java.
== Dependencies
-This plugin depends on the presence of the following plugins:
-* class_associations[http://wiki.pluginaweek.org/Class_associations]
-* custom_callbacks[http://wiki.pluginaweek.org/Custom_callbacks]
-* dry_transaction_rollbacks[http://wiki.pluginaweek.org/Dry_transaction_callbacks]
-* eval_call[http://wiki.pluginaweek.org/Eval_call]
-
-This plugin is also plugin+. That means that it contains a slice of an
-application, such as models and migrations. To test or use a plugin+, you
-must have the following plugins/gems installed:
-* plugin_dependencies[http://wiki.pluginaweek.org/Plugin_dependencies]
-* loaded_plugins[http://wiki.pluginaweek.org/Loaded_plugins]
-* appable_plugins[http://wiki.pluginaweek.org/Appable_plugins]
-* plugin_migrations[http://wiki.pluginaweek.org/Plugin_migrations]
-
-Instead of installing each individual plugin+ feature, you can install them all
-at once using the plugins+[http://wiki.pluginaweek.org/Plugins_plus] meta package,
-which contains all additional features.
+None.
+
+== Testing
+
+Before you can run any tests, the following gem must be installed:
+* plugin_test_helper[http://wiki.pluginaweek.org/Plugin_test_helper]
== References
View
@@ -3,25 +3,25 @@ require 'rake/rdoctask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
-PKG_NAME = 'has_states'
-PKG_VERSION = '0.0.1'
+PKG_NAME = 'state_machine'
+PKG_VERSION = '0.1.0'
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RUBY_FORGE_PROJECT = 'pluginaweek'
desc 'Default: run unit tests.'
task :default => :test
-desc 'Test the has_states plugin.'
+desc 'Test the state_machine plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
- t.pattern = 'test/unit/**/*_test.rb'
+ t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
-desc 'Generate documentation for the has_states plugin.'
+desc 'Generate documentation for the state_machine plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
- rdoc.title = 'HasStates'
+ rdoc.title = 'StateMachine'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
@@ -31,18 +31,15 @@ spec = Gem::Specification.new do |s|
s.name = PKG_NAME
s.version = PKG_VERSION
s.platform = Gem::Platform::RUBY
- s.summary = 'Adds support for managing states, events, and transitions within a model'
+ s.summary = 'Adds support for creating state machines for attributes within a model'
- s.files = FileList['{app,db,lib,test}/**/*'].to_a + %w(CHANGELOG init.rb MIT-LICENSE Rakefile README)
+ s.files = FileList['{lib,test}/**/*'].to_a + %w(CHANGELOG init.rb MIT-LICENSE Rakefile README)
s.require_path = 'lib'
- s.autorequire = 'has_states'
+ s.autorequire = 'state_machine'
s.has_rdoc = true
- s.test_files = Dir['test/unit/**/*_test.rb']
- s.add_dependency 'class_associations', '>= 0.0.1'
- s.add_dependency 'custom_callbacks', '>= 0.0.1'
- s.add_dependency 'eval_call', '>= 0.0.1'
+ s.test_files = Dir['test/**/*_test.rb']
- s.author = 'Aaron Pfeifer, Neil Abraham'
+ s.author = 'Aaron Pfeifer'
s.email = 'info@pluginaweek.org'
s.homepage = 'http://www.pluginaweek.org'
end
View
@@ -1,32 +0,0 @@
-# An event is an observable stimulus, response, or action. The invociation of
-# an event can result in the transition from one state to another.
-#
-# == State Changes
-#
-# All events track when they were invoked through instances of the StateChange
-# model. You can access these state changes like so:
-#
-# >> e = Event.find_by_name('ignite')
-# => #<Event:0x2b6f911b6710 @attributes={"name"=>"ignite", "id"=>"302", "human_name"=>nil, "owner_type"=>"Vehicle"}>
-# >> e.state_changes
-# => [#<StateChange:0x2b6f91190e70 @attributes={"event_id"=>"302", "from_state_id"=>"301", "stateful_type"=>"Vehicle", "id"=>"3", "to_state_id"=>"302", "occurred_at"=>"2007-08-22 16:10:41", "stateful_id"=>"2"}>, ...]
-class Event < ActiveRecord::Base
- has_many :state_changes,
- :order => 'occurred_at ASC',
- :dependent => :destroy
-
- validates_presence_of :name
- validates_uniqueness_of :name,
- :scope => :owner_type
-
- # A humanized version of the name
- def human_name
- read_attribute(:human_name) || name.to_s.titleize
- end
-
- # The symbolic name of the event
- def to_sym
- name = read_attribute(:name)
- name ? name.to_sym : name
- end
-end
View
@@ -1,42 +0,0 @@
-# A state represents a phase in the lifetime of a machine. The state of a
-# machine is usually what drives the functionality of systems external to the
-# machine.
-#
-# == State Changes
-#
-# All states track when they were transitioned *to* and *from* through instances
-# of the StateChange model. You can access these state changes like so:
-#
-# >> s = State.find_by_name('parked')
-# => #<State:0x2b6f91183810 @attributes={"name"=>"parked", "id"=>"301", "human_name"=>nil, "owner_type"=>"Vehicle"}>
-# >> s.changes_from
-# => [#<StateChange:0x2b6f91176020 @attributes={"event_id"=>"302", "from_state_id"=>"301", "stateful_type"=>"Vehicle", "id"=>"3", "to_state_id"=>"302", "occurred_at"=>"2007-08-22 16:10:41", "stateful_id"=>"2"}>, ...]
-# >> s.changes_to
-# => [#<StateChange:0x2b6f9116e258 @attributes={"event_id"=>nil, "from_state_id"=>nil, "stateful_type"=>"Vehicle", "id"=>"1", "to_state_id"=>"301", "occurred_at"=>"2007-08-22 16:10:41", "stateful_id"=>"1"}>, ...]
-class State < ActiveRecord::Base
- has_many :changes_from,
- :class_name => 'StateChange',
- :foreign_key => 'from_state_id',
- :order => 'occurred_at ASC',
- :dependent => :destroy
- has_many :changes_to,
- :class_name => 'StateChange',
- :foreign_key => 'to_state_id',
- :order => 'occurred_at ASC',
- :dependent => :destroy
-
- validates_presence_of :name
- validates_uniqueness_of :name,
- :scope => :owner_type
-
- # A humanized version of the name
- def human_name
- read_attribute(:human_name) || name.to_s.titleize
- end
-
- # The symbolic name of the state
- def to_sym
- name = read_attribute(:name)
- name ? name.to_sym : nil
- end
-end
@@ -1,36 +0,0 @@
-# Represents a change from one state to another via a stimulus (event). A state
-# change may be a loopback, in which case the from state and to state are the
-# same.
-#
-# == Timestamps
-#
-# Every state change is timestamped with the +occurred_at+ attribute. Since
-# this is not the standard name for timestamps (such as +updated_at+ or
-# +created_at+), there is a create hook which will automatically set the value for
-# +occurred_at+.
-class StateChange < ActiveRecord::Base
- belongs_to :event
- belongs_to :from_state,
- :class_name => 'State',
- :foreign_key => 'from_state_id'
- belongs_to :to_state,
- :class_name => 'State',
- :foreign_key => 'to_state_id'
- belongs_to :stateful,
- :polymorphic => true
-
- validates_presence_of :stateful_id,
- :stateful_type,
- :to_state_id
-
- def create_with_custom_timestamps #:nodoc:
- # Record when the state change occurred if this model is enabled for
- # timestamps
- if record_timestamps
- occurred_at = self.class.default_timezone == :utc ? Time.now.utc : Time.now
- write_attribute('occurred_at', occurred_at)
- end
- create_without_custom_timestamps
- end
- alias_method_chain :create, :custom_timestamps
-end
@@ -1,14 +0,0 @@
-class CreateStates < ActiveRecord::Migration
- def self.up
- create_table :states do |t|
- t.column :name, :string, :null => false
- t.column :human_name, :string
- t.column :owner_type, :string, :null => false
- end
- add_index :states, [:name, :owner_type], :unique => true
- end
-
- def self.down
- drop_table :states
- end
-end
@@ -1,14 +0,0 @@
-class CreateEvents < ActiveRecord::Migration
- def self.up
- create_table :events do |t|
- t.column :name, :string, :null => false
- t.column :human_name, :string
- t.column :owner_type, :string, :null => false
- end
- add_index :events, [:name, :owner_type], :unique => true
- end
-
- def self.down
- drop_table :events
- end
-end
@@ -1,16 +0,0 @@
-class CreateStateChanges < ActiveRecord::Migration
- def self.up
- create_table :state_changes do |t|
- t.column :stateful_id, :integer, :null => false, :references => nil
- t.column :stateful_type, :string, :null => false
- t.column :from_state_id, :integer, :references => :states
- t.column :to_state_id, :integer, :null => false, :references => :states
- t.column :event_id, :integer
- t.column :occurred_at, :timestamp, :null => false
- end
- end
-
- def self.down
- drop_table :state_changes
- end
-end
View
@@ -1 +1 @@
-require 'has_states'
+require 'state_machine'
Oops, something went wrong.

0 comments on commit f356504

Please sign in to comment.