decide.rb 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +17 -13
- data/examples/decide.md +23 -0
- data/examples/evolve.md +23 -0
- data/examples/state.md +65 -0
- data/lib/decider/version.rb +1 -1
- data/lib/decider.rb +56 -15
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da945c90e06dab8eb7c391135319bf4f9e126fd2e1904ac4481aabfc4df8943a
|
4
|
+
data.tar.gz: 9e70f93d1ee072b555f7a44b8babc2c0fb0bef50df27949d3bd45654b8c05ed8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67a5f37bf266ce6ca5ca35820cc384fa502d33ca0dae794527d160dc7b247eb7e376c96f6dedfe204c348c25436d64db6c934d9fc407daf8e8421c38e23758bd
|
7
|
+
data.tar.gz: aea4f6f4de5a4509ce7b908fb04c59e6546f96c01502b762b564e5c8f44b7b235f380bf98631cf2f906d08eeaf210cde02564ce6060b5fbf9677fcbdd24b675f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# 0.3.0
|
2
|
+
|
3
|
+
* Rename `Decider.state` to `Decider.initial_state`
|
4
|
+
* Allow to use anything as state
|
5
|
+
* Use tuple-like array for composition
|
6
|
+
* Do not raise error when deciding unknown command
|
7
|
+
* Do not raise error when evolving unknown event
|
8
|
+
|
1
9
|
# 0.2.0
|
2
10
|
|
3
11
|
* Added `terminal?` function
|
data/README.md
CHANGED
@@ -21,6 +21,16 @@ gem "decide.rb"
|
|
21
21
|
```ruby
|
22
22
|
require "decider"
|
23
23
|
|
24
|
+
State = Data.define(:value) do
|
25
|
+
def max?
|
26
|
+
value >= MAX
|
27
|
+
end
|
28
|
+
|
29
|
+
def min?
|
30
|
+
value <= MIN
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
24
34
|
module Commands
|
25
35
|
Increase = Data.define
|
26
36
|
Decrease = Data.define
|
@@ -36,16 +46,7 @@ MAX = 100
|
|
36
46
|
|
37
47
|
ValueDecider = Decider.define do
|
38
48
|
# define intial state
|
39
|
-
|
40
|
-
# you can define custom methods on state
|
41
|
-
def max?
|
42
|
-
value >= MAX
|
43
|
-
end
|
44
|
-
|
45
|
-
def min?
|
46
|
-
value <= MIN
|
47
|
-
end
|
48
|
-
end
|
49
|
+
initial_state State.new(value: 0)
|
49
50
|
|
50
51
|
# decide command with state
|
51
52
|
decide Commands::Increase do |command, state|
|
@@ -89,8 +90,11 @@ new_state = events.reduce(state) { |state, event| ValueDecider.evolve(state, eve
|
|
89
90
|
You can also compose deciders:
|
90
91
|
|
91
92
|
```ruby
|
93
|
+
Left = Data.define(:value)
|
94
|
+
Right = Data.define(:value)
|
95
|
+
|
92
96
|
left = Decider.define do
|
93
|
-
|
97
|
+
initial_state Left.new(value: 0)
|
94
98
|
|
95
99
|
decide Commands::LeftCommand do |command, state|
|
96
100
|
[Events::LeftEvent.new(value: command.value)]
|
@@ -106,7 +110,7 @@ left = Decider.define do
|
|
106
110
|
end
|
107
111
|
|
108
112
|
right = Decider.define do
|
109
|
-
|
113
|
+
initial_state Right.new(value: 0)
|
110
114
|
|
111
115
|
decide Commands::RightCommand do |command, state|
|
112
116
|
[Events::RightEvent.new(value: command.value)]
|
@@ -124,7 +128,7 @@ end
|
|
124
128
|
Composition = Decider.compose(left, right)
|
125
129
|
|
126
130
|
state = Composition.initial_state
|
127
|
-
#> #<data left=#<data value=0>, right=#<data value=0>>
|
131
|
+
#> #<data left=#<data Left value=0>, right=#<data Right value=0>>
|
128
132
|
|
129
133
|
events = Composition.decide(Commands::LeftCommand.new(value: 1), state)
|
130
134
|
#> [#<data value=1>]
|
data/examples/decide.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Decide
|
2
|
+
|
3
|
+
## Handling unknown commands
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
decider = Decider.define do
|
7
|
+
initial_state :initial
|
8
|
+
end
|
9
|
+
```
|
10
|
+
|
11
|
+
Return empty list of events (nothing changed) by default:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
decider.decide :unknown, decider.initial_state
|
15
|
+
#> []
|
16
|
+
```
|
17
|
+
|
18
|
+
Raise error when using `decide!`:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
decider.decide! :unknown, decider.initial_state
|
22
|
+
#> raise ArgumentError, Unknown command
|
23
|
+
```
|
data/examples/evolve.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Evolve
|
2
|
+
|
3
|
+
## Handling unknown events
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
decider = Decider.define do
|
7
|
+
initial_state :initial
|
8
|
+
end
|
9
|
+
```
|
10
|
+
|
11
|
+
Return state (nothing changed) by default:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
decider.evolve decider.initial_state, :unknown
|
15
|
+
#> :initial
|
16
|
+
```
|
17
|
+
|
18
|
+
Raise error when using `evolve!`:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
decider.evolve! decider.initial_state, :unknown
|
22
|
+
#> raise ArgumentError, Unknown event
|
23
|
+
```
|
data/examples/state.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# State examples
|
2
|
+
|
3
|
+
## Data
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
State = Data.define(:value)
|
7
|
+
|
8
|
+
decider = Decider.define do
|
9
|
+
initial_state State.new(value: 0)
|
10
|
+
|
11
|
+
decide Commands::Command do |command, state|
|
12
|
+
[Events::Event.new(value: command.value)]
|
13
|
+
end
|
14
|
+
|
15
|
+
evolve Events::Event do |state, event|
|
16
|
+
state.with(value: state.value + 1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
## Primitive
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
decider = Decider.define do
|
25
|
+
initial_state :turned_off
|
26
|
+
|
27
|
+
decide Commands::TurnOn do |_command, state|
|
28
|
+
case state
|
29
|
+
in :turned_off
|
30
|
+
[Events::TurnedOn.new]
|
31
|
+
else
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
decide Commands::TurnOff do |_command, state|
|
37
|
+
case state
|
38
|
+
in :turned_on
|
39
|
+
[Events::TurnedOn.new]
|
40
|
+
else
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
evolve Events::TurnedOn do |_state, _event|
|
46
|
+
:turned_off
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
## List
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
decider = Decider.define do
|
55
|
+
initial_state []
|
56
|
+
|
57
|
+
decide Commands::Command do |command, _state|
|
58
|
+
[Events::Event.new(value: command.value)]
|
59
|
+
end
|
60
|
+
|
61
|
+
evolve Events::Event do |state, event|
|
62
|
+
state = state + [event]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
data/lib/decider/version.rb
CHANGED
data/lib/decider.rb
CHANGED
@@ -4,10 +4,34 @@ module Decider
|
|
4
4
|
StateAlreadyDefined = Class.new(StandardError)
|
5
5
|
StateNotDefined = Class.new(StandardError)
|
6
6
|
|
7
|
+
class Composition < Array
|
8
|
+
BLANK = Object.new
|
9
|
+
private_constant :BLANK
|
10
|
+
|
11
|
+
def initialize(left, right)
|
12
|
+
super([left, right]).freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def with(left: BLANK, right: BLANK)
|
16
|
+
Composition.new(
|
17
|
+
(left == BLANK) ? self.left : left,
|
18
|
+
(right == BLANK) ? self.right : right
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def left
|
23
|
+
self[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
def right
|
27
|
+
self[1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
7
31
|
class Module < ::Module
|
8
|
-
def initialize(
|
32
|
+
def initialize(initial_state:, deciders:, evolvers:, terminal:)
|
9
33
|
define_method(:initial_state) do
|
10
|
-
|
34
|
+
initial_state
|
11
35
|
end
|
12
36
|
|
13
37
|
define_method(:commands) do
|
@@ -18,7 +42,7 @@ module Decider
|
|
18
42
|
evolvers.keys
|
19
43
|
end
|
20
44
|
|
21
|
-
define_method(:decide) do |command, state|
|
45
|
+
define_method(:decide!) do |command, state|
|
22
46
|
handler = deciders.fetch(command.class) {
|
23
47
|
raise ArgumentError, "Unknown command: #{command.class}"
|
24
48
|
}
|
@@ -26,7 +50,15 @@ module Decider
|
|
26
50
|
handler.call(command, state)
|
27
51
|
end
|
28
52
|
|
29
|
-
define_method(:
|
53
|
+
define_method(:decide) do |command, state|
|
54
|
+
handler = deciders.fetch(command.class) {
|
55
|
+
return []
|
56
|
+
}
|
57
|
+
|
58
|
+
handler.call(command, state)
|
59
|
+
end
|
60
|
+
|
61
|
+
define_method(:evolve!) do |state, event|
|
30
62
|
handler = evolvers.fetch(event.class) {
|
31
63
|
raise ArgumentError, "Unknown event: #{event.class}"
|
32
64
|
}
|
@@ -34,6 +66,14 @@ module Decider
|
|
34
66
|
handler.call(state, event)
|
35
67
|
end
|
36
68
|
|
69
|
+
define_method(:evolve) do |state, event|
|
70
|
+
handler = evolvers.fetch(event.class) {
|
71
|
+
return state
|
72
|
+
}
|
73
|
+
|
74
|
+
handler.call(state, event)
|
75
|
+
end
|
76
|
+
|
37
77
|
define_method(:terminal?) do |state|
|
38
78
|
terminal.call(state)
|
39
79
|
end
|
@@ -46,7 +86,7 @@ module Decider
|
|
46
86
|
attr_reader :module
|
47
87
|
|
48
88
|
def initialize
|
49
|
-
@
|
89
|
+
@initial_state = DEFAULT
|
50
90
|
@deciders = {}
|
51
91
|
@evolvers = {}
|
52
92
|
@terminal = ->(_state) { false }
|
@@ -55,29 +95,30 @@ module Decider
|
|
55
95
|
def build(&block)
|
56
96
|
instance_exec(&block) if block_given?
|
57
97
|
|
58
|
-
raise StateNotDefined if @
|
98
|
+
raise StateNotDefined if @initial_state == DEFAULT
|
99
|
+
|
100
|
+
decider = Object.new
|
59
101
|
|
60
102
|
@module = Module.new(
|
61
|
-
|
103
|
+
initial_state: @initial_state,
|
62
104
|
deciders: deciders,
|
63
105
|
evolvers: evolvers,
|
64
106
|
terminal: terminal
|
65
107
|
)
|
66
108
|
|
67
|
-
|
109
|
+
decider.extend(@module)
|
68
110
|
|
69
|
-
|
111
|
+
decider
|
70
112
|
end
|
71
113
|
|
72
114
|
private
|
73
115
|
|
74
|
-
attr_reader :
|
116
|
+
attr_reader :deciders, :evolvers, :terminal
|
75
117
|
|
76
|
-
def state
|
77
|
-
raise StateAlreadyDefined if @
|
118
|
+
def initial_state(state)
|
119
|
+
raise StateAlreadyDefined if @initial_state != DEFAULT
|
78
120
|
|
79
|
-
@
|
80
|
-
@initial_state_args = kwargs
|
121
|
+
@initial_state = state
|
81
122
|
end
|
82
123
|
|
83
124
|
def decide(command, &block)
|
@@ -101,7 +142,7 @@ module Decider
|
|
101
142
|
|
102
143
|
def self.compose(left, right)
|
103
144
|
define do
|
104
|
-
|
145
|
+
initial_state Composition.new(left.initial_state, right.initial_state)
|
105
146
|
|
106
147
|
left.commands.each do |klass|
|
107
148
|
decide klass do |command, state|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decide.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Dudulski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Functional Event Sourcing Decider in Ruby
|
14
14
|
email:
|
@@ -23,6 +23,9 @@ files:
|
|
23
23
|
- README.md
|
24
24
|
- Rakefile
|
25
25
|
- Steepfile
|
26
|
+
- examples/decide.md
|
27
|
+
- examples/evolve.md
|
28
|
+
- examples/state.md
|
26
29
|
- lib/decider.rb
|
27
30
|
- lib/decider/version.rb
|
28
31
|
- sig/decider.rbs
|