branston 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/README.rdoc +1 -1
  2. data/lib/branston/Gemfile +25 -0
  3. data/lib/branston/Gemfile.lock +76 -0
  4. data/lib/branston/app/controllers/application_controller.rb +1 -1
  5. data/lib/branston/app/controllers/outcomes_controller.rb +2 -0
  6. data/lib/branston/app/controllers/stories_controller.rb +82 -86
  7. data/lib/branston/app/controllers/users_controller.rb +69 -11
  8. data/lib/branston/app/helpers/iterations_helper.rb +13 -13
  9. data/lib/branston/app/models/iteration.rb +3 -1
  10. data/lib/branston/app/models/release.rb +0 -1
  11. data/lib/branston/app/models/story.rb +30 -28
  12. data/lib/branston/app/models/user.rb +46 -1
  13. data/lib/branston/app/views/layouts/_header.html.erb +8 -3
  14. data/lib/branston/app/views/layouts/user_roles.html.erb +5 -5
  15. data/lib/branston/app/views/sessions/new.html.erb +8 -14
  16. data/lib/branston/app/views/users/_admin_controls.html.erb +14 -0
  17. data/lib/branston/app/views/users/_form.html.erb +27 -0
  18. data/lib/branston/app/views/users/edit.html.erb +9 -0
  19. data/lib/branston/app/views/users/index.html.erb +14 -0
  20. data/lib/branston/app/views/users/new.html.erb +3 -22
  21. data/lib/branston/config/boot.rb +20 -0
  22. data/lib/branston/config/environment.rb +2 -7
  23. data/lib/branston/config/environments/test.rb +0 -8
  24. data/lib/branston/config/preinitializer.rb +21 -0
  25. data/lib/branston/config/routes.rb +15 -10
  26. data/lib/branston/db/development.sqlite3 +0 -0
  27. data/lib/branston/db/development_structure.sql +21 -8
  28. data/lib/branston/db/migrate/20100723161424_add_state_to_user.rb +12 -0
  29. data/lib/branston/db/migrate/20100726150322_add_activation_fields_to_user.rb +12 -0
  30. data/lib/branston/db/migrate/20100729125551_set_default_user_state_to_pending.rb +10 -0
  31. data/lib/branston/db/migrate/20100812133837_add_is_admin_property_to_user.rb +10 -0
  32. data/lib/branston/db/migrate/20100812140532_set_default_user_state_to_active.rb +10 -0
  33. data/lib/branston/db/migrate/20100812143455_add_default_admin_user.rb +17 -0
  34. data/lib/branston/db/migrate/20110408162438_remove_is_admin_property_and_add_role_instead.rb +12 -0
  35. data/lib/branston/db/pristine.sqlite3 +0 -0
  36. data/lib/branston/db/schema.rb +6 -8
  37. data/lib/branston/db/test.sqlite3 +0 -0
  38. data/lib/branston/log/development.log +1181 -433
  39. data/lib/branston/log/test.log +145306 -52026
  40. data/lib/branston/test/blueprints.rb +22 -28
  41. data/lib/branston/test/functional/iterations_controller_test.rb +149 -113
  42. data/lib/branston/test/functional/outcomes_controller_test.rb +94 -60
  43. data/lib/branston/test/functional/preconditions_controller_test.rb +101 -67
  44. data/lib/branston/test/functional/releases_controller_test.rb +85 -49
  45. data/lib/branston/test/functional/scenarios_controller_test.rb +104 -70
  46. data/lib/branston/test/functional/stories_controller_test.rb +41 -12
  47. data/lib/branston/test/functional/users_controller_test.rb +364 -43
  48. data/lib/branston/test/unit/iteration_test.rb +37 -6
  49. data/lib/branston/test/unit/outcome_test.rb +2 -2
  50. data/lib/branston/test/unit/participation_test.rb +2 -2
  51. data/lib/branston/test/unit/precondition_test.rb +3 -3
  52. data/lib/branston/test/unit/release_test.rb +4 -0
  53. data/lib/branston/test/unit/scenario_test.rb +4 -4
  54. data/lib/branston/test/unit/story_test.rb +62 -40
  55. data/lib/branston/test/unit/user_test.rb +195 -5
  56. metadata +136 -156
  57. data/lib/branston/app/controllers/user_roles_controller.rb +0 -105
  58. data/lib/branston/app/helpers/user_roles_helper.rb +0 -2
  59. data/lib/branston/app/models/user_role.rb +0 -21
  60. data/lib/branston/app/views/layouts/outcomes.html.erb +0 -17
  61. data/lib/branston/app/views/layouts/preconditions.html.erb +0 -17
  62. data/lib/branston/app/views/layouts/releases.html.erb +0 -17
  63. data/lib/branston/app/views/user_roles/edit.html.erb +0 -16
  64. data/lib/branston/app/views/user_roles/index.html.erb +0 -20
  65. data/lib/branston/app/views/user_roles/new.html.erb +0 -15
  66. data/lib/branston/app/views/user_roles/show.html.erb +0 -8
  67. data/lib/branston/coverage/app-controllers-application_controller_rb.html +0 -231
  68. data/lib/branston/coverage/app-controllers-iterations_controller_rb.html +0 -801
  69. data/lib/branston/coverage/app-controllers-outcomes_controller_rb.html +0 -759
  70. data/lib/branston/coverage/app-controllers-preconditions_controller_rb.html +0 -783
  71. data/lib/branston/coverage/app-controllers-releases_controller_rb.html +0 -705
  72. data/lib/branston/coverage/app-controllers-scenarios_controller_rb.html +0 -777
  73. data/lib/branston/coverage/app-controllers-sessions_controller_rb.html +0 -411
  74. data/lib/branston/coverage/app-controllers-stories_controller_rb.html +0 -1071
  75. data/lib/branston/coverage/app-controllers-user_roles_controller_rb.html +0 -693
  76. data/lib/branston/coverage/app-controllers-users_controller_rb.html +0 -315
  77. data/lib/branston/coverage/app-helpers-application_helper_rb.html +0 -327
  78. data/lib/branston/coverage/app-helpers-iterations_helper_rb.html +0 -363
  79. data/lib/branston/coverage/app-helpers-outcomes_helper_rb.html +0 -75
  80. data/lib/branston/coverage/app-helpers-preconditions_helper_rb.html +0 -75
  81. data/lib/branston/coverage/app-helpers-releases_helper_rb.html +0 -75
  82. data/lib/branston/coverage/app-helpers-sessions_helper_rb.html +0 -75
  83. data/lib/branston/coverage/app-helpers-stories_helper_rb.html +0 -75
  84. data/lib/branston/coverage/app-helpers-user_roles_helper_rb.html +0 -75
  85. data/lib/branston/coverage/app-models-iteration_rb.html +0 -321
  86. data/lib/branston/coverage/app-models-outcome_rb.html +0 -243
  87. data/lib/branston/coverage/app-models-participation_rb.html +0 -189
  88. data/lib/branston/coverage/app-models-precondition_rb.html +0 -243
  89. data/lib/branston/coverage/app-models-release_rb.html +0 -195
  90. data/lib/branston/coverage/app-models-scenario_rb.html +0 -231
  91. data/lib/branston/coverage/app-models-story_rb.html +0 -621
  92. data/lib/branston/coverage/app-models-user_rb.html +0 -513
  93. data/lib/branston/coverage/app-models-user_role_rb.html +0 -189
  94. data/lib/branston/coverage/index.html +0 -570
  95. data/lib/branston/coverage/jquery-1.3.2.min.js +0 -19
  96. data/lib/branston/coverage/jquery.tablesorter.min.js +0 -15
  97. data/lib/branston/coverage/lib-client_rb.html +0 -537
  98. data/lib/branston/coverage/lib-faker_extras_rb.html +0 -207
  99. data/lib/branston/coverage/lib-story_generator_rb.html +0 -873
  100. data/lib/branston/coverage/print.css +0 -12
  101. data/lib/branston/coverage/rcov.js +0 -42
  102. data/lib/branston/coverage/screen.css +0 -270
  103. data/lib/branston/db/migrate/20091127131037_create_user_roles.rb +0 -13
  104. data/lib/branston/db/migrate/20091127172950_add_story_id_to_user_role.rb +0 -10
  105. data/lib/branston/test/functional/user_roles_controller_test.rb +0 -71
  106. data/lib/branston/test/unit/helpers/user_roles_helper_test.rb +0 -4
  107. data/lib/branston/test/unit/user_role_test.rb +0 -9
  108. data/lib/branston/tmp/performance/BrowsingTest#test_homepage_process_time_flat.txt +0 -8
  109. data/lib/branston/tmp/performance/BrowsingTest#test_homepage_process_time_graph.html +0 -6718
  110. data/lib/branston/tmp/performance/BrowsingTest#test_homepage_process_time_tree.txt +0 -9942
  111. data/lib/branston/vendor/plugins/state_machine/CHANGELOG.rdoc +0 -298
  112. data/lib/branston/vendor/plugins/state_machine/LICENSE +0 -20
  113. data/lib/branston/vendor/plugins/state_machine/README.rdoc +0 -466
  114. data/lib/branston/vendor/plugins/state_machine/Rakefile +0 -98
  115. data/lib/branston/vendor/plugins/state_machine/examples/AutoShop_state.png +0 -0
  116. data/lib/branston/vendor/plugins/state_machine/examples/Car_state.png +0 -0
  117. data/lib/branston/vendor/plugins/state_machine/examples/TrafficLight_state.png +0 -0
  118. data/lib/branston/vendor/plugins/state_machine/examples/Vehicle_state.png +0 -0
  119. data/lib/branston/vendor/plugins/state_machine/examples/auto_shop.rb +0 -11
  120. data/lib/branston/vendor/plugins/state_machine/examples/car.rb +0 -19
  121. data/lib/branston/vendor/plugins/state_machine/examples/merb-rest/controller.rb +0 -51
  122. data/lib/branston/vendor/plugins/state_machine/examples/merb-rest/model.rb +0 -28
  123. data/lib/branston/vendor/plugins/state_machine/examples/merb-rest/view_edit.html.erb +0 -24
  124. data/lib/branston/vendor/plugins/state_machine/examples/merb-rest/view_index.html.erb +0 -23
  125. data/lib/branston/vendor/plugins/state_machine/examples/merb-rest/view_new.html.erb +0 -13
  126. data/lib/branston/vendor/plugins/state_machine/examples/merb-rest/view_show.html.erb +0 -17
  127. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/controller.rb +0 -43
  128. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/migration.rb +0 -11
  129. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/model.rb +0 -23
  130. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/view_edit.html.erb +0 -25
  131. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/view_index.html.erb +0 -23
  132. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/view_new.html.erb +0 -14
  133. data/lib/branston/vendor/plugins/state_machine/examples/rails-rest/view_show.html.erb +0 -17
  134. data/lib/branston/vendor/plugins/state_machine/examples/traffic_light.rb +0 -7
  135. data/lib/branston/vendor/plugins/state_machine/examples/vehicle.rb +0 -31
  136. data/lib/branston/vendor/plugins/state_machine/init.rb +0 -1
  137. data/lib/branston/vendor/plugins/state_machine/lib/state_machine.rb +0 -388
  138. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/assertions.rb +0 -36
  139. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/callback.rb +0 -189
  140. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/condition_proxy.rb +0 -94
  141. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/eval_helpers.rb +0 -67
  142. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/event.rb +0 -252
  143. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/event_collection.rb +0 -122
  144. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/extensions.rb +0 -149
  145. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/guard.rb +0 -230
  146. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations.rb +0 -68
  147. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations/active_record.rb +0 -492
  148. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations/active_record/locale.rb +0 -11
  149. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations/active_record/observer.rb +0 -41
  150. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations/data_mapper.rb +0 -351
  151. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations/data_mapper/observer.rb +0 -139
  152. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/integrations/sequel.rb +0 -322
  153. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/machine.rb +0 -1467
  154. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/machine_collection.rb +0 -155
  155. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/matcher.rb +0 -123
  156. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/matcher_helpers.rb +0 -54
  157. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/node_collection.rb +0 -152
  158. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/state.rb +0 -249
  159. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/state_collection.rb +0 -112
  160. data/lib/branston/vendor/plugins/state_machine/lib/state_machine/transition.rb +0 -394
  161. data/lib/branston/vendor/plugins/state_machine/state_machine.gemspec +0 -30
  162. data/lib/branston/vendor/plugins/state_machine/tasks/state_machine.rake +0 -1
  163. data/lib/branston/vendor/plugins/state_machine/tasks/state_machine.rb +0 -30
  164. data/lib/branston/vendor/plugins/state_machine/test/classes/switch.rb +0 -11
  165. data/lib/branston/vendor/plugins/state_machine/test/functional/state_machine_test.rb +0 -941
  166. data/lib/branston/vendor/plugins/state_machine/test/test_helper.rb +0 -4
  167. data/lib/branston/vendor/plugins/state_machine/test/unit/assertions_test.rb +0 -40
  168. data/lib/branston/vendor/plugins/state_machine/test/unit/callback_test.rb +0 -455
  169. data/lib/branston/vendor/plugins/state_machine/test/unit/condition_proxy_test.rb +0 -328
  170. data/lib/branston/vendor/plugins/state_machine/test/unit/eval_helpers_test.rb +0 -120
  171. data/lib/branston/vendor/plugins/state_machine/test/unit/event_collection_test.rb +0 -326
  172. data/lib/branston/vendor/plugins/state_machine/test/unit/event_test.rb +0 -743
  173. data/lib/branston/vendor/plugins/state_machine/test/unit/guard_test.rb +0 -908
  174. data/lib/branston/vendor/plugins/state_machine/test/unit/integrations/active_record_test.rb +0 -1367
  175. data/lib/branston/vendor/plugins/state_machine/test/unit/integrations/data_mapper_test.rb +0 -962
  176. data/lib/branston/vendor/plugins/state_machine/test/unit/integrations/sequel_test.rb +0 -859
  177. data/lib/branston/vendor/plugins/state_machine/test/unit/integrations_test.rb +0 -42
  178. data/lib/branston/vendor/plugins/state_machine/test/unit/invalid_event_test.rb +0 -7
  179. data/lib/branston/vendor/plugins/state_machine/test/unit/invalid_transition_test.rb +0 -7
  180. data/lib/branston/vendor/plugins/state_machine/test/unit/machine_collection_test.rb +0 -938
  181. data/lib/branston/vendor/plugins/state_machine/test/unit/machine_test.rb +0 -2004
  182. data/lib/branston/vendor/plugins/state_machine/test/unit/matcher_helpers_test.rb +0 -37
  183. data/lib/branston/vendor/plugins/state_machine/test/unit/matcher_test.rb +0 -155
  184. data/lib/branston/vendor/plugins/state_machine/test/unit/node_collection_test.rb +0 -207
  185. data/lib/branston/vendor/plugins/state_machine/test/unit/state_collection_test.rb +0 -280
  186. data/lib/branston/vendor/plugins/state_machine/test/unit/state_machine_test.rb +0 -31
  187. data/lib/branston/vendor/plugins/state_machine/test/unit/state_test.rb +0 -795
  188. data/lib/branston/vendor/plugins/state_machine/test/unit/transition_test.rb +0 -1212
@@ -1,249 +0,0 @@
1
- require 'state_machine/assertions'
2
- require 'state_machine/condition_proxy'
3
-
4
- module StateMachine
5
- # A state defines a value that an attribute can be in after being transitioned
6
- # 0 or more times. States can represent a value of any type in Ruby, though
7
- # the most common (and default) type is String.
8
- #
9
- # In addition to defining the machine's value, a state can also define a
10
- # behavioral context for an object when that object is in the state. See
11
- # StateMachine::Machine#state for more information about how state-driven
12
- # behavior can be utilized.
13
- class State
14
- include Assertions
15
-
16
- # The state machine for which this state is defined
17
- attr_accessor :machine
18
-
19
- # The unique identifier for the state used in event and callback definitions
20
- attr_reader :name
21
-
22
- # The fully-qualified identifier for the state, scoped by the machine's
23
- # namespace
24
- attr_reader :qualified_name
25
-
26
- # The value that is written to a machine's attribute when an object
27
- # transitions into this state
28
- attr_writer :value
29
-
30
- # Whether this state's value should be cached after being evaluated
31
- attr_accessor :cache
32
-
33
- # Whether or not this state is the initial state to use for new objects
34
- attr_accessor :initial
35
- alias_method :initial?, :initial
36
-
37
- # A custom lambda block for determining whether a given value matches this
38
- # state
39
- attr_accessor :matcher
40
-
41
- # Tracks all of the methods that have been defined for the machine's owner
42
- # class when objects are in this state.
43
- #
44
- # Maps :method_name => UnboundMethod
45
- attr_reader :methods
46
-
47
- # Creates a new state within the context of the given machine.
48
- #
49
- # Configuration options:
50
- # * <tt>:initial</tt> - Whether this state is the beginning state for the
51
- # machine. Default is false.
52
- # * <tt>:value</tt> - The value to store when an object transitions to this
53
- # state. Default is the name (stringified).
54
- # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
55
- # then setting this to true will cache the evaluated result
56
- # * <tt>:if</tt> - Determines whether a value matches this state
57
- # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
58
- # By default, the configured value is matched.
59
- def initialize(machine, name, options = {}) #:nodoc:
60
- assert_valid_keys(options, :initial, :value, :cache, :if)
61
-
62
- @machine = machine
63
- @name = name
64
- @qualified_name = name && machine.namespace ? :"#{machine.namespace}_#{name}" : name
65
- @value = options.include?(:value) ? options[:value] : name && name.to_s
66
- @cache = options[:cache]
67
- @matcher = options[:if]
68
- @methods = {}
69
- @initial = options[:initial] == true
70
-
71
- add_predicate
72
- end
73
-
74
- # Creates a copy of this state in addition to the list of associated
75
- # methods to prevent conflicts across different states.
76
- def initialize_copy(orig) #:nodoc:
77
- super
78
- @methods = methods.dup
79
- end
80
-
81
- # Determines whether there are any states that can be transitioned to from
82
- # this state. If there are none, then this state is considered *final*.
83
- # Any objects in a final state will remain so forever given the current
84
- # machine's definition.
85
- def final?
86
- !machine.events.any? do |event|
87
- event.guards.any? do |guard|
88
- guard.state_requirements.any? do |requirement|
89
- requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name)
90
- end
91
- end
92
- end
93
- end
94
-
95
- # Generates a human-readable description of this state's name / value:
96
- #
97
- # For example,
98
- #
99
- # State.new(machine, :parked).description # => "parked"
100
- # State.new(machine, :parked, :value => :parked).description # => "parked"
101
- # State.new(machine, :parked, :value => nil).description # => "parked (nil)"
102
- # State.new(machine, :parked, :value => 1).description # => "parked (1)"
103
- # State.new(machine, :parked, :value => lambda {Time.now}).description # => "parked (*)
104
- def description
105
- description = name ? name.to_s : name.inspect
106
- description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
107
- description
108
- end
109
-
110
- # The value that represents this state. This will optionally evaluate the
111
- # original block if it's a lambda block. Otherwise, the static value is
112
- # returned.
113
- #
114
- # For example,
115
- #
116
- # State.new(machine, :parked, :value => 1).value # => 1
117
- # State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008
118
- # State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => <Proc:0xb6ea7ca0@...>
119
- def value(eval = true)
120
- if @value.is_a?(Proc) && eval
121
- if cache_value?
122
- @value = @value.call
123
- machine.states.update(self)
124
- @value
125
- else
126
- @value.call
127
- end
128
- else
129
- @value
130
- end
131
- end
132
-
133
- # Determines whether this state matches the given value. If no matcher is
134
- # configured, then this will check whether the values are equivalent.
135
- # Otherwise, the matcher will determine the result.
136
- #
137
- # For example,
138
- #
139
- # # Without a matcher
140
- # state = State.new(machine, :parked, :value => 1)
141
- # state.matches?(1) # => true
142
- # state.matches?(2) # => false
143
- #
144
- # # With a matcher
145
- # state = State.new(machine, :parked, :value => lambda {Time.now}, :if => lambda {|value| !value.nil?})
146
- # state.matches?(nil) # => false
147
- # state.matches?(Time.now) # => true
148
- def matches?(other_value)
149
- matcher ? matcher.call(other_value) : other_value == value
150
- end
151
-
152
- # Defines a context for the state which will be enabled on instances of
153
- # the owner class when the machine is in this state.
154
- #
155
- # This can be called multiple times. Each time a new context is created,
156
- # a new module will be included in the owner class.
157
- def context(&block)
158
- owner_class = machine.owner_class
159
- machine_name = machine.name
160
- name = self.name
161
-
162
- # Evaluate the method definitions
163
- context = ConditionProxy.new(owner_class, lambda {|object| object.class.state_machine(machine_name).states.matches?(object, name)})
164
- context.class_eval(&block)
165
- context.instance_methods.each do |method|
166
- methods[method.to_sym] = context.instance_method(method)
167
-
168
- # Calls the method defined by the current state of the machine
169
- context.class_eval <<-end_eval, __FILE__, __LINE__
170
- def #{method}(*args, &block)
171
- self.class.state_machine(#{machine_name.inspect}).states.match!(self).call(self, #{method.inspect}, *args, &block)
172
- end
173
- end_eval
174
- end
175
-
176
- # Include the context so that it can be bound to the owner class (the
177
- # context is considered an ancestor, so it's allowed to be bound)
178
- owner_class.class_eval { include context }
179
-
180
- context
181
- end
182
-
183
- # Calls a method defined in this state's context on the given object. All
184
- # arguments and any block will be passed into the method defined.
185
- #
186
- # If the method has never been defined for this state, then a NoMethodError
187
- # will be raised.
188
- def call(object, method, *args, &block)
189
- if context_method = methods[method.to_sym]
190
- # Method is defined by the state: proxy it through
191
- context_method.bind(object).call(*args, &block)
192
- else
193
- # Raise exception as if the method never existed on the original object
194
- raise NoMethodError, "undefined method '#{method}' for #{object} with #{name || 'nil'} #{machine.name}"
195
- end
196
- end
197
-
198
- # Draws a representation of this state on the given machine. This will
199
- # create a new node on the graph with the following properties:
200
- # * +label+ - The human-friendly description of the state.
201
- # * +width+ - The width of the node. Always 1.
202
- # * +height+ - The height of the node. Always 1.
203
- # * +shape+ - The actual shape of the node. If the state is a final
204
- # state, then "doublecircle", otherwise "ellipse".
205
- #
206
- # The actual node generated on the graph will be returned.
207
- def draw(graph)
208
- node = graph.add_node(name ? name.to_s : 'nil',
209
- :label => description,
210
- :width => '1',
211
- :height => '1',
212
- :shape => final? ? 'doublecircle' : 'ellipse'
213
- )
214
-
215
- # Add open arrow for initial state
216
- graph.add_edge(graph.add_node('starting_state', :shape => 'point'), node) if initial?
217
-
218
- node
219
- end
220
-
221
- # Generates a nicely formatted description of this state's contents.
222
- #
223
- # For example,
224
- #
225
- # state = StateMachine::State.new(machine, :parked, :value => 1, :initial => true)
226
- # state # => #<StateMachine::State name=:parked value=1 initial=true context=[]>
227
- def inspect
228
- attributes = [[:name, name], [:value, @value], [:initial, initial?], [:context, methods.keys]]
229
- "#<#{self.class} #{attributes.map {|attr, value| "#{attr}=#{value.inspect}"} * ' '}>"
230
- end
231
-
232
- private
233
- # Should the value be cached after it's evaluated for the first time?
234
- def cache_value?
235
- @cache
236
- end
237
-
238
- # Adds a predicate method to the owner class so long as a name has
239
- # actually been configured for the state
240
- def add_predicate
241
- return unless name
242
-
243
- # Checks whether the current value matches this state
244
- machine.define_instance_method("#{qualified_name}?") do |machine, object|
245
- machine.states.matches?(object, name)
246
- end
247
- end
248
- end
249
- end
@@ -1,112 +0,0 @@
1
- require 'state_machine/node_collection'
2
-
3
- module StateMachine
4
- # Represents a collection of states in a state machine
5
- class StateCollection < NodeCollection
6
- def initialize(machine) #:nodoc:
7
- super(machine, :index => [:name, :value])
8
- end
9
-
10
- # Determines whether the given object is in a specific state. If the
11
- # object's current value doesn't match the state, then this will return
12
- # false, otherwise true. If the given state is unknown, then an IndexError
13
- # will be raised.
14
- #
15
- # == Examples
16
- #
17
- # class Vehicle
18
- # state_machine :initial => :parked do
19
- # other_states :idling
20
- # end
21
- # end
22
- #
23
- # states = Vehicle.state_machine.states
24
- # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
25
- #
26
- # states.matches?(vehicle, :parked) # => true
27
- # states.matches?(vehicle, :idling) # => false
28
- # states.matches?(vehicle, :invalid) # => IndexError: :invalid is an invalid key for :name index
29
- def matches?(object, name)
30
- fetch(name).matches?(machine.read(object, :state))
31
- end
32
-
33
- # Determines the current state of the given object as configured by this
34
- # state machine. This will attempt to find a known state that matches
35
- # the value of the attribute on the object.
36
- #
37
- # == Examples
38
- #
39
- # class Vehicle
40
- # state_machine :initial => :parked do
41
- # other_states :idling
42
- # end
43
- # end
44
- #
45
- # states = Vehicle.state_machine.states
46
- #
47
- # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
48
- # states.match(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=true>
49
- #
50
- # vehicle.state = 'idling'
51
- # states.match(vehicle) # => #<StateMachine::State name=:idling value="idling" initial=true>
52
- #
53
- # vehicle.state = 'invalid'
54
- # states.match(vehicle) # => nil
55
- def match(object)
56
- value = machine.read(object, :state)
57
- self[value, :value] || detect {|state| state.matches?(value)}
58
- end
59
-
60
- # Determines the current state of the given object as configured by this
61
- # state machine. If no state is found, then an ArgumentError will be
62
- # raised.
63
- #
64
- # == Examples
65
- #
66
- # class Vehicle
67
- # state_machine :initial => :parked do
68
- # other_states :idling
69
- # end
70
- # end
71
- #
72
- # states = Vehicle.state_machine.states
73
- #
74
- # vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
75
- # states.match!(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=true>
76
- #
77
- # vehicle.state = 'invalid'
78
- # states.match!(vehicle) # => ArgumentError: "invalid" is not a known state value
79
- def match!(object)
80
- match(object) || raise(ArgumentError, "#{machine.read(object, :state).inspect} is not a known #{machine.name} value")
81
- end
82
-
83
- # Gets the order in which states should be displayed based on where they
84
- # were first referenced. This will order states in the following priority:
85
- #
86
- # 1. Initial state
87
- # 2. Event transitions (:from, :except_from, :to, :except_to options)
88
- # 3. States with behaviors
89
- # 4. States referenced via +state+ or +other_states+
90
- # 5. States referenced in callbacks
91
- #
92
- # This order will determine how the GraphViz visualizations are rendered.
93
- def by_priority
94
- order = select {|state| state.initial}.map {|state| state.name}
95
-
96
- machine.events.each {|event| order += event.known_states}
97
- order += select {|state| state.methods.any?}.map {|state| state.name}
98
- order += keys(:name) - machine.callbacks.values.flatten.map {|callback| callback.known_states}.flatten
99
- order += keys(:name)
100
-
101
- order.uniq!
102
- order.map! {|name| self[name]}
103
- order
104
- end
105
-
106
- private
107
- # Gets the value for the given attribute on the node
108
- def value(node, attribute)
109
- attribute == :value ? node.value(false) : super
110
- end
111
- end
112
- end
@@ -1,394 +0,0 @@
1
- module StateMachine
2
- # An invalid transition was attempted
3
- class InvalidTransition < StandardError
4
- end
5
-
6
- # A transition represents a state change for a specific attribute.
7
- #
8
- # Transitions consist of:
9
- # * An event
10
- # * A starting state
11
- # * An ending state
12
- class Transition
13
- class << self
14
- # Runs one or more transitions in parallel. All transitions will run
15
- # through the following steps:
16
- # 1. Before callbacks
17
- # 2. Persist state
18
- # 3. Invoke action
19
- # 4. After callbacks (if configured)
20
- # 5. Rollback (if action is unsuccessful)
21
- #
22
- # Configuration options:
23
- # * <tt>:action</tt> - Whether to run the action configured for each transition
24
- # * <tt>:after</tt> - Whether to run after callbacks
25
- #
26
- # If a block is passed to this method, that block will be called instead
27
- # of invoking each transition's action.
28
- def perform(transitions, options = {})
29
- # Validate that the transitions are for separate machines / attributes
30
- attributes = transitions.map {|transition| transition.attribute}.uniq
31
- raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != transitions.length
32
-
33
- success = false
34
-
35
- # Run before callbacks. If any callback halts, then the entire chain
36
- # is halted for every transition.
37
- if transitions.all? {|transition| transition.before}
38
- # Persist the new state for each attribute
39
- transitions.each {|transition| transition.persist}
40
-
41
- # Run the actions associated with each machine
42
- begin
43
- results = {}
44
- success =
45
- if block_given?
46
- # Block was given: use the result for each transition
47
- result = yield
48
- transitions.each {|transition| results[transition.action] = result}
49
- !!result
50
- elsif options[:action] == false
51
- # Skip the action
52
- true
53
- else
54
- # Run each transition's action (only once)
55
- object = transitions.first.object
56
- transitions.all? do |transition|
57
- action = transition.action
58
- action && !results.include?(action) ? results[action] = object.send(action) : true
59
- end
60
- end
61
- rescue Exception
62
- # Action failed: rollback
63
- transitions.each {|transition| transition.rollback}
64
- raise
65
- end
66
-
67
- # Run after callbacks even when the actions failed. The :after option
68
- # is ignored if the transitions were unsuccessful.
69
- transitions.each {|transition| transition.after(results[transition.action], success)} unless options[:after] == false && success
70
-
71
- # Rollback the transitions if the transaction was unsuccessful
72
- transitions.each {|transition| transition.rollback} unless success
73
- end
74
-
75
- success
76
- end
77
-
78
- # Runs one or more transitions within a transaction. See StateMachine::Transition.perform
79
- # for more information.
80
- def perform_within_transaction(transitions, options = {})
81
- success = false
82
- transitions.first.within_transaction do
83
- success = perform(transitions, options)
84
- end
85
-
86
- success
87
- end
88
- end
89
-
90
- # The object being transitioned
91
- attr_reader :object
92
-
93
- # The state machine for which this transition is defined
94
- attr_reader :machine
95
-
96
- # The event that triggered the transition
97
- attr_reader :event
98
-
99
- # The fully-qualified name of the event that triggered the transition
100
- attr_reader :qualified_event
101
-
102
- # The original state value *before* the transition
103
- attr_reader :from
104
-
105
- # The original state name *before* the transition
106
- attr_reader :from_name
107
-
108
- # The original fully-qualified state name *before* transition
109
- attr_reader :qualified_from_name
110
-
111
- # The new state value *after* the transition
112
- attr_reader :to
113
-
114
- # The new state name *after* the transition
115
- attr_reader :to_name
116
-
117
- # The new fully-qualified state name *after* the transition
118
- attr_reader :qualified_to_name
119
-
120
- # The arguments passed in to the event that triggered the transition
121
- # (does not include the +run_action+ boolean argument if specified)
122
- attr_accessor :args
123
-
124
- # The result of invoking the action associated with the machine
125
- attr_reader :result
126
-
127
- # Creates a new, specific transition
128
- def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc:
129
- @object = object
130
- @machine = machine
131
- @args = []
132
-
133
- # Event information
134
- event = machine.events.fetch(event)
135
- @event = event.name
136
- @qualified_event = event.qualified_name
137
-
138
- # From state information
139
- from_state = machine.states.fetch(from_name)
140
- @from = read_state ? machine.read(object, :state) : from_state.value
141
- @from_name = from_state.name
142
- @qualified_from_name = from_state.qualified_name
143
-
144
- # To state information
145
- to_state = machine.states.fetch(to_name)
146
- @to = to_state.value
147
- @to_name = to_state.name
148
- @qualified_to_name = to_state.qualified_name
149
- end
150
-
151
- # The attribute which this transition's machine is defined for
152
- def attribute
153
- machine.attribute
154
- end
155
-
156
- # The action that will be run when this transition is performed
157
- def action
158
- machine.action
159
- end
160
-
161
- # Does this transition represent a loopback (i.e. the from and to state
162
- # are the same)
163
- #
164
- # == Example
165
- #
166
- # machine = StateMachine.new(Vehicle)
167
- # StateMachine::Transition.new(Vehicle.new, machine, :park, :parked, :parked).loopback? # => true
168
- # StateMachine::Transition.new(Vehicle.new, machine, :park, :idling, :parked).loopback? # => false
169
- def loopback?
170
- from_name == to_name
171
- end
172
-
173
- # A hash of all the core attributes defined for this transition with their
174
- # names as keys and values of the attributes as values.
175
- #
176
- # == Example
177
- #
178
- # machine = StateMachine.new(Vehicle)
179
- # transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
180
- # transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
181
- def attributes
182
- @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to}
183
- end
184
-
185
- # Runs the actual transition and any before/after callbacks associated
186
- # with the transition. The action associated with the transition/machine
187
- # can be skipped by passing in +false+.
188
- #
189
- # == Examples
190
- #
191
- # class Vehicle
192
- # state_machine :action => :save do
193
- # ...
194
- # end
195
- # end
196
- #
197
- # vehicle = Vehicle.new
198
- # transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling)
199
- # transition.perform # => Runs the +save+ action after setting the state attribute
200
- # transition.perform(false) # => Only sets the state attribute
201
- def perform(*args)
202
- run_action = [true, false].include?(args.last) ? args.pop : true
203
- self.args = args
204
-
205
- # Run the transition
206
- self.class.perform_within_transaction([self], :action => run_action)
207
- end
208
-
209
- # Runs a block within a transaction for the object being transitioned.
210
- # By default, transactions are a no-op unless otherwise defined by the
211
- # machine's integration.
212
- def within_transaction
213
- machine.within_transaction(object) do
214
- yield
215
- end
216
- end
217
-
218
- # Runs the machine's +before+ callbacks for this transition. Only
219
- # callbacks that are configured to match the event, from state, and to
220
- # state will be invoked.
221
- #
222
- # Once the callbacks are run, they cannot be run again until this transition
223
- # is reset.
224
- #
225
- # == Example
226
- #
227
- # class Vehicle
228
- # state_machine do
229
- # before_transition :on => :ignite, :do => lambda {|vehicle| ...}
230
- # end
231
- # end
232
- #
233
- # vehicle = Vehicle.new
234
- # transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling)
235
- # transition.before
236
- def before
237
- result = false
238
-
239
- catch(:halt) do
240
- unless @before_run
241
- callback(:before)
242
- @before_run = true
243
- end
244
-
245
- result = true
246
- end
247
-
248
- result
249
- end
250
-
251
- # Transitions the current value of the state to that specified by the
252
- # transition. Once the state is persisted, it cannot be persisted again
253
- # until this transition is reset.
254
- #
255
- # == Example
256
- #
257
- # class Vehicle
258
- # state_machine do
259
- # event :ignite do
260
- # transition :parked => :idling
261
- # end
262
- # end
263
- # end
264
- #
265
- # vehicle = Vehicle.new
266
- # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
267
- # transition.persist
268
- #
269
- # vehicle.state # => 'idling'
270
- def persist
271
- unless @persisted
272
- machine.write(object, :state, to)
273
- @persisted = true
274
- end
275
- end
276
-
277
- # Runs the machine's +after+ callbacks for this transition. Only
278
- # callbacks that are configured to match the event, from state, and to
279
- # state will be invoked.
280
- #
281
- # The result can be used to indicate whether the associated machine action
282
- # was executed successfully.
283
- #
284
- # Once the callbacks are run, they cannot be run again until this transition
285
- # is reset.
286
- #
287
- # == Halting
288
- #
289
- # If any callback throws a <tt>:halt</tt> exception, it will be caught
290
- # and the callback chain will be automatically stopped. However, this
291
- # exception will not bubble up to the caller since +after+ callbacks
292
- # should never halt the execution of a +perform+.
293
- #
294
- # == Example
295
- #
296
- # class Vehicle
297
- # state_machine do
298
- # after_transition :on => :ignite, :do => lambda {|vehicle| ...}
299
- #
300
- # event :ignite do
301
- # transition :parked => :idling
302
- # end
303
- # end
304
- # end
305
- #
306
- # vehicle = Vehicle.new
307
- # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
308
- # transition.after(true)
309
- def after(result = nil, success = true)
310
- @result = result
311
-
312
- catch(:halt) do
313
- unless @after_run
314
- callback(:after, :success => success)
315
- @after_run = true
316
- end
317
- end
318
-
319
- true
320
- end
321
-
322
- # Rolls back changes made to the object's state via this transition. This
323
- # will revert the state back to the +from+ value.
324
- #
325
- # == Example
326
- #
327
- # class Vehicle
328
- # state_machine :initial => :parked do
329
- # event :ignite do
330
- # transition :parked => :idling
331
- # end
332
- # end
333
- # end
334
- #
335
- # vehicle = Vehicle.new # => #<Vehicle:0xb7b7f568 @state="parked">
336
- # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
337
- #
338
- # # Persist the new state
339
- # vehicle.state # => "parked"
340
- # transition.persist
341
- # vehicle.state # => "idling"
342
- #
343
- # # Roll back to the original state
344
- # transition.rollback
345
- # vehicle.state # => "parked"
346
- def rollback
347
- reset
348
- machine.write(object, :state, from)
349
- end
350
-
351
- # Resets any tracking of which callbacks have already been run and whether
352
- # the state has already been persisted
353
- def reset
354
- @before_run = @persisted = @after_run = false
355
- end
356
-
357
- # Generates a nicely formatted description of this transitions's contents.
358
- #
359
- # For example,
360
- #
361
- # transition = StateMachine::Transition.new(object, machine, :ignite, :parked, :idling)
362
- # transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
363
- def inspect
364
- "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>"
365
- end
366
-
367
- protected
368
- # Gets a hash of the context defining this unique transition (including
369
- # event, from state, and to state).
370
- #
371
- # == Example
372
- #
373
- # machine = StateMachine.new(Vehicle)
374
- # transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
375
- # transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
376
- def context
377
- @context ||= {:on => event, :from => from_name, :to => to_name}
378
- end
379
-
380
- # Runs the callbacks of the given type for this transition. This will
381
- # only invoke callbacks that exactly match the event, from state, and
382
- # to state that describe this transition.
383
- #
384
- # Additional callback parameters can be specified. By default, this
385
- # transition is also passed into callbacks.
386
- def callback(type, context = {})
387
- context = self.context.merge(context)
388
-
389
- machine.callbacks[type].each do |callback|
390
- callback.call(object, context, self)
391
- end
392
- end
393
- end
394
- end