Skip to content

Commit

Permalink
Dispatch state behavior to the superclass if it's undefined for a par…
Browse files Browse the repository at this point in the history
…ticular state
  • Loading branch information
Sandro Turriate and Tim Pope authored and obrie committed Mar 14, 2010
1 parent 1c61dbd commit cb3a8de
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rdoc
@@ -1,5 +1,6 @@
== master

* Dispatch state behavior to the superclass if it's undefined for a particular state [Sandro Turriate and Tim Pope]
* Fix state / event names not supporting i18n in ActiveRecord
* Fix original ActiveRecord::Observer#update not being used for non-state_machine callbacks [Jeremy Wells]
* Add support for ActiveRecord 3.0
Expand Down
8 changes: 4 additions & 4 deletions lib/state_machine/state.rb
Expand Up @@ -168,7 +168,7 @@ def context(&block)
# Calls the method defined by the current state of the machine
context.class_eval <<-end_eval, __FILE__, __LINE__
def #{method}(*args, &block)
self.class.state_machine(#{machine_name.inspect}).states.match!(self).call(self, #{method.inspect}, *args, &block)
self.class.state_machine(#{machine_name.inspect}).states.match!(self).call(self, #{method.inspect}, lambda {super}, *args, &block)
end
end_eval
end
Expand All @@ -185,13 +185,13 @@ def #{method}(*args, &block)
#
# If the method has never been defined for this state, then a NoMethodError
# will be raised.
def call(object, method, *args, &block)
def call(object, method, method_missing = nil, *args, &block)
if context_method = methods[method.to_sym]
# Method is defined by the state: proxy it through
context_method.bind(object).call(*args, &block)
else
# Raise exception as if the method never existed on the original object
raise NoMethodError, "undefined method '#{method}' for #{object} with #{name || 'nil'} #{machine.name}"
# Dispatch to the superclass since this state doesn't handle the method
method_missing.call if method_missing
end
end

Expand Down
22 changes: 21 additions & 1 deletion test/functional/state_machine_test.rb
Expand Up @@ -131,6 +131,10 @@ def fix
auto_shop.fix_vehicle
end

def decibels
0.0
end

private
# Safety first! Puts on our seatbelt
def put_on_seatbelt
Expand Down Expand Up @@ -169,7 +173,13 @@ class Car < Vehicle
end

class Motorcycle < Vehicle
state_machine :initial => :idling
state_machine :initial => :idling do
state :first_gear do
def decibels
1.0
end
end
end
end

class TrafficLight
Expand Down Expand Up @@ -765,6 +775,16 @@ def test_should_not_allow_crash
def test_should_not_allow_repair
assert !@motorcycle.repair
end

def test_should_inherit_decibels_from_superclass
@motorcycle.park
assert_equal 0.0, @motorcycle.decibels
end

def test_should_use_decibels_defined_in_state
@motorcycle.shift_up
assert_equal 1.0, @motorcycle.decibels
end
end

class CarTest < Test::Unit::TestCase
Expand Down
11 changes: 5 additions & 6 deletions test/unit/state_test.rb
Expand Up @@ -626,9 +626,8 @@ def speed
@object = @klass.new
end

def test_should_raise_an_exception
exception = assert_raise(NoMethodError) { @state.call(@object, :invalid) }
assert_equal "undefined method 'invalid' for #{@object} with idling state", exception.message
def test_should_call_method_missing_arg
assert_equal 1, @state.call(@object, :invalid, lambda {1})
end
end

Expand All @@ -648,19 +647,19 @@ def speed(arg = nil)
end

def test_should_not_raise_an_exception
assert_nothing_raised { @state.call(@object, :speed) }
assert_nothing_raised { @state.call(@object, :speed, lambda {raise}) }
end

def test_should_pass_arguments_through
assert_equal 1, @state.call(@object, :speed, 1)
assert_equal 1, @state.call(@object, :speed, lambda {}, 1)
end

def test_should_pass_blocks_through
assert_equal [nil, 1], @state.call(@object, :speed) {1}
end

def test_should_pass_both_arguments_and_blocks_through
assert_equal [1, 2], @state.call(@object, :speed, 1) {2}
assert_equal [1, 2], @state.call(@object, :speed, lambda {}, 1) {2}
end
end

Expand Down

0 comments on commit cb3a8de

Please sign in to comment.