switcher 0.1.0 → 0.2.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.
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - rbx-19mode
5
+ - jruby-19mode
6
+
7
+ branches:
8
+ only:
9
+ - master
data/Gemfile CHANGED
@@ -7,5 +7,6 @@ gem "guard-rspec"
7
7
 
8
8
  group "activerecord" do
9
9
  gem "activerecord", "~> 3.0.0"
10
- gem "sqlite3"
10
+ gem 'sqlite3-ruby', :platforms => :ruby
11
+ gem 'activerecord-jdbcsqlite3-adapter', :platforms => :jruby
11
12
  end
data/README.md CHANGED
@@ -1,11 +1,113 @@
1
- Not for production use
1
+ [![Build Status](https://secure.travis-ci.org/Ptico/switcher.png)](http://travis-ci.org/Ptico/switcher)
2
2
 
3
- Docs: See specs
3
+ Switcher is lightweight event-driven state machine.
4
+
5
+ ## Basic usage:
6
+
7
+ ```ruby
8
+ class Package
9
+ include Switcher::Object
10
+
11
+ def initialize(weight)
12
+ @sent_weight = weight.to_f
13
+ @tracking_number = nil
14
+ end
15
+
16
+ switcher :delivery do
17
+ state :requested do
18
+ on :send, switch_to: :sent do |ev, num|
19
+ @tracking_number = num
20
+ end
21
+ end
22
+
23
+ state :sent do
24
+ before :receive, call: :check_number
25
+
26
+ on :receive do |ev, number, weight|
27
+ if weight < (@weight - 0.3)
28
+ ev.switch_to :stolen
29
+ else
30
+ ev.switch_to :received
31
+ end
32
+ end
33
+
34
+ after :receive do |ev|
35
+ unpack if delivery_received?
36
+ end
37
+
38
+ on :miss, switch_to: :missed
39
+ end
40
+
41
+ state :received
42
+ state :stolen
43
+ state :missed
44
+ end
45
+
46
+ def check_number(ev, number, weight)
47
+ ev.stop if number.to_s != @tracking_number
48
+ end
49
+
50
+ def unpack
51
+ puts "TADA!"
52
+ end
53
+ end
54
+
55
+ package = Package.new(4.2)
56
+
57
+ package.delivery # => :requested
58
+ package.can_send? # => true
59
+ package.send! "AB123456CD"
60
+ package.delivery # => :sent
61
+ package.receive!(3.5, "CD654321AB")
62
+ package.delivery # => :sent
63
+ package.receive!(4.1, "AB123456CD")
64
+ # > TADA!
65
+
66
+ package.delivery # => :received
67
+ package.delivery_prev # => :sent
68
+ package.delivery_received? # => true
69
+ package.can_miss? # => false
70
+ ```
71
+
72
+ ## Usage with Active Record:
73
+
74
+ ```ruby
75
+ class User < ActiveRecord::Base
76
+ include Switcher::ActiveRecord
77
+
78
+ switcher :membership do
79
+ state :guest do
80
+ on :approve, switch_to: :member
81
+ end
82
+ state :member do
83
+ on :ban, switch_to: :banned do |ev, reason|
84
+ ev.stop unless reason
85
+ ban_reason = reason
86
+ end
87
+ end
88
+ state :banned do
89
+ on :unban, switch_to: :member
90
+ after :unban do
91
+ ban_reason = nil
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ user = User.find(5)
98
+ user.ban! "Stupid bastard"
99
+ user.membership # => :banned
100
+ user.save
101
+ ```
102
+
103
+ ## More examples:
104
+
105
+ * [Wiki](https://github.com/Ptico/switcher/wiki)
106
+ * [Specs](https://github.com/Ptico/switcher/tree/master/spec)
4
107
 
5
108
  ## TODO:
6
109
 
7
110
  * 1.8 compat
8
- * Write README
9
111
  * Refactoring
10
112
  * Events without state
11
113
  * Ability to define validations, associations etc. in state (ability to call static methods from object)
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => :spec
5
+
6
+ desc "Test switcher"
7
+ RSpec::Core::RakeTask.new('spec')
@@ -13,6 +13,9 @@ module Switcher
13
13
  define_method(:"#{spec_name}_prev") { self.instance_variable_get(:"@#{spec_name}_statement").state_prev }
14
14
 
15
15
  define_method(:"#{spec_name}") { self.instance_variable_get(:"@#{spec_name}_statement").state_current }
16
+ define_method(:"#{spec_name}=") { nil } # FIXME - raise exception
17
+
18
+ define_method(:"#{spec_name}_force") { |state| self.instance_variable_get(:"@#{spec_name}_statement").force_state(state.to_sym) }
16
19
 
17
20
  events = []
18
21
 
@@ -62,6 +65,8 @@ module Switcher
62
65
  end
63
66
 
64
67
  def switch_from(orig, event, *args)
68
+ return false unless (self.respond_to?(:"can_#{event}?") and self.send(:"can_#{event}?"))
69
+
65
70
  self.class.class_variable_get(:@@__specs__).each do |spc|
66
71
  spc_name = spc.name
67
72
 
@@ -72,6 +77,8 @@ module Switcher
72
77
  write_attribute("#{spc_name}_prev", self.send(:"#{spc_name}_prev"))
73
78
  end
74
79
  end
80
+
81
+ true
75
82
  end
76
83
 
77
84
  end
@@ -20,6 +20,9 @@ module Switcher
20
20
  define_method(:"#{spec_name}_prev") { self.instance_variable_get(:"@#{spec_name}_statement").state_prev }
21
21
 
22
22
  define_method(:"#{spec_name}") { self.instance_variable_get(:"@#{spec_name}_statement").state_current }
23
+ define_method(:"#{spec_name}=") { nil }
24
+
25
+ define_method(:"#{spec_name}_force") { |state| self.instance_variable_get(:"@#{spec_name}_statement").force_state(state.to_sym) }
23
26
 
24
27
  events = []
25
28
 
@@ -57,9 +60,13 @@ module Switcher
57
60
  end
58
61
 
59
62
  def switch_from(orig, event, *args)
63
+ return false unless (self.respond_to?(:"can_#{event}?") and self.send(:"can_#{event}?"))
64
+
60
65
  self.class.class_variable_get(:@@__specs__).each do |spc|
61
66
  self.instance_variable_get(:"@#{spc.name}_statement").publish(event, orig, args)
62
67
  end
68
+
69
+ true
63
70
  end
64
71
  end
65
72
  end
@@ -28,6 +28,9 @@ module Switcher
28
28
 
29
29
  def trigger(event, facade, instance, args)
30
30
  if ev = @events[event]
31
+ if ev[:options][:call]
32
+ instance.instance_exec(facade, *args) { |facad, *arguments| self.send(ev[:options][:call], facad, *arguments) }
33
+ end
31
34
  instance.instance_exec(facade, *args, &ev[:callback]) if ev[:callback].respond_to?(:call)
32
35
  if !facade.stopped && ev[:options][:allow_switch] && switch_to = ev[:options][:switch_to]
33
36
  facade.switch_to(switch_to)
@@ -51,6 +51,12 @@ module Switcher
51
51
  end
52
52
  end
53
53
 
54
+ def force_state(state)
55
+ return unless @spec.states_list.include?(state.to_sym) # FIXME - raise exception
56
+ @state_prev = state_current
57
+ @state_current = state.to_sym
58
+ end
59
+
54
60
  private
55
61
 
56
62
  def set_state(facade)
@@ -1,3 +1,3 @@
1
1
  module Switcher
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -68,9 +68,7 @@ class Human
68
68
  end
69
69
 
70
70
  state :civil do
71
- on :find_job, switch_to: :manager do |ev, cv|
72
- ev.restrict unless cv
73
- end
71
+ on :find_job, call: :employment, switch_to: :manager
74
72
  end
75
73
 
76
74
  state :manager do
@@ -89,4 +87,8 @@ class Human
89
87
  def hard_to?(learn=false)
90
88
  !!learn
91
89
  end
90
+
91
+ def employment(ev, cv)
92
+ ev.restrict unless cv
93
+ end
92
94
  end
@@ -59,6 +59,13 @@ describe Switcher do
59
59
  person.state_manager?.should be_true
60
60
  person.state_civil?.should be_false
61
61
  end
62
+
63
+ it "should force state if needed" do
64
+ crashed_car = Car.new
65
+
66
+ crashed_car.state_force(:damaged)
67
+ crashed_car.state.should eq(:damaged)
68
+ end
62
69
  end
63
70
 
64
71
  describe "Multiple states" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-04 00:00:00.000000000Z
12
+ date: 2012-03-05 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70251275731280 !ruby/object:Gem::Requirement
16
+ requirement: &70322138874120 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70251275731280
24
+ version_requirements: *70322138874120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70251275730860 !ruby/object:Gem::Requirement
27
+ requirement: &70322138873700 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70251275730860
35
+ version_requirements: *70322138873700
36
36
  description: Switcher is simple, event-driven state machine
37
37
  email:
38
38
  - andrey@aejis.eu
@@ -41,6 +41,7 @@ extensions: []
41
41
  extra_rdoc_files: []
42
42
  files:
43
43
  - .gitignore
44
+ - .travis.yml
44
45
  - Gemfile
45
46
  - Guardfile
46
47
  - README.md