state_machine 0.8.1 → 0.9.0
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.
- 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
|