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.
Files changed (42) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +162 -23
  4. data/Rakefile +3 -18
  5. data/lib/state_machine.rb +3 -4
  6. data/lib/state_machine/callback.rb +65 -13
  7. data/lib/state_machine/eval_helpers.rb +20 -4
  8. data/lib/state_machine/initializers.rb +4 -0
  9. data/lib/state_machine/initializers/merb.rb +1 -0
  10. data/lib/state_machine/initializers/rails.rb +7 -0
  11. data/lib/state_machine/integrations.rb +21 -6
  12. data/lib/state_machine/integrations/active_model.rb +414 -0
  13. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  14. data/lib/state_machine/integrations/{active_record → active_model}/observer.rb +7 -7
  15. data/lib/state_machine/integrations/active_record.rb +65 -129
  16. data/lib/state_machine/integrations/active_record/locale.rb +4 -11
  17. data/lib/state_machine/integrations/data_mapper.rb +24 -6
  18. data/lib/state_machine/integrations/data_mapper/observer.rb +36 -0
  19. data/lib/state_machine/integrations/mongo_mapper.rb +295 -0
  20. data/lib/state_machine/integrations/sequel.rb +33 -7
  21. data/lib/state_machine/machine.rb +121 -23
  22. data/lib/state_machine/machine_collection.rb +12 -103
  23. data/lib/state_machine/transition.rb +125 -164
  24. data/lib/state_machine/transition_collection.rb +244 -0
  25. data/lib/tasks/state_machine.rb +12 -15
  26. data/test/functional/state_machine_test.rb +11 -1
  27. data/test/unit/callback_test.rb +305 -32
  28. data/test/unit/eval_helpers_test.rb +103 -1
  29. data/test/unit/event_test.rb +2 -1
  30. data/test/unit/guard_test.rb +2 -1
  31. data/test/unit/integrations/active_model_test.rb +909 -0
  32. data/test/unit/integrations/active_record_test.rb +1542 -1292
  33. data/test/unit/integrations/data_mapper_test.rb +1369 -1041
  34. data/test/unit/integrations/mongo_mapper_test.rb +1349 -0
  35. data/test/unit/integrations/sequel_test.rb +1214 -985
  36. data/test/unit/integrations_test.rb +8 -0
  37. data/test/unit/machine_collection_test.rb +140 -513
  38. data/test/unit/machine_test.rb +212 -10
  39. data/test/unit/state_test.rb +2 -1
  40. data/test/unit/transition_collection_test.rb +2098 -0
  41. data/test/unit/transition_test.rb +704 -552
  42. 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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2009 Aaron Pfefier
1
+ Copyright (c) 2006-2010 Aaron Pfefier
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -7,7 +7,7 @@ Ruby class.
7
7
 
8
8
  API
9
9
 
10
- * http://api.pluginaweek.org/state_machine
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 integration
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
- validates_present :seatbelt_on
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:rails CLASS=Vehicle
559
+ rake state_machine:draw CLASS=Vehicle
429
560
 
430
- If you are using this library as a gem, the following must be added to the end
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:merb CLASS=Vehicle
580
+ rake state_machine:draw CLASS=Vehicle
450
581
 
451
582
  === Interactive graphs
452
583
 
453
- Jean Bovet - {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html].
454
- This is a great tool for "simulating, visualizing and transforming finite state
455
- automata and Turing Machines". This tool can help in the creation of states and
456
- events for your models. It is cross-platform, written in Java.
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 entire test suite (will test ActiveRecord, DataMapper, and Sequel
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
- Target specific versions of integrations like so:
595
+ Test specific versions of integrations like so:
466
596
 
467
- rake test AR_VERSION=2.0.0 DM_VERSION=0.9.4 SEQUEL_VERSION=2.8.0
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 causes +after+ callbacks for
474
- attribute-based event transitions to fail
475
- * Overridden event methods won't get invoked when using attribute-based event
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.8.1'
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 = spec.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 => [:gem, :package] do
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 :data_mapper, :active_record, and :sequel. By default, this
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
- # Register rake tasks for supported libraries
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 after a specific transition occurs.
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
- @methods.each do |method|
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(*(arity == 0 ? [] : args), &block)
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(*(arity == 0 ? [] : args))
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