mongoid_state_machine 0.1

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.
@@ -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
+