mongoid_state_machine 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ !log*.rb
2
+ *.log
3
+ .DS_Store
4
+ *.db
5
+ rdoc/*
6
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :gemcutter
2
+
3
+ gemspec
@@ -0,0 +1,64 @@
1
+ = Acts As State Machine for Mongoid
2
+
3
+ It's a customized plugin from http://github.com/omghax/acts_as_state_machine that works smoothly through Mongoid.
4
+
5
+ I only removed the Class Methods that were using the method with_scope (ActiveRecord) because it's not supported in Mongoid (not a problem for me)
6
+
7
+ == Installation
8
+
9
+ Add the gem into your Gemfile
10
+
11
+ gem "mongoid_state_machine", :require => "mongoid/state_machine"
12
+
13
+ And run `bundle install`
14
+
15
+ == Example
16
+
17
+ class Order
18
+ include Mongoid::Document
19
+
20
+ include Mongoid::StateMachine
21
+
22
+ state_machine :initial => :opened
23
+
24
+ state :opened
25
+ state :closed, :enter => Proc.new {|o| Mailer.send_notice(o)}
26
+ state :returned
27
+
28
+ event :close do
29
+ transitions :to => :closed, :from => :opened
30
+ end
31
+
32
+ event :return do
33
+ transitions :to => :returned, :from => :closed
34
+ end
35
+ end
36
+
37
+ o = Order.create
38
+ o.close! # notice is sent by mailer
39
+ o.return!
40
+
41
+ == Acknowledge
42
+ This project was originally developed by Scott Barron
43
+
44
+ == License
45
+ Copyright (c) 2010 Bruno Azisaka Maciel
46
+
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of this software and associated documentation files (the
49
+ "Software"), to deal in the Software without restriction, including
50
+ without limitation the rights to use, copy, modify, merge, publish,
51
+ distribute, sublicense, and/or sell copies of the Software, and to
52
+ permit persons to whom the Software is furnished to do so, subject to
53
+ the following conditions:
54
+
55
+ The above copyright notice and this permission notice shall be
56
+ included in all copies or substantial portions of the Software.
57
+
58
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
59
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
60
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
61
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
62
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
63
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
64
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :default => :test
5
+
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'lib' << 'test'
9
+ test.pattern = 'test/**/test_*.rb'
10
+ test.verbose = true
11
+ end
@@ -0,0 +1,11 @@
1
+ * Currently invalid events are ignored, create an option so that they can be
2
+ ignored or raise an exception.
3
+
4
+ * Query for a list of possible next states.
5
+
6
+ * Make listing states optional since they can be inferred from the events.
7
+ Only required to list a state if you want to define a transition block for it.
8
+
9
+ * Real transition actions
10
+
11
+ * Default states
@@ -0,0 +1,231 @@
1
+ module Mongoid
2
+ module StateMachine #:nodoc:
3
+
4
+ VERSION = "0.1"
5
+
6
+ class InvalidState < Exception #:nodoc:
7
+ end
8
+ class NoInitialState < Exception #:nodoc:
9
+ end
10
+
11
+ def self.included(base) #:nodoc:
12
+ base.extend ActMacro
13
+ end
14
+
15
+ module SupportingClasses
16
+ # Default transition action. Always returns true.
17
+ NOOP = lambda { |o| true }
18
+
19
+ class State
20
+ attr_reader :name, :value
21
+
22
+ def initialize(name, options)
23
+ @name = name.to_sym
24
+ @value = (options[:value] || @name).to_s
25
+ @after = Array(options[:after])
26
+ @enter = options[:enter] || NOOP
27
+ @exit = options[:exit] || NOOP
28
+ end
29
+
30
+ def entering(record)
31
+ record.send(:run_transition_action, @enter)
32
+ end
33
+
34
+ def entered(record)
35
+ @after.each { |action| record.send(:run_transition_action, action) }
36
+ end
37
+
38
+ def exited(record)
39
+ record.send(:run_transition_action, @exit)
40
+ end
41
+ end
42
+
43
+ class StateTransition
44
+ attr_reader :from, :to, :opts
45
+
46
+ def initialize(options)
47
+ @from = options[:from].to_s
48
+ @to = options[:to].to_s
49
+ @guard = options[:guard] || NOOP
50
+ @opts = options
51
+ end
52
+
53
+ def guard(obj)
54
+ @guard ? obj.send(:run_transition_action, @guard) : true
55
+ end
56
+
57
+ def perform(record)
58
+ return false unless guard(record)
59
+ loopback = record.current_state.to_s == to
60
+ states = record.class.read_inheritable_attribute(:states)
61
+ next_state = states[to]
62
+ old_state = states[record.current_state.to_s]
63
+
64
+ next_state.entering(record) unless loopback
65
+
66
+ next_state.entered(record) unless loopback
67
+ old_state.exited(record) unless loopback
68
+
69
+ record.send("#{record.class.state_column}=", next_state.value)
70
+ record.save
71
+ end
72
+
73
+ def ==(obj)
74
+ @from == obj.from && @to == obj.to
75
+ end
76
+ end
77
+
78
+ class Event
79
+ attr_reader :name
80
+ attr_reader :transitions
81
+ attr_reader :opts
82
+
83
+ def initialize(name, opts, transition_table, &block)
84
+ @name = name.to_sym
85
+ @transitions = transition_table[@name] = []
86
+ instance_eval(&block) if block
87
+ @opts = opts
88
+ @opts.freeze
89
+ @transitions.freeze
90
+ freeze
91
+ end
92
+
93
+ def next_states(record)
94
+ @transitions.select { |t| t.from == record.current_state.to_s }
95
+ end
96
+
97
+ def fire(record)
98
+ next_states(record).each do |transition|
99
+ break true if transition.perform(record)
100
+ end
101
+ end
102
+
103
+ def transitions(trans_opts)
104
+ Array(trans_opts[:from]).each do |s|
105
+ @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ module ActMacro
112
+ # Configuration options are
113
+ #
114
+ # * +column+ - specifies the column name to use for keeping the state (default: state)
115
+ # * +initial+ - specifies an initial state for newly created objects (required)
116
+ def state_machine(options = {})
117
+ class_eval do
118
+ extend ClassMethods
119
+ include InstanceMethods
120
+
121
+ raise NoInitialState unless options[:initial]
122
+
123
+ write_inheritable_attribute :states, {}
124
+ write_inheritable_attribute :initial_state, options[:initial]
125
+ write_inheritable_attribute :transition_table, {}
126
+ write_inheritable_attribute :event_table, {}
127
+ write_inheritable_attribute :state_column, options[:column] || 'state'
128
+
129
+ class_inheritable_reader :initial_state
130
+ class_inheritable_reader :state_column
131
+ class_inheritable_reader :transition_table
132
+ class_inheritable_reader :event_table
133
+
134
+ before_create :set_initial_state
135
+ after_create :run_initial_state_actions
136
+ end
137
+ end
138
+ end
139
+
140
+ module InstanceMethods
141
+ def set_initial_state #:nodoc:
142
+ send("#{self.class.state_column}=", self.class.initial_state.to_s)
143
+ end
144
+
145
+ def run_initial_state_actions
146
+ initial = self.class.read_inheritable_attribute(:states)[self.class.initial_state.to_s]
147
+ initial.entering(self)
148
+ initial.entered(self)
149
+ end
150
+
151
+ # Returns the current state the object is in, as a Ruby symbol.
152
+ def current_state
153
+ self.send(self.class.state_column).to_sym
154
+ end
155
+
156
+ # Returns what the next state for a given event would be, as a Ruby symbol.
157
+ def next_state_for_event(event)
158
+ ns = next_states_for_event(event)
159
+ ns.empty? ? nil : ns.first.to.to_sym
160
+ end
161
+
162
+ def next_states_for_event(event)
163
+ self.class.read_inheritable_attribute(:transition_table)[event.to_sym].select do |s|
164
+ s.from == current_state.to_s
165
+ end
166
+ end
167
+
168
+ def run_transition_action(action)
169
+ Symbol === action ? self.method(action).call : action.call(self)
170
+ end
171
+ private :run_transition_action
172
+ end
173
+
174
+ module ClassMethods
175
+ # Returns an array of all known states.
176
+ def states
177
+ read_inheritable_attribute(:states).keys.collect { |state| state.to_sym }
178
+ end
179
+
180
+ # Define an event. This takes a block which describes all valid transitions
181
+ # for this event.
182
+ #
183
+ # Example:
184
+ #
185
+ # class Order
186
+ # acts_as_state_machine :initial => :open
187
+ #
188
+ # state :open
189
+ # state :closed
190
+ #
191
+ # event :close_order do
192
+ # transitions :to => :closed, :from => :open
193
+ # end
194
+ # end
195
+ #
196
+ # +transitions+ takes a hash where <tt>:to</tt> is the state to transition
197
+ # to and <tt>:from</tt> is a state (or Array of states) from which this
198
+ # event can be fired.
199
+ #
200
+ # This creates an instance method used for firing the event. The method
201
+ # created is the name of the event followed by an exclamation point (!).
202
+ # Example: <tt>order.close_order!</tt>.
203
+ def event(event, opts={}, &block)
204
+ tt = read_inheritable_attribute(:transition_table)
205
+
206
+ e = SupportingClasses::Event.new(event, opts, tt, &block)
207
+ write_inheritable_hash(:event_table, event.to_sym => e)
208
+ define_method("#{event.to_s}!") { e.fire(self) }
209
+ end
210
+
211
+ # Define a state of the system. +state+ can take an optional Proc object
212
+ # which will be executed every time the system transitions into that
213
+ # state. The proc will be passed the current object.
214
+ #
215
+ # Example:
216
+ #
217
+ # class Order
218
+ # acts_as_state_machine :initial => :open
219
+ #
220
+ # state :open
221
+ # state :closed, Proc.new { |o| Mailer.send_notice(o) }
222
+ # end
223
+ def state(name, opts={})
224
+ state = SupportingClasses::State.new(name, opts)
225
+ write_inheritable_hash(:states, state.value => state)
226
+
227
+ define_method("#{state.name}?") { current_state.to_s == state.value }
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path("../lib/mongoid/state_machine", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "mongoid_state_machine"
5
+ s.version = Mongoid::StateMachine::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["Bruno Azisaka Maciel"]
8
+ s.email = ["bruno@azisaka.com.br"]
9
+ s.homepage = "https://github.com/azisaka/mongoid_state_machine"
10
+ s.summary = "A fork from the original State Machine to run on top of Mongoid"
11
+ s.description = "A fork from the original State Machine to run on top of Mongoid"
12
+
13
+ s.required_rubygems_version = ">= 1.3.6"
14
+ s.add_dependency "mongoid", ">= 2.0.0.beta.19"
15
+
16
+ s.add_development_dependency "bundler", ">= 1.0.0"
17
+ s.add_development_dependency "rake", ">= 0"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
21
+ s.require_path = 'lib'
22
+
23
+ s.rdoc_options = ["--charset=UTF-8"]
24
+ end
@@ -0,0 +1,489 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+
4
+ $:.unshift File.dirname(__FILE__) + "/../../lib"
5
+ require "init"
6
+
7
+ connection = Mongo::Connection.new
8
+ Mongoid.database = connection.db('state_machine_test')
9
+
10
+ class Conversation
11
+ include Mongoid::Document
12
+ include Mongoid::StateMachine
13
+
14
+ attr_writer :can_close
15
+ attr_accessor :read_enter, :read_exit,
16
+ :needs_attention_enter, :needs_attention_after,
17
+ :read_after_first, :read_after_second,
18
+ :closed_after
19
+
20
+ field :state
21
+ field :state_machine
22
+
23
+ # How's THAT for self-documenting? ;-)
24
+ def always_true
25
+ true
26
+ end
27
+
28
+ def can_close?
29
+ !!@can_close
30
+ end
31
+
32
+ def read_enter_action
33
+ self.read_enter = true
34
+ end
35
+
36
+ def read_after_first_action
37
+ self.read_after_first = true
38
+ end
39
+
40
+ def read_after_second_action
41
+ self.read_after_second = true
42
+ end
43
+
44
+ def closed_after_action
45
+ self.closed_after = true
46
+ end
47
+ end
48
+
49
+ class Mongoid::StateMachineTest < Test::Unit::TestCase
50
+ include Mongoid::StateMachine
51
+
52
+ def after
53
+ Conversation.destroy_all
54
+ end
55
+
56
+ def teardown
57
+ Conversation.class_eval do
58
+ write_inheritable_attribute :states, {}
59
+ write_inheritable_attribute :initial_state, nil
60
+ write_inheritable_attribute :transition_table, {}
61
+ write_inheritable_attribute :event_table, {}
62
+ write_inheritable_attribute :state_column, "state"
63
+
64
+ # Clear out any callbacks that were set by state_machine.
65
+ write_inheritable_attribute :before_create, []
66
+ write_inheritable_attribute :after_create, []
67
+ end
68
+ end
69
+
70
+ def test_no_initial_value_raises_exception
71
+ assert_raises(NoInitialState) do
72
+ Conversation.class_eval do
73
+ state_machine
74
+ end
75
+ end
76
+ end
77
+
78
+ def test_state_column
79
+ Conversation.class_eval do
80
+ state_machine :initial => :needs_attention, :column => "state_machine"
81
+ state :needs_attention
82
+ end
83
+
84
+ assert_equal "state_machine", Conversation.state_column
85
+ end
86
+
87
+ def test_initial_state_value
88
+ Conversation.class_eval do
89
+ state_machine :initial => :needs_attention
90
+ state :needs_attention
91
+ end
92
+
93
+ assert_equal :needs_attention, Conversation.initial_state
94
+ end
95
+
96
+ def test_initial_state
97
+ Conversation.class_eval do
98
+ state_machine :initial => :needs_attention
99
+ state :needs_attention
100
+ end
101
+
102
+ c = Conversation.create!
103
+ assert_equal :needs_attention, c.current_state
104
+ assert c.needs_attention?
105
+ end
106
+
107
+ def test_states_were_set
108
+ Conversation.class_eval do
109
+ state_machine :initial => :needs_attention
110
+ state :needs_attention
111
+ state :read
112
+ state :closed
113
+ state :awaiting_response
114
+ state :junk
115
+ end
116
+
117
+ [:needs_attention, :read, :closed, :awaiting_response, :junk].each do |state|
118
+ assert Conversation.states.include?(state)
119
+ end
120
+ end
121
+
122
+ def test_query_methods_created
123
+ Conversation.class_eval do
124
+ state_machine :initial => :needs_attention
125
+ state :needs_attention
126
+ state :read
127
+ state :closed
128
+ state :awaiting_response
129
+ state :junk
130
+ end
131
+
132
+ c = Conversation.create!
133
+ [:needs_attention?, :read?, :closed?, :awaiting_response?, :junk?].each do |query|
134
+ assert c.respond_to?(query)
135
+ end
136
+ end
137
+
138
+ def test_event_methods_created
139
+ Conversation.class_eval do
140
+ state_machine :initial => :needs_attention
141
+ state :needs_attention
142
+ state :read
143
+ state :closed
144
+ state :awaiting_response
145
+ state :junk
146
+
147
+ event(:new_message) {}
148
+ event(:view) {}
149
+ event(:reply) {}
150
+ event(:close) {}
151
+ event(:junk, :note => "finished") {}
152
+ event(:unjunk) {}
153
+ end
154
+
155
+ c = Conversation.create!
156
+ [:new_message!, :view!, :reply!, :close!, :junk!, :unjunk!].each do |event|
157
+ assert c.respond_to?(event)
158
+ end
159
+ end
160
+
161
+ def test_transition_table
162
+ Conversation.class_eval do
163
+ state_machine :initial => :needs_attention
164
+ state :needs_attention
165
+ state :read
166
+ state :closed
167
+ state :awaiting_response
168
+ state :junk
169
+
170
+ event :new_message do
171
+ transitions :to => :needs_attention, :from => [:read, :closed, :awaiting_response]
172
+ end
173
+ end
174
+
175
+ tt = Conversation.transition_table
176
+ assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :read, :to => :needs_attention))
177
+ assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :closed, :to => :needs_attention))
178
+ assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :awaiting_response, :to => :needs_attention))
179
+ end
180
+
181
+ def test_next_state_for_event
182
+ Conversation.class_eval do
183
+ state_machine :initial => :needs_attention
184
+ state :needs_attention
185
+ state :read
186
+
187
+ event :view do
188
+ transitions :to => :read, :from => [:needs_attention, :read]
189
+ end
190
+ end
191
+
192
+ c = Conversation.create!
193
+ assert_equal :read, c.next_state_for_event(:view)
194
+ end
195
+
196
+ def test_change_state
197
+ Conversation.class_eval do
198
+ state_machine :initial => :needs_attention
199
+ state :needs_attention
200
+ state :read
201
+
202
+ event :view do
203
+ transitions :to => :read, :from => [:needs_attention, :read]
204
+ end
205
+ end
206
+
207
+ c = Conversation.create!
208
+ c.view!
209
+ assert c.read?
210
+ end
211
+
212
+ def test_can_go_from_read_to_closed_because_guard_passes
213
+ Conversation.class_eval do
214
+ state_machine :initial => :needs_attention
215
+ state :needs_attention
216
+ state :read
217
+ state :closed
218
+ state :awaiting_response
219
+
220
+ event :view do
221
+ transitions :to => :read, :from => [:needs_attention, :read]
222
+ end
223
+
224
+ event :reply do
225
+ transitions :to => :awaiting_response, :from => [:read, :closed]
226
+ end
227
+
228
+ event :close do
229
+ transitions :to => :closed, :from => [:read, :awaiting_response], :guard => lambda { |o| o.can_close? }
230
+ end
231
+ end
232
+
233
+ c = Conversation.create!
234
+ c.can_close = true
235
+ c.view!
236
+ c.reply!
237
+ c.close!
238
+ assert_equal :closed, c.current_state
239
+ end
240
+
241
+ def test_cannot_go_from_read_to_closed_because_of_guard
242
+ Conversation.class_eval do
243
+ state_machine :initial => :needs_attention
244
+ state :needs_attention
245
+ state :read
246
+ state :closed
247
+ state :awaiting_response
248
+
249
+ event :view do
250
+ transitions :to => :read, :from => [:needs_attention, :read]
251
+ end
252
+
253
+ event :reply do
254
+ transitions :to => :awaiting_response, :from => [:read, :closed]
255
+ end
256
+
257
+ event :close do
258
+ transitions :to => :closed, :from => [:read, :awaiting_response], :guard => lambda { |o| o.can_close? }
259
+ transitions :to => :read, :from => [:read, :awaiting_response], :guard => :always_true
260
+ end
261
+ end
262
+
263
+ c = Conversation.create!
264
+ c.can_close = false
265
+ c.view!
266
+ c.reply!
267
+ c.close!
268
+ assert_equal :read, c.current_state
269
+ end
270
+
271
+ def test_ignore_invalid_events
272
+ Conversation.class_eval do
273
+ state_machine :initial => :needs_attention
274
+ state :needs_attention
275
+ state :read
276
+ state :closed
277
+ state :awaiting_response
278
+ state :junk
279
+
280
+ event :new_message do
281
+ transitions :to => :needs_attention, :from => [:read, :closed, :awaiting_response]
282
+ end
283
+
284
+ event :view do
285
+ transitions :to => :read, :from => [:needs_attention, :read]
286
+ end
287
+
288
+ event :junk, :note => "finished" do
289
+ transitions :to => :junk, :from => [:read, :closed, :awaiting_response]
290
+ end
291
+ end
292
+
293
+ c = Conversation.create
294
+ c.view!
295
+ c.junk!
296
+
297
+ # This is the invalid event
298
+ c.new_message!
299
+ assert_equal :junk, c.current_state
300
+ end
301
+
302
+ def test_entry_action_executed
303
+ Conversation.class_eval do
304
+ state_machine :initial => :needs_attention
305
+ state :needs_attention
306
+ state :read, :enter => :read_enter_action
307
+
308
+ event :view do
309
+ transitions :to => :read, :from => [:needs_attention, :read]
310
+ end
311
+ end
312
+
313
+ c = Conversation.create!
314
+ c.read_enter = false
315
+ c.view!
316
+ assert c.read_enter
317
+ end
318
+
319
+ def test_after_actions_executed
320
+ Conversation.class_eval do
321
+ state_machine :initial => :needs_attention
322
+ state :needs_attention
323
+ state :closed, :after => :closed_after_action
324
+ state :read, :enter => :read_enter_action,
325
+ :exit => Proc.new { |o| o.read_exit = true },
326
+ :after => [:read_after_first_action, :read_after_second_action]
327
+
328
+ event :view do
329
+ transitions :to => :read, :from => [:needs_attention, :read]
330
+ end
331
+
332
+ event :close do
333
+ transitions :to => :closed, :from => [:read, :awaiting_response]
334
+ end
335
+ end
336
+
337
+ c = Conversation.create!
338
+
339
+ c.read_after_first = false
340
+ c.read_after_second = false
341
+ c.closed_after = false
342
+
343
+ c.view!
344
+ assert c.read_after_first
345
+ assert c.read_after_second
346
+
347
+ c.can_close = true
348
+ c.close!
349
+
350
+ assert c.closed_after
351
+ assert_equal :closed, c.current_state
352
+ end
353
+
354
+ def test_after_actions_not_run_on_loopback_transition
355
+ Conversation.class_eval do
356
+ state_machine :initial => :needs_attention
357
+ state :needs_attention
358
+ state :closed, :after => :closed_after_action
359
+ state :read, :after => [:read_after_first_action, :read_after_second_action]
360
+
361
+ event :view do
362
+ transitions :to => :read, :from => :needs_attention
363
+ end
364
+
365
+ event :close do
366
+ transitions :to => :closed, :from => :read
367
+ end
368
+ end
369
+
370
+ c = Conversation.create!
371
+
372
+ c.view!
373
+ c.read_after_first = false
374
+ c.read_after_second = false
375
+ c.view!
376
+
377
+ assert !c.read_after_first
378
+ assert !c.read_after_second
379
+
380
+ c.can_close = true
381
+
382
+ c.close!
383
+ c.closed_after = false
384
+ c.close!
385
+
386
+ assert !c.closed_after
387
+ end
388
+
389
+ def test_exit_action_executed
390
+ Conversation.class_eval do
391
+ state_machine :initial => :needs_attention
392
+ state :junk
393
+ state :needs_attention
394
+ state :read, :exit => lambda { |o| o.read_exit = true }
395
+
396
+ event :view do
397
+ transitions :to => :read, :from => :needs_attention
398
+ end
399
+
400
+ event :junk, :note => "finished" do
401
+ transitions :to => :junk, :from => :read
402
+ end
403
+ end
404
+
405
+ c = Conversation.create!
406
+ c.read_exit = false
407
+ c.view!
408
+ c.junk!
409
+ assert c.read_exit
410
+ end
411
+
412
+ def test_entry_and_exit_not_run_on_loopback_transition
413
+ Conversation.class_eval do
414
+ state_machine :initial => :needs_attention
415
+ state :needs_attention
416
+ state :read, :exit => lambda { |o| o.read_exit = true }
417
+
418
+ event :view do
419
+ transitions :to => :read, :from => [:needs_attention, :read]
420
+ end
421
+ end
422
+
423
+ c = Conversation.create!
424
+ c.view!
425
+ c.read_enter = false
426
+ c.read_exit = false
427
+ c.view!
428
+ assert !c.read_enter
429
+ assert !c.read_exit
430
+ end
431
+
432
+ def test_entry_and_after_actions_called_for_initial_state
433
+ Conversation.class_eval do
434
+ state_machine :initial => :needs_attention
435
+ state :needs_attention, :enter => lambda { |o| o.needs_attention_enter = true },
436
+ :after => lambda { |o| o.needs_attention_after = true }
437
+ end
438
+
439
+ c = Conversation.create!
440
+ assert c.needs_attention_enter
441
+ assert c.needs_attention_after
442
+ end
443
+
444
+ def test_run_transition_action_is_private
445
+ Conversation.class_eval do
446
+
447
+ state_machine :initial => :needs_attention
448
+ state :needs_attention
449
+ end
450
+
451
+ c = Conversation.create!
452
+ assert_raises(NoMethodError) { c.run_transition_action :foo }
453
+ end
454
+
455
+ def test_can_access_events_via_event_table
456
+ Conversation.class_eval do
457
+ state_machine :initial => :needs_attention, :column => "state_machine"
458
+ state :needs_attention
459
+ state :junk
460
+
461
+ event :junk, :note => "finished" do
462
+ transitions :to => :junk, :from => :needs_attention
463
+ end
464
+ end
465
+
466
+ event = Conversation.event_table[:junk]
467
+ assert_equal :junk, event.name
468
+ assert_equal "finished", event.opts[:note]
469
+ end
470
+
471
+ def test_custom_state_values
472
+ Conversation.class_eval do
473
+ state_machine :initial => "NEEDS_ATTENTION", :column => "state_machine"
474
+ state :needs_attention, :value => "NEEDS_ATTENTION"
475
+ state :read, :value => "READ"
476
+
477
+ event :view do
478
+ transitions :to => "READ", :from => ["NEEDS_ATTENTION", "READ"]
479
+ end
480
+ end
481
+
482
+ c = Conversation.create!
483
+ assert_equal "NEEDS_ATTENTION", c.state_machine
484
+ assert c.needs_attention?
485
+ c.view!
486
+ assert_equal "READ", c.state_machine
487
+ assert c.read?
488
+ end
489
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid_state_machine
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Bruno Azisaka Maciel
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-26 00:00:00 -02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongoid
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 62196421
29
+ segments:
30
+ - 2
31
+ - 0
32
+ - 0
33
+ - beta
34
+ - 19
35
+ version: 2.0.0.beta.19
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: bundler
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 23
47
+ segments:
48
+ - 1
49
+ - 0
50
+ - 0
51
+ version: 1.0.0
52
+ type: :development
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :development
67
+ version_requirements: *id003
68
+ description: A fork from the original State Machine to run on top of Mongoid
69
+ email:
70
+ - bruno@azisaka.com.br
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - .gitignore
79
+ - Gemfile
80
+ - README.rdoc
81
+ - Rakefile
82
+ - TODO.rdoc
83
+ - lib/mongoid/state_machine.rb
84
+ - mongoid_state_machine.gemspec
85
+ - test/mongoid/test_state_machine.rb
86
+ has_rdoc: true
87
+ homepage: https://github.com/azisaka/mongoid_state_machine
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options:
92
+ - --charset=UTF-8
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 23
110
+ segments:
111
+ - 1
112
+ - 3
113
+ - 6
114
+ version: 1.3.6
115
+ requirements: []
116
+
117
+ rubyforge_project:
118
+ rubygems_version: 1.3.7
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: A fork from the original State Machine to run on top of Mongoid
122
+ test_files: []
123
+