Permalink
Browse files

Force state, :call option for listener

  • Loading branch information...
1 parent 9fc311b commit 018ec6469f9454b71f8b7361a12a6f54d192a223 @Ptico committed Mar 5, 2012
View
@@ -1,6 +1,107 @@
-Not for production use
+Switcher is lightweight event-driven state machine.
-Docs: See specs
+## Basic usage:
+
+```ruby
+class Package
+ include Switcher::Object
+
+ def initialize(weight)
+ @sent_weight = weight.to_f
+ @tracking_number = nil
+ end
+
+ switcher :delivery do
+ state :requested do
+ on :send, switch_to: :sent do |ev, num|
+ @tracking_number = num
+ end
+ end
+
+ state :sent do
+ before :receive, call: :check_number
+
+ on :receive do |ev, number, weight|
+ if weight < (@weight - 0.3)
+ ev.switch_to :stolen
+ else
+ ev.switch_to :received
+ end
+ end
+
+ after :receive do |ev|
+ unpack if delivery_received?
+ end
+
+ on :miss, switch_to: :missed
+ end
+
+ state :received
+ state :stolen
+ state :missed
+ end
+
+ def check_number(ev, number, weight)
+ ev.stop if number.to_s != @tracking_number
+ end
+
+ def unpack
+ puts "TADA!"
+ end
+end
+
+package = Package.new(4.2)
+
+package.delivery # => :requested
+package.can_send? # => true
+package.send! "AB123456CD"
+package.delivery # => :sent
+package.receive!(3.5, "CD654321AB")
+package.delivery # => :sent
+package.receive!(4.1, "AB123456CD")
+# > TADA!
+
+package.delivery # => :received
+package.delivery_prev # => :sent
+package.delivery_received? # => true
+package.can_miss? # => false
+```
+
+## Usage with Active Record:
+
+```ruby
+class User < ActiveRecord::Base
+ include Switcher::ActiveRecord
+
+ switcher :membership do
+ state :guest do
+ on :approve, switch_to: :member
+ end
+ state :member do
+ on :ban, switch_to: :banned do |ev, reason|
+ ev.stop unless reason
+ ban_reason = reason
+ end
+ end
+ state :banned do
+ on :unban, switch_to: :member
+ after :unban do
+ ban_reason = nil
+ end
+ end
+ end
+end
+
+user = User.find(5)
+user.ban! "Stupid bastard"
+user.membership # => :banned
+user.save
+```
+
+## More examples:
+
+* [Wiki](https://github.com/Ptico/switcher/wiki)
+* Specs in spec/
## TODO:
@@ -13,6 +13,9 @@ def switcher_pre_initialize(inst, spec)
define_method(:"#{spec_name}_prev") { self.instance_variable_get(:"@#{spec_name}_statement").state_prev }
define_method(:"#{spec_name}") { self.instance_variable_get(:"@#{spec_name}_statement").state_current }
+ define_method(:"#{spec_name}=") { nil } # FIXME - raise exception
+
+ define_method(:"force_#{spec_name}") { |state| self.instance_variable_get(:"@#{spec_name}_statement").force_state(state.to_sym) }
events = []
@@ -62,6 +65,8 @@ def switch(event, *args)
end
def switch_from(orig, event, *args)
+ return false unless (self.respond_to?(:"can_#{event}?") and self.send(:"can_#{event}?"))
+
self.class.class_variable_get(:@@__specs__).each do |spc|
spc_name = spc.name
@@ -72,6 +77,8 @@ def switch_from(orig, event, *args)
write_attribute("#{spc_name}_prev", self.send(:"#{spc_name}_prev"))
end
end
+
+ true
end
end
@@ -20,6 +20,9 @@ def switcher_pre_initialize(inst, spec)
define_method(:"#{spec_name}_prev") { self.instance_variable_get(:"@#{spec_name}_statement").state_prev }
define_method(:"#{spec_name}") { self.instance_variable_get(:"@#{spec_name}_statement").state_current }
+ define_method(:"#{spec_name}=") { nil }
+
+ define_method(:"force_#{spec_name}") { |state| self.instance_variable_get(:"@#{spec_name}_statement").force_state(state.to_sym) }
events = []
@@ -57,9 +60,13 @@ def switch(event, *args)
end
def switch_from(orig, event, *args)
+ return false unless (self.respond_to?(:"can_#{event}?") and self.send(:"can_#{event}?"))
+
self.class.class_variable_get(:@@__specs__).each do |spc|
self.instance_variable_get(:"@#{spc.name}_statement").publish(event, orig, args)
end
+
+ true
end
end
end
@@ -28,6 +28,9 @@ def after(event, options={}, &block)
def trigger(event, facade, instance, args)
if ev = @events[event]
+ if ev[:options][:call]
+ instance.instance_exec(facade, *args) { |facad, *arguments| self.send(ev[:options][:call], facad, *arguments) }
+ end
instance.instance_exec(facade, *args, &ev[:callback]) if ev[:callback].respond_to?(:call)
if !facade.stopped && ev[:options][:allow_switch] && switch_to = ev[:options][:switch_to]
facade.switch_to(switch_to)
@@ -51,6 +51,12 @@ def publish(event, original, args)
end
end
+ def force_state(state)
+ return unless @spec.states_list.include?(state.to_sym) # FIXME - raise exception
+ @state_prev = state_current
+ @state_current = state.to_sym
+ end
+
private
def set_state(facade)
@@ -68,9 +68,7 @@ def initialize
end
state :civil do
- on :find_job, switch_to: :manager do |ev, cv|
- ev.restrict unless cv
- end
+ on :find_job, call: :employment, switch_to: :manager
end
state :manager do
@@ -89,4 +87,8 @@ def enough?(num)
def hard_to?(learn=false)
!!learn
end
+
+ def employment(ev, cv)
+ ev.restrict unless cv
+ end
end
@@ -59,6 +59,13 @@
person.state_manager?.should be_true
person.state_civil?.should be_false
end
+
+ it "should force state if needed" do
+ crashed_car = Car.new
+
+ crashed_car.force_state(:damaged)
+ crashed_car.state.should eq(:damaged)
+ end
end
describe "Multiple states" do

0 comments on commit 018ec64

Please sign in to comment.