strict_machine 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 40ddaf714e2bcffb68253ea0ff08ad7d2cc8c934
4
- data.tar.gz: 0e52b5f8b5ae003d24d16ff52c17e737aa7660e2
3
+ metadata.gz: cd8176055fcd833b0c5e292f89d251ef32067687
4
+ data.tar.gz: 8c5526e2c67353ce0bb71d52713584219a901d1c
5
5
  SHA512:
6
- metadata.gz: 23b6365e23c11af49c22639e9a695267301c4097e8876c9a3f040e0c7a5d8d012082d16a4c940431b06e78f33e0cd6df6d85995b534504588d955518df21a25d
7
- data.tar.gz: d1577855922f10beeededcd1127a17176e1472be72afa1caf1cdc07ab3a76f2557834a8b92f55323a5504e921f6d94d7f43490978145103a9fa9e85129943a3c
6
+ metadata.gz: e364a995db6ffbde2984893fdc5f2e0b7d2dccf67b85514904aba17a5e66b86db90d958eb1e8e475e85b63589f70ecdcd7cb7cba6f800fb930073c0e7b043257
7
+ data.tar.gz: 0c2f0375d4df894541648ca6ddd7e2ebc7a14ca90edf6ed867c91a63b8effe15d914aece8e9496d6f9c964b10541bd9fb3853e145d717655272516eabfcb29ff
data/NOTES.md ADDED
@@ -0,0 +1,10 @@
1
+ Object fingerprint
2
+ ------------------
3
+
4
+ - `#state`
5
+ - `#trigger(*transitions)`
6
+ - `#state_attr`
7
+
8
+ TODO:
9
+
10
+ - `#can_trigger?(transition)`
@@ -1,7 +1,7 @@
1
1
  require_relative "ext/object"
2
2
 
3
3
  module StrictMachine
4
- VERSION = "0.1.4".freeze
4
+ VERSION = "0.2.0".freeze
5
5
 
6
6
  class << self; attr_accessor :list; end
7
7
  end
@@ -11,4 +11,5 @@ class InvalidTransitionError < StandardError; end
11
11
  class TransitionNotFoundError < StandardError; end
12
12
  class GuardedTransitionError < StandardError; end
13
13
 
14
+ require_relative "strict_machine/mount_state_machine"
14
15
  require_relative "strict_machine/base"
@@ -1,93 +1,20 @@
1
- require_relative "mount_state_machine"
2
1
  require_relative "definition_context"
3
2
 
4
3
  module StrictMachine
5
4
  class Base
6
- attr_accessor :mounted_on, :state_attr
5
+ include MountStateMachine::InstanceMethods
7
6
 
8
- def self.strict_machine(&block)
7
+ def self.strict_machine(state_attr = "state", &block)
9
8
  dc = DefinitionContext.new
10
9
  dc.instance_eval(&block)
11
10
 
11
+ stored = self
12
+
12
13
  metaclass.instance_eval do
13
14
  define_method(:definition) { dc }
15
+ define_method(:strict_machine_class) { stored }
16
+ define_method(:strict_machine_attr) { "@#{state_attr}".to_sym }
14
17
  end
15
18
  end
16
-
17
- attr_writer :state_attr
18
-
19
- def self.states
20
- definition.states
21
- end
22
-
23
- def boot!
24
- @state_attr = :status if @state_attr.nil?
25
-
26
- change_state(self.class.states.first.name)
27
- end
28
-
29
- def current_state
30
- instance_variable_get "@#{@state_attr}"
31
- end
32
-
33
- def definition
34
- self.class.definition
35
- end
36
-
37
- def transition?(meth, state)
38
- definition.transition?(meth, state)
39
- end
40
-
41
- def trigger_transition(trigger, stored = self)
42
- dt = Time.now
43
-
44
- is_bang = !trigger.to_s.index("!").nil?
45
- transition = from_state.get_transition(trigger, is_bang)
46
-
47
- if transition.guarded? && !is_bang
48
- raise GuardedTransitionError unless stored.public_send(
49
- transition.guard
50
- )
51
- end
52
-
53
- new_state = definition.get_state_by_name(transition.to)
54
- new_state.on_entry.each do |proc|
55
- stored.instance_exec(current_state, trigger.to_sym, &proc)
56
- end
57
-
58
- duration = Time.now - dt
59
-
60
- definition.transitions.each do |proc|
61
- stored.instance_exec(
62
- current_state, new_state.name, trigger.to_sym, duration, &proc
63
- )
64
- end
65
-
66
- change_state(new_state.name)
67
- end
68
-
69
- ###
70
-
71
- def respond_to?(meth, _include_private = false)
72
- transition?(meth, current_state)
73
- end
74
-
75
- def method_missing(meth, *_args)
76
- if transition?(meth, current_state)
77
- trigger_transition(meth, mounted_on || self)
78
- else
79
- raise TransitionNotFoundError, meth
80
- end
81
- end
82
-
83
- private
84
-
85
- def from_state
86
- definition.get_state_by_name(current_state)
87
- end
88
-
89
- def change_state(new_state, _is_initial = false)
90
- instance_variable_set "@#{@state_attr}".to_sym, new_state
91
- end
92
19
  end
93
20
  end
@@ -16,11 +16,13 @@ module StrictMachine
16
16
  )
17
17
  end
18
18
 
19
- def get_transition(name, is_bang)
20
- name = name [0..-2] if is_bang
19
+ def get_transition(name)
20
+ name = name.to_s
21
+ is_bang = name.end_with?("!")
22
+ name = name[0..-2] if is_bang
21
23
 
22
24
  @transition_definitions.each do |this_transition|
23
- return this_transition if this_transition.name == name.to_sym
25
+ return is_bang, this_transition if this_transition.name == name.to_sym
24
26
  end
25
27
 
26
28
  raise TransitionNotFoundError, name
@@ -3,28 +3,12 @@ require_relative "definition/state"
3
3
  module StrictMachine
4
4
  class DefinitionContext
5
5
  attr_reader :states, :transitions
6
- attr_accessor :mounted_on
7
6
 
8
7
  def initialize
9
8
  @states = []
10
9
  @transitions = []
11
10
  end
12
11
 
13
- def transition?(name, state)
14
- is_bang = (name[-1] == "!")
15
- name = is_bang ? name[0..-2] : name
16
-
17
- @states.each do |this_state|
18
- next unless this_state.name.to_sym == state.to_sym
19
-
20
- this_state.transition_definitions.each do |this_transition|
21
- return true if this_transition.name == name.to_sym
22
- end
23
- end
24
-
25
- false
26
- end
27
-
28
12
  def get_state_by_name(name)
29
13
  @states.each do |this_state|
30
14
  return this_state if this_state.name == name
@@ -33,7 +17,11 @@ module StrictMachine
33
17
  raise StateNotFoundError, name
34
18
  end
35
19
 
36
- ###
20
+ def initial_state_name
21
+ @states.first.name
22
+ end
23
+
24
+ ### DSL
37
25
 
38
26
  def state(name, &block)
39
27
  @states << State.new(name)
@@ -1,33 +1,11 @@
1
- require_relative "mount_state_machine/initializer"
1
+ require_relative "mount_state_machine/instance_methods"
2
2
  require_relative "mount_state_machine/class_methods"
3
3
 
4
4
  module StrictMachine
5
5
  module MountStateMachine
6
6
  def self.included(base)
7
7
  base.extend ClassMethods
8
- base.public_send :prepend, Initializer
9
- end
10
-
11
- def current_state
12
- @state_machine.current_state
13
- end
14
-
15
- def state_attr
16
- @state_machine.state_attr
17
- end
18
-
19
- ###
20
-
21
- def method_missing(meth, *args, &block)
22
- if @state_machine.transition?(meth, current_state)
23
- @state_machine.trigger_transition(meth, self)
24
- else
25
- super
26
- end
27
- end
28
-
29
- def respond_to?(meth, _include_private = false)
30
- @state_machine && @state_machine.transition?(meth, current_state)
8
+ base.include InstanceMethods
31
9
  end
32
10
  end
33
11
  end
@@ -2,9 +2,11 @@ module StrictMachine
2
2
  module MountStateMachine
3
3
  module ClassMethods
4
4
  def mount_state_machine(klass, options = {})
5
+ state_attr = options.fetch(:state, :state)
6
+
5
7
  metaclass.instance_eval do
6
8
  define_method(:strict_machine_class) { klass }
7
- define_method(:strict_machine_options) { options }
9
+ define_method(:strict_machine_attr) { "@#{state_attr}".to_sym }
8
10
  end
9
11
  end
10
12
  end
@@ -0,0 +1,74 @@
1
+ module StrictMachine
2
+ module MountStateMachine
3
+ module InstanceMethods
4
+ def trigger(*transitions)
5
+ transitions.map {|t| change_state(t, state) }
6
+
7
+ true
8
+ end
9
+
10
+ def state
11
+ write_initial_state if current_state_attr_value.nil?
12
+
13
+ current_state_attr_value
14
+ end
15
+
16
+ def state_attr
17
+ self.class.strict_machine_attr.to_s.gsub("@",'')
18
+ end
19
+
20
+ def states
21
+ definition.states
22
+ end
23
+
24
+ private
25
+
26
+ def current_state_attr_value
27
+ instance_variable_get state_machine_attr_name
28
+ end
29
+
30
+ def write_initial_state
31
+ write_state(definition.initial_state_name)
32
+ end
33
+
34
+ def write_state(value)
35
+ instance_variable_set state_machine_attr_name, value
36
+ end
37
+
38
+ def state_machine_attr_name
39
+ self.class.strict_machine_attr
40
+ end
41
+
42
+ def definition
43
+ self.class.strict_machine_class.definition
44
+ end
45
+
46
+ def change_state(trigger, current_state_name)
47
+ dt = Time.now
48
+ current_state = definition.get_state_by_name(current_state_name)
49
+ is_bang, transition = current_state.get_transition(trigger)
50
+
51
+ if transition.guarded? && !is_bang
52
+ raise GuardedTransitionError unless self.public_send(
53
+ transition.guard
54
+ )
55
+ end
56
+
57
+ new_state = definition.get_state_by_name(transition.to)
58
+ new_state.on_entry.each do |proc|
59
+ self.instance_exec(current_state_name, trigger.to_sym, &proc)
60
+ end
61
+
62
+ duration = Time.now - dt
63
+
64
+ definition.transitions.each do |proc|
65
+ self.instance_exec(
66
+ current_state_name, new_state.name, trigger.to_sym, duration, &proc
67
+ )
68
+ end
69
+
70
+ write_state(new_state.name)
71
+ end
72
+ end
73
+ end
74
+ end
data/spec/dsl_spec.rb CHANGED
@@ -10,8 +10,9 @@ describe DefinitionContext do
10
10
  end
11
11
  end
12
12
 
13
- expect(klass.states.size).to eq(1)
14
- expect(klass.states.first.name).to eq(:initial)
13
+ machine = klass.new
14
+ expect(machine.states.size).to eq(1)
15
+ expect(machine.states.first.name).to eq(:initial)
15
16
  end
16
17
  end
17
18
 
@@ -27,10 +28,9 @@ describe DefinitionContext do
27
28
  end
28
29
 
29
30
  machine = klass.new
30
- machine.boot!
31
- expect(machine.current_state).to eq(:initial)
32
- machine.hop
33
- expect(machine.current_state).to eq(:done)
31
+ expect(machine.state).to eq(:initial)
32
+ machine.trigger(:hop)
33
+ expect(machine.state).to eq(:done)
34
34
  end
35
35
 
36
36
  it "honors guard statements" do
@@ -48,9 +48,8 @@ describe DefinitionContext do
48
48
  end
49
49
 
50
50
  machine = klass.new
51
- machine.boot!
52
- expect(machine.current_state).to eq(:initial)
53
- expect { machine.hop }.to raise_error(GuardedTransitionError)
51
+ expect(machine.state).to eq(:initial)
52
+ expect { machine.trigger(:hop) }.to raise_error(GuardedTransitionError)
54
53
  end
55
54
 
56
55
  it "bypasses guard statements with a bang" do
@@ -68,9 +67,8 @@ describe DefinitionContext do
68
67
  end
69
68
 
70
69
  machine = klass.new
71
- machine.boot!
72
- machine.hop!
73
- expect(machine.current_state).to eql(:done)
70
+ machine.trigger(:hop!)
71
+ expect(machine.state).to eql(:done)
74
72
  end
75
73
 
76
74
  it "raises error on invalid state transitions" do
@@ -83,9 +81,8 @@ describe DefinitionContext do
83
81
  end
84
82
 
85
83
  machine = klass.new
86
- machine.boot!
87
- expect { machine.hop }.to raise_error(StateNotFoundError)
88
- expect { machine.zing }.to raise_error(TransitionNotFoundError)
84
+ expect { machine.trigger(:hop) }.to raise_error(StateNotFoundError)
85
+ expect { machine.trigger(:zing) }.to raise_error(TransitionNotFoundError)
89
86
  end
90
87
  end
91
88
 
@@ -115,8 +112,7 @@ describe DefinitionContext do
115
112
  expect_any_instance_of(klass).to receive(:log).with(:initial, :hop)
116
113
  expect_any_instance_of(klass).to receive(:log2).with(:initial, :hop)
117
114
 
118
- machine.boot!
119
- machine.hop
115
+ machine.trigger(:hop)
120
116
  end
121
117
  end
122
118
 
@@ -147,9 +143,18 @@ describe DefinitionContext do
147
143
  :middle, :final, :hop, any_args
148
144
  )
149
145
 
150
- machine.boot!
151
- machine.hop
152
- machine.hop
146
+ machine.trigger(:hop, :hop)
147
+ end
148
+ end
149
+
150
+ it "sets state to the given status option name" do
151
+ klass = Class.new(StrictMachine::Base) do
152
+ strict_machine("meh") do
153
+ state :initial
154
+ end
153
155
  end
156
+
157
+ machine = klass.new
158
+ expect(machine.state_attr).to eq("meh")
154
159
  end
155
160
  end
data/spec/dummy/dummy.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require_relative "dummy_state_machine"
2
+
1
3
  class Dummy
2
4
  include StrictMachine::MountStateMachine
3
5
 
@@ -2,46 +2,43 @@ require "spec_helper"
2
2
 
3
3
  describe "mounted StrictMachine" do
4
4
  context "state shifting" do
5
- require_relative "dummy/dummy_state_machine"
6
5
  require_relative "dummy/dummy"
7
6
 
8
7
  let!(:dummy) { Dummy.new(2) }
9
8
 
10
- it "preserves passed arguments to initializer" do
11
- expect(dummy.a).to eq(2)
9
+ it "enumerates states" do
10
+ expect(dummy.states.size).to eq(5)
11
+ expect(dummy.states.first.name).to eq(:new)
12
12
  end
13
13
 
14
14
  it "has an initial state" do
15
- expect(dummy.current_state).to eq(:new)
15
+ expect(dummy.state).to eq(:new)
16
16
  end
17
17
 
18
18
  it "shifts state" do
19
- dummy.submit
19
+ dummy.trigger(:submit)
20
20
 
21
- expect(dummy.current_state).to eq(:awaiting_review)
21
+ expect(dummy.state).to eq(:awaiting_review)
22
22
  end
23
23
 
24
24
  it "honors guard statements" do
25
- dummy.submit
26
- dummy.review
25
+ dummy.trigger(:submit, :review)
27
26
 
28
- expect { dummy.reject }.to raise_error(GuardedTransitionError)
27
+ expect { dummy.trigger(:reject) }.to raise_error(GuardedTransitionError)
29
28
 
30
- expect(dummy.current_state).to eq(:under_review)
29
+ expect(dummy.state).to eq(:under_review)
31
30
  end
32
31
 
33
32
  it "bypasses guard statements with bangs" do
34
- dummy.submit
35
- dummy.review
36
- dummy.reject!
33
+ dummy.trigger(:submit, :review, :reject!)
37
34
 
38
- expect(dummy.current_state).to eq(:rejected)
35
+ expect(dummy.state).to eq(:rejected)
39
36
  end
40
37
 
41
38
  it "calls on_transition blocks" do
42
39
  expect(dummy).to receive(:log)
43
40
 
44
- dummy.submit
41
+ dummy.trigger(:submit)
45
42
  end
46
43
 
47
44
  it "sets state to the given status option name" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strict_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Antunes
@@ -81,6 +81,7 @@ files:
81
81
  - ".travis.yml"
82
82
  - Gemfile
83
83
  - LICENSE.txt
84
+ - NOTES.md
84
85
  - README.md
85
86
  - Rakefile
86
87
  - bin/console
@@ -93,7 +94,7 @@ files:
93
94
  - lib/strict_machine/definition_context.rb
94
95
  - lib/strict_machine/mount_state_machine.rb
95
96
  - lib/strict_machine/mount_state_machine/class_methods.rb
96
- - lib/strict_machine/mount_state_machine/initializer.rb
97
+ - lib/strict_machine/mount_state_machine/instance_methods.rb
97
98
  - spec/dsl_spec.rb
98
99
  - spec/dummy/dummy.rb
99
100
  - spec/dummy/dummy_state_machine.rb
@@ -126,7 +127,7 @@ specification_version: 4
126
127
  summary: State machine functionality for Ruby classes
127
128
  test_files:
128
129
  - spec/dsl_spec.rb
129
- - spec/dummy/dummy.rb
130
- - spec/dummy/dummy_state_machine.rb
131
130
  - spec/mountable_machine_spec.rb
132
131
  - spec/spec_helper.rb
132
+ - spec/dummy/dummy.rb
133
+ - spec/dummy/dummy_state_machine.rb
@@ -1,20 +0,0 @@
1
- module StrictMachine
2
- module MountStateMachine
3
- module Initializer
4
- def initialize(*args)
5
- if self.class.respond_to?(:strict_machine_class)
6
- @state_machine = self.class.strict_machine_class.new
7
- @state_machine.mounted_on = self
8
-
9
- options = self.class.strict_machine_options
10
- state_attr = options.fetch(:state, :status)
11
- @state_machine.state_attr = state_attr
12
-
13
- @state_machine.boot!
14
- end
15
-
16
- super
17
- end
18
- end
19
- end
20
- end