strict_machine 0.1.4 → 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.
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