Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
New workflow definition cleanly undefines the old convinience methods…
…, s. also #92, #69

* works on both subclassing and dynamic workflow spec assignment
* solving problems caused by metaprogramming with even more metaprogramming ;-)
  • Loading branch information
geekq committed Aug 26, 2013
1 parent 68951c9 commit ff6752f
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 4 deletions.
38 changes: 34 additions & 4 deletions lib/workflow.rb
Expand Up @@ -25,11 +25,28 @@ def workflow(&specification)

private

# Creates the convinience methods like `my_transition!`
def assign_workflow(specification_object)
# TODO Ensure assign_workflow can be only called once
#
# creates the convinence methods, note: there is currently no way
# to reassign different workflow, undefining created methods

# Merging two workflow specifications can **not** be done automically, so
# just make the latest specification win. Same for inheritance -
# definition in the subclass wins.
if respond_to? :inherited_workflow_spec # undefine methods defined by the old workflow_spec
inherited_workflow_spec.states.values.each do |state|
state_name = state.name
module_eval do
undef_method "#{state_name}?"
end

state.events.values.each do |event|
event_name = event.name
module_eval do
undef_method "#{event_name}!".to_sym
undef_method "can_#{event_name}?"
end
end
end
end

@workflow_spec = specification_object
@workflow_spec.states.values.each do |state|
Expand Down Expand Up @@ -230,6 +247,19 @@ def persist_workflow_state(new_value)

def self.included(klass)
klass.send :include, InstanceMethods

# backup the parent workflow spec, making accessible through #inherited_workflow_spec
if klass.superclass.respond_to?(:workflow_spec, true)
klass.module_eval do
# see http://stackoverflow.com/a/2495650/111995 for implementation explanation
pro = Proc.new { klass.superclass.workflow_spec }
singleton_class = class << self; self; end
singleton_class.send(:define_method, :inherited_workflow_spec) do
pro.call
end
end
end

klass.extend ClassMethods

if Object.const_defined?(:ActiveRecord)
Expand Down
54 changes: 54 additions & 0 deletions test/inheritance_test.rb
@@ -0,0 +1,54 @@
require File.join(File.dirname(__FILE__), 'test_helper')
require 'workflow'
class InheritanceTest < ActiveRecordTestCase

test '#69 inheritance' do
class Animal
include Workflow

workflow do

state :conceived do
event :birth, :transition_to => :born
end

state :born do

end
end
end

class Cat < Animal
include Workflow
workflow do

state :upset do
event :scratch, :transition_to => :hiding
end

state :hiding do

end
end
end

assert_equal [:born, :conceived] , Animal.workflow_spec.states.keys.sort
assert_equal [:hiding, :upset], Cat.workflow_spec.states.keys.sort, "Workflow definitions are not inherited"

animal = Animal.new
cat = Cat.new

animal.birth!

assert_raise NoMethodError, 'Methods defined by the old workflow spec should have be gone away' do
cat.birth!
end

assert_equal [:birth!, :halt!, :process_event!], bang_methods(animal)
assert_equal [:halt!, :process_event!, :scratch!], bang_methods(cat)
end

def bang_methods(obj)
(obj.public_methods-Object.public_methods).select {|m| m =~ /!$/}.sort
end
end

0 comments on commit ff6752f

Please sign in to comment.