Skip to content
This repository
Browse code

Add simple support for ActiveModel's StateMachine for ActiveRecord

  • Loading branch information...
commit aad5a30bf25d8a3167afd685fc91c99f4f09cc57 1 parent 55d1d12
Joshua Peek authored August 04, 2009
7  activemodel/lib/active_model/state_machine.rb
@@ -5,12 +5,9 @@ module StateMachine
5 5
     autoload :State, 'active_model/state_machine/state'
6 6
     autoload :StateTransition, 'active_model/state_machine/state_transition'
7 7
 
8  
-    class InvalidTransition < Exception
9  
-    end
  8
+    extend ActiveSupport::Concern
10 9
 
11  
-    def self.included(base)
12  
-      require 'active_model/state_machine/machine'
13  
-      base.extend ClassMethods
  10
+    class InvalidTransition < Exception
14 11
     end
15 12
 
16 13
     module ClassMethods
12  activemodel/lib/active_model/state_machine/event.rb
... ...
@@ -1,5 +1,3 @@
1  
-require 'active_model/state_machine/state_transition'
2  
-
3 1
 module ActiveModel
4 2
   module StateMachine
5 3
     class Event
@@ -53,12 +51,12 @@ def update(options = {}, &block)
53 51
         self
54 52
       end
55 53
 
56  
-    private
57  
-      def transitions(trans_opts)
58  
-        Array(trans_opts[:from]).each do |s|
59  
-          @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
  54
+      private
  55
+        def transitions(trans_opts)
  56
+          Array(trans_opts[:from]).each do |s|
  57
+            @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
  58
+          end
60 59
         end
61  
-      end
62 60
     end
63 61
   end
64 62
 end
29  activemodel/lib/active_model/state_machine/machine.rb
... ...
@@ -1,6 +1,3 @@
1  
-require 'active_model/state_machine/state'
2  
-require 'active_model/state_machine/event'
3  
-
4 1
 module ActiveModel
5 2
   module StateMachine
6 3
     class Machine
@@ -57,22 +54,22 @@ def current_state_variable
57 54
         "@#{@name}_current_state"
58 55
       end
59 56
 
60  
-    private
61  
-      def state(name, options = {})
62  
-        @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
63  
-      end
  57
+      private
  58
+        def state(name, options = {})
  59
+          @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
  60
+        end
64 61
 
65  
-      def event(name, options = {}, &block)
66  
-        (@events[name] ||= Event.new(self, name)).update(options, &block)
67  
-      end
  62
+        def event(name, options = {}, &block)
  63
+          (@events[name] ||= Event.new(self, name)).update(options, &block)
  64
+        end
68 65
 
69  
-      def event_fired_callback
70  
-        @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
71  
-      end
  66
+        def event_fired_callback
  67
+          @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
  68
+        end
72 69
 
73  
-      def event_failed_callback
74  
-        @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
75  
-      end
  70
+        def event_failed_callback
  71
+          @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
  72
+        end
76 73
     end
77 74
   end
78 75
 end
2  activemodel/lib/active_model/state_machine/state_transition.rb
@@ -18,7 +18,7 @@ def perform(obj)
18 18
           true
19 19
         end
20 20
       end
21  
-      
  21
+
22 22
       def execute(obj, *args)
23 23
         case @on_transition
24 24
         when Symbol, String
1  activerecord/lib/active_record.rb
@@ -65,6 +65,7 @@ def self.load_all!
65 65
   autoload :SchemaDumper, 'active_record/schema_dumper'
66 66
   autoload :Serialization, 'active_record/serialization'
67 67
   autoload :SessionStore, 'active_record/session_store'
  68
+  autoload :StateMachine, 'active_record/state_machine'
68 69
   autoload :TestCase, 'active_record/test_case'
69 70
   autoload :Timestamp, 'active_record/timestamp'
70 71
   autoload :Transactions, 'active_record/transactions'
24  activerecord/lib/active_record/state_machine.rb
... ...
@@ -0,0 +1,24 @@
  1
+module ActiveRecord
  2
+  module StateMachine #:nodoc:
  3
+    extend ActiveSupport::Concern
  4
+    include ActiveModel::StateMachine
  5
+
  6
+    included do
  7
+      before_validation :set_initial_state
  8
+      validates_presence_of :state
  9
+    end
  10
+
  11
+    protected
  12
+      def write_state(state_machine, state)
  13
+        update_attributes! :state => state.to_s
  14
+      end
  15
+
  16
+      def read_state(state_machine)
  17
+        self.state.to_sym
  18
+      end
  19
+
  20
+      def set_initial_state
  21
+        self.state ||= self.class.state_machine.initial_state.to_s
  22
+      end
  23
+  end
  24
+end
42  activerecord/test/cases/state_machine_test.rb
... ...
@@ -0,0 +1,42 @@
  1
+require 'cases/helper'
  2
+require 'models/traffic_light'
  3
+
  4
+class StateMachineTest < ActiveRecord::TestCase
  5
+  def setup
  6
+    @light = TrafficLight.create!
  7
+  end
  8
+
  9
+  test "states initial state" do
  10
+    assert @light.off?
  11
+    assert_equal :off, @light.current_state
  12
+  end
  13
+
  14
+  test "transition to a valid state" do
  15
+    @light.reset
  16
+    assert @light.red?
  17
+    assert_equal :red, @light.current_state
  18
+
  19
+    @light.green_on
  20
+    assert @light.green?
  21
+    assert_equal :green, @light.current_state
  22
+  end
  23
+
  24
+  test "transition does not persist state" do
  25
+    @light.reset
  26
+    assert_equal :red, @light.current_state
  27
+    @light.reload
  28
+    assert_equal "off", @light.state
  29
+  end
  30
+
  31
+  test "transition does persists state" do
  32
+    @light.reset!
  33
+    assert_equal :red, @light.current_state
  34
+    @light.reload
  35
+    assert_equal "red", @light.state
  36
+  end
  37
+
  38
+  test "transition to an invalid state" do
  39
+    assert_raise(ActiveModel::StateMachine::InvalidTransition) { @light.yellow_on }
  40
+    assert_equal :off, @light.current_state
  41
+  end
  42
+end
27  activerecord/test/models/traffic_light.rb
... ...
@@ -0,0 +1,27 @@
  1
+class TrafficLight < ActiveRecord::Base
  2
+  include ActiveRecord::StateMachine
  3
+
  4
+  state_machine do
  5
+    state :off
  6
+
  7
+    state :red
  8
+    state :green
  9
+    state :yellow
  10
+
  11
+    event :red_on do
  12
+      transitions :to => :red, :from => [:yellow]
  13
+    end
  14
+
  15
+    event :green_on do
  16
+      transitions :to => :green, :from => [:red]
  17
+    end
  18
+
  19
+    event :yellow_on do
  20
+      transitions :to => :yellow, :from => [:green]
  21
+    end
  22
+
  23
+    event :reset do
  24
+      transitions :to => :red, :from => [:off]
  25
+    end
  26
+  end
  27
+end
7  activerecord/test/schema/schema.rb
@@ -448,6 +448,13 @@ def create_table(*args, &block)
448 448
     t.integer :pet_id, :integer
449 449
   end
450 450
 
  451
+  create_table :traffic_lights, :force => true do |t|
  452
+    t.string   :location
  453
+    t.string   :state
  454
+    t.datetime :created_at
  455
+    t.datetime :updated_at
  456
+  end
  457
+
451 458
   create_table :treasures, :force => true do |t|
452 459
     t.column :name, :string
453 460
     t.column :looter_id, :integer

0 notes on commit aad5a30

Please sign in to comment.
Something went wrong with that request. Please try again.