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