verborghs-state_machine 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. data/CHANGELOG.rdoc +360 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +635 -0
  4. data/Rakefile +77 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine/assertions.rb +36 -0
  28. data/lib/state_machine/callback.rb +241 -0
  29. data/lib/state_machine/condition_proxy.rb +106 -0
  30. data/lib/state_machine/eval_helpers.rb +83 -0
  31. data/lib/state_machine/event.rb +267 -0
  32. data/lib/state_machine/event_collection.rb +122 -0
  33. data/lib/state_machine/extensions.rb +149 -0
  34. data/lib/state_machine/guard.rb +230 -0
  35. data/lib/state_machine/initializers/merb.rb +1 -0
  36. data/lib/state_machine/initializers/rails.rb +5 -0
  37. data/lib/state_machine/initializers.rb +4 -0
  38. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  40. data/lib/state_machine/integrations/active_model.rb +445 -0
  41. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  42. data/lib/state_machine/integrations/active_record.rb +522 -0
  43. data/lib/state_machine/integrations/data_mapper/observer.rb +175 -0
  44. data/lib/state_machine/integrations/data_mapper.rb +379 -0
  45. data/lib/state_machine/integrations/mongo_mapper.rb +309 -0
  46. data/lib/state_machine/integrations/sequel.rb +356 -0
  47. data/lib/state_machine/integrations.rb +83 -0
  48. data/lib/state_machine/machine.rb +1645 -0
  49. data/lib/state_machine/machine_collection.rb +64 -0
  50. data/lib/state_machine/matcher.rb +123 -0
  51. data/lib/state_machine/matcher_helpers.rb +54 -0
  52. data/lib/state_machine/node_collection.rb +152 -0
  53. data/lib/state_machine/state.rb +260 -0
  54. data/lib/state_machine/state_collection.rb +112 -0
  55. data/lib/state_machine/transition.rb +399 -0
  56. data/lib/state_machine/transition_collection.rb +244 -0
  57. data/lib/state_machine.rb +421 -0
  58. data/lib/tasks/state_machine.rake +1 -0
  59. data/lib/tasks/state_machine.rb +27 -0
  60. data/test/files/en.yml +9 -0
  61. data/test/files/switch.rb +11 -0
  62. data/test/functional/state_machine_test.rb +980 -0
  63. data/test/test_helper.rb +4 -0
  64. data/test/unit/assertions_test.rb +40 -0
  65. data/test/unit/callback_test.rb +728 -0
  66. data/test/unit/condition_proxy_test.rb +328 -0
  67. data/test/unit/eval_helpers_test.rb +222 -0
  68. data/test/unit/event_collection_test.rb +324 -0
  69. data/test/unit/event_test.rb +795 -0
  70. data/test/unit/guard_test.rb +909 -0
  71. data/test/unit/integrations/active_model_test.rb +956 -0
  72. data/test/unit/integrations/active_record_test.rb +1918 -0
  73. data/test/unit/integrations/data_mapper_test.rb +1814 -0
  74. data/test/unit/integrations/mongo_mapper_test.rb +1382 -0
  75. data/test/unit/integrations/sequel_test.rb +1492 -0
  76. data/test/unit/integrations_test.rb +50 -0
  77. data/test/unit/invalid_event_test.rb +7 -0
  78. data/test/unit/invalid_transition_test.rb +7 -0
  79. data/test/unit/machine_collection_test.rb +565 -0
  80. data/test/unit/machine_test.rb +2349 -0
  81. data/test/unit/matcher_helpers_test.rb +37 -0
  82. data/test/unit/matcher_test.rb +155 -0
  83. data/test/unit/node_collection_test.rb +207 -0
  84. data/test/unit/state_collection_test.rb +280 -0
  85. data/test/unit/state_machine_test.rb +31 -0
  86. data/test/unit/state_test.rb +848 -0
  87. data/test/unit/transition_collection_test.rb +2098 -0
  88. data/test/unit/transition_test.rb +1384 -0
  89. metadata +176 -0
Binary file
@@ -0,0 +1,11 @@
1
+ class AutoShop
2
+ state_machine :initial => :available do
3
+ event :tow_vehicle do
4
+ transition :available => :busy
5
+ end
6
+
7
+ event :fix_vehicle do
8
+ transition :busy => :available
9
+ end
10
+ end
11
+ end
data/examples/car.rb ADDED
@@ -0,0 +1,19 @@
1
+ class Car < Vehicle
2
+ state_machine do
3
+ event :reverse do
4
+ transition [:parked, :idling, :first_gear] => :backing_up
5
+ end
6
+
7
+ event :park do
8
+ transition :backing_up => :parked
9
+ end
10
+
11
+ event :idle do
12
+ transition :backing_up => :idling
13
+ end
14
+
15
+ event :shift_up do
16
+ transition :backing_up => :first_gear
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ class Users < Application
2
+ # GET /users
3
+ def index
4
+ @users = User.all
5
+ display @users
6
+ end
7
+
8
+ # GET /users/1
9
+ def show(id)
10
+ @user = User.get(id)
11
+ raise NotFound unless @user
12
+ display @user
13
+ end
14
+
15
+ # GET /users/new
16
+ def new
17
+ only_provides :html
18
+ @user = User.new
19
+ display @user
20
+ end
21
+
22
+ # GET /users/1/edit
23
+ def edit(id)
24
+ only_provides :html
25
+ @user = User.get(id)
26
+ raise NotFound unless @user
27
+ display @user
28
+ end
29
+
30
+ # POST /users
31
+ def create(user)
32
+ @user = User.new(user)
33
+ if @user.save
34
+ redirect resource(@user), :message => {:notice => "User was successfully created"}
35
+ else
36
+ message[:error] = "User failed to be created"
37
+ render :new
38
+ end
39
+ end
40
+
41
+ # PUT /users/1
42
+ def update(id, user)
43
+ @user = User.get(id)
44
+ raise NotFound unless @user
45
+ if @user.update_attributes(user)
46
+ redirect resource(@user)
47
+ else
48
+ display @user, :edit
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ class User
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :name, String
6
+
7
+ validates_present :name, :state, :access_state
8
+
9
+ state_machine :initial => :unregistered do
10
+ event :register do
11
+ transition :unregistered => :registered
12
+ end
13
+
14
+ event :unregister do
15
+ transition :registered => :unregistered
16
+ end
17
+ end
18
+
19
+ state_machine :access_state, :initial => :enabled do
20
+ event :enable do
21
+ transition all => :enabled
22
+ end
23
+
24
+ event :disable do
25
+ transition all => :disabled
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ <h1>Editing User</h1>
2
+
3
+ <%= form_for @user, :action => resource(@user) do %>
4
+ <%= error_messages %>
5
+
6
+ <p>
7
+ <%= text_field :name, :label => 'Name' %>
8
+ </p>
9
+
10
+ <p>
11
+ <%= label :state %><br />
12
+ <%= select :state_event, :selected => @user.state_event.to_s, :collection => @user.state_transitions, :value_method => :event, :text_method => :human_to_name, :prompt => @user.human_state_name %>
13
+ </p>
14
+
15
+ <p>
16
+ <%= label :access_state %><br />
17
+ <%= select :access_state_event, :selected => @user.access_state_event.to_s, :collection => @user.access_state_transitions, :value_method => :event, :text_method => :human_event, :prompt => "don't change" %>
18
+ </p>
19
+
20
+ <p><%= submit 'Update' %></p>
21
+ <% end =%>
22
+
23
+ <%= link_to 'Show', resource(@user) %> |
24
+ <%= link_to 'Back', resource(:users) %>
@@ -0,0 +1,23 @@
1
+ <h1>Listing Users</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>State</th>
7
+ <th>Access State</th>
8
+ </tr>
9
+
10
+ <% @users.each do |user| %>
11
+ <tr>
12
+ <td><%=h user.name %></td>
13
+ <td><%=h user.human_state_name %></td>
14
+ <td><%=h user.human_access_state_name %></td>
15
+ <td><%= link_to 'Show', resource(user) %></td>
16
+ <td><%= link_to 'Edit', resource(user, :edit) %></td>
17
+ </tr>
18
+ <% end %>
19
+ </table>
20
+
21
+ <br />
22
+
23
+ <%= link_to 'New User', resource(:users, :new) %>
@@ -0,0 +1,13 @@
1
+ <h1>New User</h1>
2
+
3
+ <%= form_for @user, :action => resource(:users) do %>
4
+ <%= error_messages %>
5
+
6
+ <p>
7
+ <%= text_field :name, :label => 'Name' %>
8
+ </p>
9
+
10
+ <p><%= submit 'Create' %></p>
11
+ <% end =%>
12
+
13
+ <%= link_to 'Back', resource(:users) %>
@@ -0,0 +1,17 @@
1
+ <p>
2
+ <b>Name:</b>
3
+ <%=h @user.name %>
4
+ </p>
5
+
6
+ <p>
7
+ <b>State:</b>
8
+ <%=h @user.human_state_name %>
9
+ </p>
10
+
11
+ <p>
12
+ <b>Access State:</b>
13
+ <%=h @user.human_access_state_name %>
14
+ </p>
15
+
16
+ <%= link_to 'Edit', resource(@user, :edit) %> |
17
+ <%= link_to 'Back', resource(:users) %>
@@ -0,0 +1,43 @@
1
+ class UsersController < ApplicationController
2
+ # GET /users
3
+ def index
4
+ @users = User.all
5
+ end
6
+
7
+ # GET /users/1
8
+ def show
9
+ @user = User.find(params[:id])
10
+ end
11
+
12
+ # GET /users/new
13
+ def new
14
+ @user = User.new
15
+ end
16
+
17
+ # GET /users/1/edit
18
+ def edit
19
+ @user = User.find(params[:id])
20
+ end
21
+
22
+ # POST /users
23
+ def create
24
+ @user = User.new(params[:user])
25
+
26
+ if @user.save
27
+ redirect_to(@user)
28
+ else
29
+ render :action => 'new'
30
+ end
31
+ end
32
+
33
+ # PUT /users/1
34
+ def update
35
+ @user = User.find(params[:id])
36
+
37
+ if @user.update_attributes(params[:user])
38
+ redirect_to(@user)
39
+ else
40
+ render :action => 'edit'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.string :name, :state, :access_state
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :users
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ class User < ActiveRecord::Base
2
+ validates_presence_of :name, :state, :access_state
3
+
4
+ state_machine :initial => :unregistered do
5
+ event :register do
6
+ transition :unregistered => :registered
7
+ end
8
+
9
+ event :unregister do
10
+ transition :registered => :unregistered
11
+ end
12
+ end
13
+
14
+ state_machine :access_state, :initial => :enabled do
15
+ event :enable do
16
+ transition all => :enabled
17
+ end
18
+
19
+ event :disable do
20
+ transition all => :disabled
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ <h1>Editing User</h1>
2
+
3
+ <% form_for(@user) do |f| %>
4
+ <%= f.error_messages %>
5
+
6
+ <p>
7
+ <%= f.label :name %><br />
8
+ <%= f.text_field :name %>
9
+ </p>
10
+
11
+ <p>
12
+ <%= f.label :state %><br />
13
+ <%= f.collection_select :state_event, @user.state_transitions, :event, :human_to_name, :include_blank => @user.human_state_name %>
14
+ </p>
15
+
16
+ <p>
17
+ <%= f.label :access_state %><br />
18
+ <%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :human_event, :include_blank => "don't change" %>
19
+ </p>
20
+
21
+ <p><%= f.submit 'Update' %></p>
22
+ <% end %>
23
+
24
+ <%= link_to 'Show', @user %> |
25
+ <%= link_to 'Back', users_path %>
@@ -0,0 +1,23 @@
1
+ <h1>Listing Users</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>State</th>
7
+ <th>Access State</th>
8
+ </tr>
9
+
10
+ <% @users.each do |user| %>
11
+ <tr>
12
+ <td><%=h user.name %></td>
13
+ <td><%=h user.human_state_name %></td>
14
+ <td><%=h user.human_access_state_name %></td>
15
+ <td><%= link_to 'Show', user %></td>
16
+ <td><%= link_to 'Edit', edit_user_path(user) %></td>
17
+ </tr>
18
+ <% end %>
19
+ </table>
20
+
21
+ <br />
22
+
23
+ <%= link_to 'New User', new_user_path %>
@@ -0,0 +1,14 @@
1
+ <h1>New User</h1>
2
+
3
+ <% form_for @user do |f| %>
4
+ <%= f.error_messages %>
5
+
6
+ <p>
7
+ <%= f.label :name %><br />
8
+ <%= f.text_field :name %>
9
+ </p>
10
+
11
+ <p><%= f.submit 'Create' %></p>
12
+ <% end %>
13
+
14
+ <%= link_to 'Back', users_path %>
@@ -0,0 +1,17 @@
1
+ <p>
2
+ <b>Name:</b>
3
+ <%=h @user.name %>
4
+ </p>
5
+
6
+ <p>
7
+ <b>State:</b>
8
+ <%=h @user.human_state_name %>
9
+ </p>
10
+
11
+ <p>
12
+ <b>Access State:</b>
13
+ <%=h @user.human_access_state_name %>
14
+ </p>
15
+
16
+ <%= link_to 'Edit', edit_user_path(@user) %> |
17
+ <%= link_to 'Back', users_path %>
@@ -0,0 +1,7 @@
1
+ class TrafficLight
2
+ state_machine :initial => :stop do
3
+ event :cycle do
4
+ transition :stop => :proceed, :proceed => :caution, :caution => :stop
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ class Vehicle
2
+ state_machine :initial => :parked do
3
+ event :park do
4
+ transition [:idling, :first_gear] => :parked
5
+ end
6
+
7
+ event :ignite do
8
+ transition :stalled => same, :parked => :idling
9
+ end
10
+
11
+ event :idle do
12
+ transition :first_gear => :idling
13
+ end
14
+
15
+ event :shift_up do
16
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
17
+ end
18
+
19
+ event :shift_down do
20
+ transition :third_gear => :second_gear, :second_gear => :first_gear
21
+ end
22
+
23
+ event :crash do
24
+ transition [:first_gear, :second_gear, :third_gear] => :stalled
25
+ end
26
+
27
+ event :repair do
28
+ transition :stalled => :parked
29
+ end
30
+ end
31
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'state_machine'
@@ -0,0 +1,36 @@
1
+ module StateMachine
2
+ # Provides a set of helper methods for making assertions about the content
3
+ # of various objects
4
+ module Assertions
5
+ # Validates that the given hash *only* includes the specified valid keys.
6
+ # If any invalid keys are found, an ArgumentError will be raised.
7
+ #
8
+ # == Examples
9
+ #
10
+ # options = {:name => 'John Smith', :age => 30}
11
+ #
12
+ # assert_valid_keys(options, :name) # => ArgumentError: Invalid key(s): age
13
+ # assert_valid_keys(options, 'name', 'age') # => ArgumentError: Invalid key(s): age, name
14
+ # assert_valid_keys(options, :name, :age) # => nil
15
+ def assert_valid_keys(hash, *valid_keys)
16
+ invalid_keys = hash.keys - valid_keys
17
+ raise ArgumentError, "Invalid key(s): #{invalid_keys.join(', ')}" unless invalid_keys.empty?
18
+ end
19
+
20
+ # Validates that the given hash only includes at *most* one of a set of
21
+ # exclusive keys. If more than one key is found, an ArgumentError will be
22
+ # raised.
23
+ #
24
+ # == Examples
25
+ #
26
+ # options = {:only => :on, :except => :off}
27
+ # assert_exclusive_keys(options, :only) # => nil
28
+ # assert_exclusive_keys(options, :except) # => nil
29
+ # assert_exclusive_keys(options, :only, :except) # => ArgumentError: Conflicting keys: only, except
30
+ # assert_exclusive_keys(options, :only, :except, :with) # => ArgumentError: Conflicting keys: only, except
31
+ def assert_exclusive_keys(hash, *exclusive_keys)
32
+ conflicting_keys = exclusive_keys & hash.keys
33
+ raise ArgumentError, "Conflicting keys: #{conflicting_keys.join(', ')}" unless conflicting_keys.length <= 1
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,241 @@
1
+ require 'state_machine/guard'
2
+ require 'state_machine/eval_helpers'
3
+
4
+ module StateMachine
5
+ # Callbacks represent hooks into objects that allow logic to be triggered
6
+ # before, after, or around a specific set of transitions.
7
+ class Callback
8
+ include EvalHelpers
9
+
10
+ class << self
11
+ # Determines whether to automatically bind the callback to the object
12
+ # being transitioned. This only applies to callbacks that are defined as
13
+ # lambda blocks (or Procs). Some integrations, such as DataMapper, handle
14
+ # callbacks by executing them bound to the object involved, while other
15
+ # integrations, such as ActiveRecord, pass the object as an argument to
16
+ # the callback. This can be configured on an application-wide basis by
17
+ # setting this configuration to +true+ or +false+. The default value
18
+ # is +false+.
19
+ #
20
+ # *Note* that the DataMapper and Sequel integrations automatically
21
+ # configure this value on a per-callback basis, so it does not have to
22
+ # be enabled application-wide.
23
+ #
24
+ # == Examples
25
+ #
26
+ # When not bound to the object:
27
+ #
28
+ # class Vehicle
29
+ # state_machine do
30
+ # before_transition do |vehicle|
31
+ # vehicle.set_alarm
32
+ # end
33
+ # end
34
+ #
35
+ # def set_alarm
36
+ # ...
37
+ # end
38
+ # end
39
+ #
40
+ # When bound to the object:
41
+ #
42
+ # StateMachine::Callback.bind_to_object = true
43
+ #
44
+ # class Vehicle
45
+ # state_machine do
46
+ # before_transition do
47
+ # self.set_alarm
48
+ # end
49
+ # end
50
+ #
51
+ # def set_alarm
52
+ # ...
53
+ # end
54
+ # end
55
+ attr_accessor :bind_to_object
56
+
57
+ # The application-wide terminator to use for callbacks when not
58
+ # explicitly defined. Terminators determine whether to cancel a
59
+ # callback chain based on the return value of the callback.
60
+ #
61
+ # See StateMachine::Callback#terminator for more information.
62
+ attr_accessor :terminator
63
+ end
64
+
65
+ # The type of callback chain this callback is for. This can be one of the
66
+ # following:
67
+ # * +before+
68
+ # * +after+
69
+ # * +around+
70
+ attr_accessor :type
71
+
72
+ # An optional block for determining whether to cancel the callback chain
73
+ # based on the return value of the callback. By default, the callback
74
+ # chain never cancels based on the return value (i.e. there is no implicit
75
+ # terminator). Certain integrations, such as ActiveRecord and Sequel,
76
+ # change this default value.
77
+ #
78
+ # == Examples
79
+ #
80
+ # Canceling the callback chain without a terminator:
81
+ #
82
+ # class Vehicle
83
+ # state_machine do
84
+ # before_transition do |vehicle|
85
+ # throw :halt
86
+ # end
87
+ # end
88
+ # end
89
+ #
90
+ # Canceling the callback chain with a terminator value of +false+:
91
+ #
92
+ # class Vehicle
93
+ # state_machine do
94
+ # before_transition do |vehicle|
95
+ # false
96
+ # end
97
+ # end
98
+ # end
99
+ attr_reader :terminator
100
+
101
+ # The guard that determines whether or not this callback can be invoked
102
+ # based on the context of the transition. The event, from state, and
103
+ # to state must all match in order for the guard to pass.
104
+ #
105
+ # See StateMachine::Guard for more information.
106
+ attr_reader :guard
107
+
108
+ # Creates a new callback that can get called based on the configured
109
+ # options.
110
+ #
111
+ # In addition to the possible configuration options for guards, the
112
+ # following options can be configured:
113
+ # * <tt>:bind_to_object</tt> - Whether to bind the callback to the object involved.
114
+ # If set to false, the object will be passed as a parameter instead.
115
+ # Default is integration-specific or set to the application default.
116
+ # * <tt>:terminator</tt> - A block/proc that determines what callback
117
+ # results should cause the callback chain to halt (if not using the
118
+ # default <tt>throw :halt</tt> technique).
119
+ #
120
+ # More information about how those options affect the behavior of the
121
+ # callback can be found in their attribute definitions.
122
+ def initialize(type, *args, &block)
123
+ @type = type
124
+ raise ArgumentError, 'Type must be :before, :after, or :around' unless [:before, :after, :around].include?(type)
125
+
126
+ options = args.last.is_a?(Hash) ? args.pop : {}
127
+ @methods = args
128
+ @methods.concat(Array(options.delete(:do)))
129
+ @methods << block if block_given?
130
+ raise ArgumentError, 'Method(s) for callback must be specified' unless @methods.any?
131
+
132
+ options = {:bind_to_object => self.class.bind_to_object, :terminator => self.class.terminator}.merge(options)
133
+
134
+ # Proxy lambda blocks so that they're bound to the object
135
+ bind_to_object = options.delete(:bind_to_object)
136
+ @methods.map! do |method|
137
+ bind_to_object && method.is_a?(Proc) ? bound_method(method) : method
138
+ end
139
+
140
+ @terminator = options.delete(:terminator)
141
+ @guard = Guard.new(options)
142
+ end
143
+
144
+ # Gets a list of the states known to this callback by looking at the
145
+ # guard's known states
146
+ def known_states
147
+ guard.known_states
148
+ end
149
+
150
+ # Runs the callback as long as the transition context matches the guard
151
+ # requirements configured for this callback. If a block is provided, it
152
+ # will be called when the last method has run.
153
+ #
154
+ # If a terminator has been configured and it matches the result from the
155
+ # evaluated method, then the callback chain should be halted.
156
+ def call(object, context = {}, *args, &block)
157
+ if @guard.matches?(object, context)
158
+ run_methods(object, context, 0, *args, &block)
159
+ true
160
+ else
161
+ false
162
+ end
163
+ end
164
+
165
+ # Verifies that the success requirement for this callback matches the given
166
+ # value
167
+ def matches_success?(success)
168
+ guard.success_requirement.matches?(success)
169
+ end
170
+
171
+ private
172
+ # Runs all of the methods configured for this callback.
173
+ #
174
+ # When running +around+ callbacks, this will evaluate each method and
175
+ # yield when the last method has yielded. The callback will only halt if
176
+ # one of the methods does not yield.
177
+ #
178
+ # For all other types of callbacks, this will evaluate each method in
179
+ # order. The callback will only halt if the resulting value from the
180
+ # method passes the terminator.
181
+ def run_methods(object, context = {}, index = 0, *args, &block)
182
+ if type == :around
183
+ if method = @methods[index]
184
+ yielded = false
185
+ evaluate_method(object, method, *args) do
186
+ yielded = true
187
+ run_methods(object, context, index + 1, *args, &block)
188
+ end
189
+
190
+ throw :halt unless yielded
191
+ else
192
+ yield if block_given?
193
+ end
194
+ else
195
+ @methods.each do |method|
196
+ result = evaluate_method(object, method, *args)
197
+ throw :halt if @terminator && @terminator.call(result)
198
+ end
199
+ end
200
+ end
201
+
202
+ # Generates a method that can be bound to the object being transitioned
203
+ # when the callback is invoked
204
+ def bound_method(block)
205
+ type = self.type
206
+ arity = block.arity
207
+ arity += 1 if arity >= 0 # Make sure the object gets passed
208
+ arity += 1 if arity == 1 && type == :around # Make sure the block gets passed
209
+
210
+ method = if RUBY_VERSION >= '1.9'
211
+ lambda do |object, *args|
212
+ object.instance_exec(*args, &block)
213
+ end
214
+ else
215
+ # Generate a thread-safe unbound method that can be used on any object.
216
+ # This is a workaround for not having Ruby 1.9's instance_exec
217
+ unbound_method = Object.class_eval do
218
+ time = Time.now
219
+ method_name = "__bind_#{time.to_i}_#{time.usec}"
220
+ define_method(method_name, &block)
221
+ method = instance_method(method_name)
222
+ remove_method(method_name)
223
+ method
224
+ end
225
+
226
+ # Proxy calls to the method so that the method can be bound *and*
227
+ # the arguments are adjusted
228
+ lambda do |object, *args|
229
+ unbound_method.bind(object).call(*args)
230
+ end
231
+ end
232
+
233
+ # Proxy arity to the original block
234
+ (class << method; self; end).class_eval do
235
+ define_method(:arity) { arity }
236
+ end
237
+
238
+ method
239
+ end
240
+ end
241
+ end