state_machine 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +162 -23
- data/Rakefile +3 -18
- data/lib/state_machine.rb +3 -4
- data/lib/state_machine/callback.rb +65 -13
- data/lib/state_machine/eval_helpers.rb +20 -4
- data/lib/state_machine/initializers.rb +4 -0
- data/lib/state_machine/initializers/merb.rb +1 -0
- data/lib/state_machine/initializers/rails.rb +7 -0
- data/lib/state_machine/integrations.rb +21 -6
- data/lib/state_machine/integrations/active_model.rb +414 -0
- data/lib/state_machine/integrations/active_model/locale.rb +11 -0
- data/lib/state_machine/integrations/{active_record → active_model}/observer.rb +7 -7
- data/lib/state_machine/integrations/active_record.rb +65 -129
- data/lib/state_machine/integrations/active_record/locale.rb +4 -11
- data/lib/state_machine/integrations/data_mapper.rb +24 -6
- data/lib/state_machine/integrations/data_mapper/observer.rb +36 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +295 -0
- data/lib/state_machine/integrations/sequel.rb +33 -7
- data/lib/state_machine/machine.rb +121 -23
- data/lib/state_machine/machine_collection.rb +12 -103
- data/lib/state_machine/transition.rb +125 -164
- data/lib/state_machine/transition_collection.rb +244 -0
- data/lib/tasks/state_machine.rb +12 -15
- data/test/functional/state_machine_test.rb +11 -1
- data/test/unit/callback_test.rb +305 -32
- data/test/unit/eval_helpers_test.rb +103 -1
- data/test/unit/event_test.rb +2 -1
- data/test/unit/guard_test.rb +2 -1
- data/test/unit/integrations/active_model_test.rb +909 -0
- data/test/unit/integrations/active_record_test.rb +1542 -1292
- data/test/unit/integrations/data_mapper_test.rb +1369 -1041
- data/test/unit/integrations/mongo_mapper_test.rb +1349 -0
- data/test/unit/integrations/sequel_test.rb +1214 -985
- data/test/unit/integrations_test.rb +8 -0
- data/test/unit/machine_collection_test.rb +140 -513
- data/test/unit/machine_test.rb +212 -10
- data/test/unit/state_test.rb +2 -1
- data/test/unit/transition_collection_test.rb +2098 -0
- data/test/unit/transition_test.rb +704 -552
- metadata +16 -3
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.9.0 / 2010-04-12
|
4
|
+
|
5
|
+
* Use attribute-based event transitions whenever possible to ensure consistency
|
6
|
+
* Fix action helpers being defined when the action is *only* defined in the machine's owner class
|
7
|
+
* Disable attribute-based event transitions in DataMapper 0.9.4 - 0.9.6 when dm-validations is being used
|
8
|
+
* Add support for DataMapper 0.10.3+
|
9
|
+
* Add around_transition callbacks
|
10
|
+
* Fix transition failures during save not being handled correctly in Sequel 2.12.0+
|
11
|
+
* Fix attribute-based event transitions not hooking in properly in DataMapper 0.10.0+ and Sequel 2.12.0+
|
12
|
+
* Fix dynamic initial states causing errors in Ruby 1.9+ if no arguments are defined in the block
|
13
|
+
* Add MongoMapper 0.5.5+ support
|
14
|
+
* Add ActiveModel 3.0+ support for use with integrations that implement its interface
|
15
|
+
* Fix DataMapper integration failing when ActiveSupport is loaded in place of Extlib
|
16
|
+
* Add version dependencies for ruby-graphviz
|
17
|
+
* Remove app-specific rails / merb rake tasks in favor of always running state_machine:draw
|
18
|
+
* Add Rails 3 railtie for automatically loading rake tasks when installed as a gem
|
19
|
+
|
3
20
|
== 0.8.1 / 2010-03-14
|
4
21
|
|
5
22
|
* Release gems via rake-gemcutter instead of rubyforge
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -7,7 +7,7 @@ Ruby class.
|
|
7
7
|
|
8
8
|
API
|
9
9
|
|
10
|
-
* http://
|
10
|
+
* http://rdoc.info/projects/pluginaweek/state_machine
|
11
11
|
|
12
12
|
Bugs
|
13
13
|
|
@@ -37,10 +37,8 @@ Some brief, high-level features include:
|
|
37
37
|
* Defining state machines on any Ruby class
|
38
38
|
* Multiple state machines on a single class
|
39
39
|
* Namespaced state machines
|
40
|
-
* before/after transition hooks with explicit transition requirements
|
41
|
-
* ActiveRecord
|
42
|
-
* DataMapper integration
|
43
|
-
* Sequel integration
|
40
|
+
* before/after/around transition hooks with explicit transition requirements
|
41
|
+
* Integration with ActiveModel, ActiveRecord, DataMapper, MongoMapper, and Sequel
|
44
42
|
* State predicates
|
45
43
|
* State-driven instance / class behavior
|
46
44
|
* State values of any data type
|
@@ -70,7 +68,7 @@ Below is an example of many of the features offered by this plugin, including:
|
|
70
68
|
Class definition:
|
71
69
|
|
72
70
|
class Vehicle
|
73
|
-
attr_accessor :seatbelt_on
|
71
|
+
attr_accessor :seatbelt_on, :time_used
|
74
72
|
|
75
73
|
state_machine :state, :initial => :parked do
|
76
74
|
before_transition :parked => any - :parked, :do => :put_on_seatbelt
|
@@ -81,6 +79,12 @@ Class definition:
|
|
81
79
|
vehicle.seatbelt_on = false
|
82
80
|
end
|
83
81
|
|
82
|
+
around_transition do |vehicle, transition, block|
|
83
|
+
start = Time.now
|
84
|
+
block.call
|
85
|
+
vehicle.time_used += Time.now - start
|
86
|
+
end
|
87
|
+
|
84
88
|
event :park do
|
85
89
|
transition [:idling, :first_gear] => :parked
|
86
90
|
end
|
@@ -107,6 +111,7 @@ Class definition:
|
|
107
111
|
|
108
112
|
event :repair do
|
109
113
|
transition :stalled => :parked, :if => :auto_shop_busy?
|
114
|
+
transition :stalled => same # Loopback if we have to
|
110
115
|
end
|
111
116
|
|
112
117
|
state :parked do
|
@@ -143,6 +148,7 @@ Class definition:
|
|
143
148
|
|
144
149
|
def initialize
|
145
150
|
@seatbelt_on = false
|
151
|
+
@time_used = 0
|
146
152
|
super() # NOTE: This *must* be called, otherwise states won't get initialized
|
147
153
|
end
|
148
154
|
|
@@ -229,12 +235,72 @@ libraries. These integrations add library-specific behavior, allowing for state
|
|
229
235
|
machines to work more tightly with the conventions defined by those libraries.
|
230
236
|
|
231
237
|
The integrations currently available include:
|
238
|
+
* ActiveModel classes
|
232
239
|
* ActiveRecord models
|
233
240
|
* DataMapper resources
|
241
|
+
* MongoMapper models
|
234
242
|
* Sequel models
|
235
243
|
|
236
244
|
A brief overview of these integrations is described below.
|
237
245
|
|
246
|
+
=== ActiveModel
|
247
|
+
|
248
|
+
The ActiveModel integration is useful for both standalone usage and for providing
|
249
|
+
the base implementation for ORMs which implement the ActiveModel API. This
|
250
|
+
integration adds support for validation errors, dirty attribute tracking, and
|
251
|
+
observers. For example,
|
252
|
+
|
253
|
+
class Vehicle
|
254
|
+
include ActiveModel::Dirty
|
255
|
+
include ActiveModel::Validations
|
256
|
+
include ActiveModel::Observing
|
257
|
+
|
258
|
+
attr_accessor :state
|
259
|
+
define_attribute_methods [:state]
|
260
|
+
|
261
|
+
state_machine :initial => :parked do
|
262
|
+
before_transition :parked => any - :parked, :do => :put_on_seatbelt
|
263
|
+
after_transition any => :parked do |vehicle, transition|
|
264
|
+
vehicle.seatbelt = 'off'
|
265
|
+
end
|
266
|
+
around_transition :benchmark
|
267
|
+
|
268
|
+
event :ignite do
|
269
|
+
transition :parked => :idling
|
270
|
+
end
|
271
|
+
|
272
|
+
state :first_gear, :second_gear do
|
273
|
+
validates_presence_of :seatbelt_on
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def put_on_seatbelt
|
278
|
+
...
|
279
|
+
end
|
280
|
+
|
281
|
+
def benchmark
|
282
|
+
...
|
283
|
+
yield
|
284
|
+
...
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class VehicleObserver < ActiveModel::Observer
|
289
|
+
# Callback for :ignite event *before* the transition is performed
|
290
|
+
def before_ignite(vehicle, transition)
|
291
|
+
# log message
|
292
|
+
end
|
293
|
+
|
294
|
+
# Generic transition callback *after* the transition is performed
|
295
|
+
def after_transition(vehicle, transition)
|
296
|
+
Audit.log(vehicle, transition)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
For more information about the various behaviors added for ActiveModel state
|
301
|
+
machines and how to build new integrations that use ActiveModel, see
|
302
|
+
StateMachine::Integrations::ActiveModel.
|
303
|
+
|
238
304
|
=== ActiveRecord
|
239
305
|
|
240
306
|
The ActiveRecord integration adds support for database transactions, automatically
|
@@ -246,6 +312,7 @@ saving the record, named scopes, validation errors, and observers. For example,
|
|
246
312
|
after_transition any => :parked do |vehicle, transition|
|
247
313
|
vehicle.seatbelt = 'off'
|
248
314
|
end
|
315
|
+
around_transition :benchmark
|
249
316
|
|
250
317
|
event :ignite do
|
251
318
|
transition :parked => :idling
|
@@ -259,6 +326,12 @@ saving the record, named scopes, validation errors, and observers. For example,
|
|
259
326
|
def put_on_seatbelt
|
260
327
|
...
|
261
328
|
end
|
329
|
+
|
330
|
+
def benchmark
|
331
|
+
...
|
332
|
+
yield
|
333
|
+
...
|
334
|
+
end
|
262
335
|
end
|
263
336
|
|
264
337
|
class VehicleObserver < ActiveRecord::Observer
|
@@ -293,19 +366,26 @@ callbacks, validation errors, and observers. For example,
|
|
293
366
|
after_transition any => :parked do |transition|
|
294
367
|
self.seatbelt = 'off' # self is the record
|
295
368
|
end
|
369
|
+
around_transition :benchmark
|
296
370
|
|
297
371
|
event :ignite do
|
298
372
|
transition :parked => :idling
|
299
373
|
end
|
300
374
|
|
301
375
|
state :first_gear, :second_gear do
|
302
|
-
|
376
|
+
validates_presence_of :seatbelt_on
|
303
377
|
end
|
304
378
|
end
|
305
379
|
|
306
380
|
def put_on_seatbelt
|
307
381
|
...
|
308
382
|
end
|
383
|
+
|
384
|
+
def benchmark
|
385
|
+
...
|
386
|
+
yield
|
387
|
+
...
|
388
|
+
end
|
309
389
|
end
|
310
390
|
|
311
391
|
class VehicleObserver
|
@@ -322,6 +402,12 @@ callbacks, validation errors, and observers. For example,
|
|
322
402
|
after_transition do |transition|
|
323
403
|
Audit.log(self, transition) # self is the record
|
324
404
|
end
|
405
|
+
|
406
|
+
around_transition do |transition, block|
|
407
|
+
# mark start time
|
408
|
+
block.call
|
409
|
+
# mark stop time
|
410
|
+
end
|
325
411
|
end
|
326
412
|
|
327
413
|
*Note* that the DataMapper::Observer integration is optional and only available
|
@@ -330,6 +416,44 @@ when the dm-observer library is installed.
|
|
330
416
|
For more information about the various behaviors added for DataMapper state
|
331
417
|
machines, see StateMachine::Integrations::DataMapper.
|
332
418
|
|
419
|
+
=== MongoMapper
|
420
|
+
|
421
|
+
The MongoMapper integration adds support for automatically saving the record,
|
422
|
+
basic scopes, validation errors and callbacks. For example,
|
423
|
+
|
424
|
+
class Vehicle
|
425
|
+
include MongoMapper::Document
|
426
|
+
|
427
|
+
state_machine :initial => :parked do
|
428
|
+
before_transition :parked => any - :parked, :do => :put_on_seatbelt
|
429
|
+
after_transition any => :parked do |vehicle, transition|
|
430
|
+
vehicle.seatbelt = 'off' # self is the record
|
431
|
+
end
|
432
|
+
around_transition :benchmark
|
433
|
+
|
434
|
+
event :ignite do
|
435
|
+
transition :parked => :idling
|
436
|
+
end
|
437
|
+
|
438
|
+
state :first_gear, :second_gear do
|
439
|
+
validates_presence_of :seatbelt_on
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def put_on_seatbelt
|
444
|
+
...
|
445
|
+
end
|
446
|
+
|
447
|
+
def benchmark
|
448
|
+
...
|
449
|
+
yield
|
450
|
+
...
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
For more information about the various behaviors added for MongoMapper state
|
455
|
+
machines, see StateMachine::Integrations::MongoMapper.
|
456
|
+
|
333
457
|
=== Sequel
|
334
458
|
|
335
459
|
Like the ActiveRecord integration, the Sequel integration adds support for
|
@@ -342,6 +466,7 @@ errors and callbacks. For example,
|
|
342
466
|
after_transition any => :parked do |transition|
|
343
467
|
self.seatbelt = 'off' # self is the record
|
344
468
|
end
|
469
|
+
around_transition :benchmark
|
345
470
|
|
346
471
|
event :ignite do
|
347
472
|
transition :parked => :idling
|
@@ -355,6 +480,12 @@ errors and callbacks. For example,
|
|
355
480
|
def put_on_seatbelt
|
356
481
|
...
|
357
482
|
end
|
483
|
+
|
484
|
+
def benchmark
|
485
|
+
...
|
486
|
+
yield
|
487
|
+
...
|
488
|
+
end
|
358
489
|
end
|
359
490
|
|
360
491
|
For more information about the various behaviors added for Sequel state
|
@@ -425,10 +556,10 @@ environment, meaning that it's unnecessary to specify the actual file to load.
|
|
425
556
|
|
426
557
|
For example,
|
427
558
|
|
428
|
-
rake state_machine:draw
|
559
|
+
rake state_machine:draw CLASS=Vehicle
|
429
560
|
|
430
|
-
If you are using this library as a gem, the following must be added
|
431
|
-
of your application's Rakefile in order for the above task to work:
|
561
|
+
If you are using this library as a gem in Rails 2.x, the following must be added
|
562
|
+
to the end of your application's Rakefile in order for the above task to work:
|
432
563
|
|
433
564
|
require 'tasks/state_machine'
|
434
565
|
|
@@ -446,34 +577,36 @@ files to load.
|
|
446
577
|
|
447
578
|
For example,
|
448
579
|
|
449
|
-
rake state_machine:draw
|
580
|
+
rake state_machine:draw CLASS=Vehicle
|
450
581
|
|
451
582
|
=== Interactive graphs
|
452
583
|
|
453
|
-
Jean Bovet
|
454
|
-
|
455
|
-
automata and Turing Machines".
|
456
|
-
|
584
|
+
Jean Bovet's {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html]
|
585
|
+
is a great tool for "simulating, visualizing and transforming finite state
|
586
|
+
automata and Turing Machines". It can help in the creation of states and events
|
587
|
+
for your models. It is cross-platform, written in Java.
|
457
588
|
|
458
589
|
== Testing
|
459
590
|
|
460
|
-
To run the
|
461
|
-
integrations if the proper dependencies are available):
|
591
|
+
To run the core test suite (does *not* test any of the integrations):
|
462
592
|
|
463
593
|
rake test
|
464
594
|
|
465
|
-
|
595
|
+
Test specific versions of integrations like so:
|
466
596
|
|
467
|
-
rake test
|
597
|
+
rake test INTEGRATION=active_model VERSION=3.0.0
|
598
|
+
rake test INTEGRATION=active_record VERSION=2.0.0
|
599
|
+
rake test INTEGRATION=data_mapper VERSION=0.9.4
|
600
|
+
rake test INTEGRATION=mongo_mapper VERSION=0.5.5
|
601
|
+
rake test INTEGRATION=sequel VERSION=2.8.0
|
468
602
|
|
469
603
|
== Caveats
|
470
604
|
|
471
605
|
The following caveats should be noted when using state_machine:
|
472
606
|
|
473
|
-
* DataMapper: dm-validations 0.9.4 - 0.9.6
|
474
|
-
attribute-based event transitions
|
475
|
-
*
|
476
|
-
transitions
|
607
|
+
* DataMapper: Attribute-based event transitions are disabled when dm-validations 0.9.4 - 0.9.6 is in use
|
608
|
+
* Overridden event methods won't get invoked when using attribute-based event transitions
|
609
|
+
* around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations
|
477
610
|
|
478
611
|
== Dependencies
|
479
612
|
|
@@ -481,6 +614,12 @@ transitions
|
|
481
614
|
|
482
615
|
If using specific integrations:
|
483
616
|
|
617
|
+
* ActiveModel[http://rubyonrails.org] integration: 3.0.0 or later
|
484
618
|
* ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
|
485
619
|
* DataMapper[http://datamapper.org] integration: 0.9.4 or later
|
620
|
+
* MongoMapper[http://mongomapper.com] integration: 0.5.5 or later
|
486
621
|
* Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
|
622
|
+
|
623
|
+
If graphing state machine:
|
624
|
+
|
625
|
+
* ruby-graphviz[http://github.com/glejeune/Ruby-Graphviz]: 0.9.0 or later
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
|
|
6
6
|
|
7
7
|
spec = Gem::Specification.new do |s|
|
8
8
|
s.name = 'state_machine'
|
9
|
-
s.version = '0.
|
9
|
+
s.version = '0.9.0'
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
|
12
12
|
s.description = s.summary
|
@@ -28,7 +28,7 @@ task :default => :test
|
|
28
28
|
desc "Test the #{spec.name} plugin."
|
29
29
|
Rake::TestTask.new(:test) do |t|
|
30
30
|
t.libs << 'lib'
|
31
|
-
t.test_files =
|
31
|
+
t.test_files = ENV['INTEGRATION'] ? Dir["test/unit/integrations/#{ENV['INTEGRATION']}_test.rb"] : Dir['test/{functional,unit}/*_test.rb']
|
32
32
|
t.verbose = true
|
33
33
|
end
|
34
34
|
|
@@ -66,23 +66,8 @@ Rake::GemPackageTask.new(spec) do |p|
|
|
66
66
|
p.gem_spec = spec
|
67
67
|
end
|
68
68
|
|
69
|
-
desc 'Publish the beta gem.'
|
70
|
-
task :pgem => [:package] do
|
71
|
-
require 'rake/contrib/sshpublisher'
|
72
|
-
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
73
|
-
end
|
74
|
-
|
75
|
-
desc 'Publish the API documentation.'
|
76
|
-
task :pdoc => [:rdoc] do
|
77
|
-
require 'rake/contrib/sshpublisher'
|
78
|
-
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
79
|
-
end
|
80
|
-
|
81
|
-
desc 'Publish the API docs and gem'
|
82
|
-
task :publish => [:pgem, :pdoc, :release]
|
83
|
-
|
84
69
|
desc 'Publish the release files to RubyForge.'
|
85
|
-
task :release =>
|
70
|
+
task :release => :package do
|
86
71
|
require 'rake/gemcutter'
|
87
72
|
|
88
73
|
Rake::Gemcutter::Tasks.new(spec)
|
data/lib/state_machine.rb
CHANGED
@@ -24,8 +24,8 @@ module StateMachine
|
|
24
24
|
# Default is nil.
|
25
25
|
# * <tt>:integration</tt> - The name of the integration to use for adding
|
26
26
|
# library-specific behavior to the machine. Built-in integrations
|
27
|
-
# include :
|
28
|
-
# is determined automatically.
|
27
|
+
# include :active_model, :active_record, :data_mapper, :mongo_mapper, and
|
28
|
+
# :sequel. By default, this is determined automatically.
|
29
29
|
#
|
30
30
|
# Configuration options relevant to ORM integrations:
|
31
31
|
# * <tt>:plural</tt> - The pluralized name of the attribute. By default,
|
@@ -384,5 +384,4 @@ Class.class_eval do
|
|
384
384
|
include StateMachine::MacroMethods
|
385
385
|
end
|
386
386
|
|
387
|
-
|
388
|
-
Merb::Plugins.add_rakefiles("#{File.dirname(__FILE__)}/tasks/state_machine") if defined?(Merb::Plugins)
|
387
|
+
require 'state_machine/initializers'
|
@@ -3,7 +3,7 @@ require 'state_machine/eval_helpers'
|
|
3
3
|
|
4
4
|
module StateMachine
|
5
5
|
# Callbacks represent hooks into objects that allow logic to be triggered
|
6
|
-
# before or
|
6
|
+
# before, after, or around a specific set of transitions.
|
7
7
|
class Callback
|
8
8
|
include EvalHelpers
|
9
9
|
|
@@ -62,6 +62,13 @@ module StateMachine
|
|
62
62
|
attr_accessor :terminator
|
63
63
|
end
|
64
64
|
|
65
|
+
# The type of callback chain this callback is for. This can be one of the
|
66
|
+
# following:
|
67
|
+
# * +before+
|
68
|
+
# * +after+
|
69
|
+
# * +around+
|
70
|
+
attr_accessor :type
|
71
|
+
|
65
72
|
# An optional block for determining whether to cancel the callback chain
|
66
73
|
# based on the return value of the callback. By default, the callback
|
67
74
|
# chain never cancels based on the return value (i.e. there is no implicit
|
@@ -112,12 +119,14 @@ module StateMachine
|
|
112
119
|
#
|
113
120
|
# More information about how those options affect the behavior of the
|
114
121
|
# callback can be found in their attribute definitions.
|
115
|
-
def initialize(*args, &block)
|
122
|
+
def initialize(type, *args, &block)
|
123
|
+
@type = type
|
124
|
+
raise ArgumentError, 'Type must be :before, :after, or :around' unless [:before, :after, :around].include?(type)
|
125
|
+
|
116
126
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
117
127
|
@methods = args
|
118
128
|
@methods.concat(Array(options.delete(:do)))
|
119
129
|
@methods << block if block_given?
|
120
|
-
|
121
130
|
raise ArgumentError, 'Method(s) for callback must be specified' unless @methods.any?
|
122
131
|
|
123
132
|
options = {:bind_to_object => self.class.bind_to_object, :terminator => self.class.terminator}.merge(options)
|
@@ -139,32 +148,68 @@ module StateMachine
|
|
139
148
|
end
|
140
149
|
|
141
150
|
# Runs the callback as long as the transition context matches the guard
|
142
|
-
# requirements configured for this callback.
|
151
|
+
# requirements configured for this callback. If a block is provided, it
|
152
|
+
# will be called when the last method has run.
|
143
153
|
#
|
144
154
|
# If a terminator has been configured and it matches the result from the
|
145
155
|
# evaluated method, then the callback chain should be halted.
|
146
|
-
def call(object, context = {}, *args)
|
156
|
+
def call(object, context = {}, *args, &block)
|
147
157
|
if @guard.matches?(object, context)
|
148
|
-
|
149
|
-
result = evaluate_method(object, method, *args)
|
150
|
-
throw :halt if @terminator && @terminator.call(result)
|
151
|
-
end
|
152
|
-
|
158
|
+
run_methods(object, context, 0, *args, &block)
|
153
159
|
true
|
154
160
|
else
|
155
161
|
false
|
156
162
|
end
|
157
163
|
end
|
158
164
|
|
165
|
+
# Verifies that the success requirement for this callback matches the given
|
166
|
+
# value
|
167
|
+
def matches_success?(success)
|
168
|
+
guard.success_requirement.matches?(success)
|
169
|
+
end
|
170
|
+
|
159
171
|
private
|
172
|
+
# Runs all of the methods configured for this callback.
|
173
|
+
#
|
174
|
+
# When running +around+ callbacks, this will evaluate each method and
|
175
|
+
# yield when the last method has yielded. The callback will only halt if
|
176
|
+
# one of the methods does not yield.
|
177
|
+
#
|
178
|
+
# For all other types of callbacks, this will evaluate each method in
|
179
|
+
# order. The callback will only halt if the resulting value from the
|
180
|
+
# method passes the terminator.
|
181
|
+
def run_methods(object, context = {}, index = 0, *args, &block)
|
182
|
+
if type == :around
|
183
|
+
if method = @methods[index]
|
184
|
+
yielded = false
|
185
|
+
evaluate_method(object, method, *args) do
|
186
|
+
yielded = true
|
187
|
+
run_methods(object, context, index + 1, *args, &block)
|
188
|
+
end
|
189
|
+
|
190
|
+
throw :halt unless yielded
|
191
|
+
else
|
192
|
+
yield if block_given?
|
193
|
+
end
|
194
|
+
else
|
195
|
+
@methods.each do |method|
|
196
|
+
result = evaluate_method(object, method, *args)
|
197
|
+
throw :halt if @terminator && @terminator.call(result)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
160
202
|
# Generates a method that can be bound to the object being transitioned
|
161
203
|
# when the callback is invoked
|
162
204
|
def bound_method(block)
|
205
|
+
type = self.type
|
163
206
|
arity = block.arity
|
207
|
+
arity += 1 if arity >= 0
|
208
|
+
arity += 1 if arity == 1 && type == :around
|
164
209
|
|
165
|
-
if RUBY_VERSION >= '1.9'
|
210
|
+
method = if RUBY_VERSION >= '1.9'
|
166
211
|
lambda do |object, *args|
|
167
|
-
object.instance_exec(*
|
212
|
+
object.instance_exec(*args, &block)
|
168
213
|
end
|
169
214
|
else
|
170
215
|
# Generate a thread-safe unbound method that can be used on any object.
|
@@ -181,9 +226,16 @@ module StateMachine
|
|
181
226
|
# Proxy calls to the method so that the method can be bound *and*
|
182
227
|
# the arguments are adjusted
|
183
228
|
lambda do |object, *args|
|
184
|
-
unbound_method.bind(object).call(*
|
229
|
+
unbound_method.bind(object).call(*args)
|
185
230
|
end
|
186
231
|
end
|
232
|
+
|
233
|
+
# Proxy arity to the original block
|
234
|
+
(class << method; self; end).class_eval do
|
235
|
+
define_method(:arity) { arity }
|
236
|
+
end
|
237
|
+
|
238
|
+
method
|
187
239
|
end
|
188
240
|
end
|
189
241
|
end
|