decide.rb 0.2.0 → 0.4.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
  SHA256:
3
- metadata.gz: 1d042a0810111e7fcb0f9226ad109b20903a8305689df72d25a04caaa17deeff
4
- data.tar.gz: d45f4d9aa5edc832dbdf303c8b0ba2e5aa6b7293c8c00614f9f8dfc69186c5c3
3
+ metadata.gz: 73423e6911201b60ed5a3333ef636880cc93751425e95b39e522bed75d9ca4a5
4
+ data.tar.gz: 2b352d0c34587f8dfe90e819504e3d07093de738ddde24cea4e96a152f09ce0d
5
5
  SHA512:
6
- metadata.gz: bf2b770bdabf04def99cbb942d6de633b00a9be6637adec05e81ad52228022e92e0cfc8e71e2e9ac12501febcdb411386242eb341d25e4d0be8f52b62f761ff6
7
- data.tar.gz: 614461548e7859a8524cbd6e29961790443e89a10e69d49ec690448af7204afb04a71bf984e8825de3d7b57496753e836e98c0b7ff9c4475d2715838ffaa0075
6
+ metadata.gz: 8f2e2449fac23e3ae81014a6fd0cfefd5a2a888089c3c89b2072cb52592f6ba5bb659c9f2a816c6d8b13eb6082b463f42594dbffedb2add06469857b7bfe07d0
7
+ data.tar.gz: 1a310ac9f050f40ba09df6605860fc62b348e093133fd0237618726753d25f5c669f72b723b9072664862d1d80a01898dd963dbd323c97ef30c8533ed9f523e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # Unreleased
2
+
3
+ * Accept more data structures for commands and events
4
+
5
+ # 0.3.0
6
+
7
+ * Rename `Decider.state` to `Decider.initial_state`
8
+ * Allow to use anything as state
9
+ * Use tuple-like array for composition
10
+ * Do not raise error when deciding unknown command
11
+ * Do not raise error when evolving unknown event
12
+
1
13
  # 0.2.0
2
14
 
3
15
  * 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
- state value: 0 do
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
- state value: 0
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
- state value: 0
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>]
@@ -0,0 +1,81 @@
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
+ ```
24
+
25
+ ## Commands
26
+
27
+ Commands can be primitives like symbols:
28
+
29
+ ```ruby
30
+ decider = Decider.define do
31
+ initial_state :initial
32
+
33
+ decide :start do |command, state|
34
+ case state
35
+ in :initial, :stopped
36
+ [:started]
37
+ else
38
+ []
39
+ end
40
+ end
41
+
42
+ decide :stop do |command, state|
43
+ case state
44
+ in :started
45
+ [:stopped]
46
+ else
47
+ []
48
+ end
49
+ end
50
+ end
51
+ ```
52
+
53
+ Or any classes like [Dry::Struct](https://dry-rb.org/gems/dry-struct/) or [Data](https://rubyapi.org/3.3/o/data):
54
+
55
+ ```ruby
56
+ Start = Data.define(:value)
57
+ Stop = Data.define
58
+
59
+ decider = Decider.define do
60
+ initial_state :initial
61
+
62
+ decide Start do |command, state|
63
+ case state
64
+ in :initial, :stopped
65
+ [:started, command.value]
66
+ else
67
+ []
68
+ end
69
+ end
70
+
71
+ decide Stop do |command, state|
72
+ case state
73
+ in :started
74
+ [:stopped]
75
+ else
76
+ []
77
+ end
78
+ end
79
+ end
80
+ ```
81
+
@@ -0,0 +1,62 @@
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
+ ```
24
+
25
+ ## Events
26
+
27
+ Events can be primitives like symbols:
28
+
29
+ ```ruby
30
+ decider = Decider.define do
31
+ initial_state :initial
32
+
33
+ evolve :started do |state, event|
34
+ :started
35
+ end
36
+
37
+ evolve :stopped do |state, event|
38
+ :stopped
39
+ end
40
+ end
41
+ ```
42
+
43
+ Or any classes like [Dry::Struct](https://dry-rb.org/gems/dry-struct/) or [Data](https://rubyapi.org/3.3/o/data):
44
+
45
+ ```ruby
46
+ State = Data.define(:speed)
47
+ Started = Data.define(:speed)
48
+ Stopped = Data.define
49
+
50
+ decider = Decider.define do
51
+ initial_state State.new(speed: 0)
52
+
53
+ evolve Started do |state, event|
54
+ State.new(speed: event.speed)
55
+ end
56
+
57
+ evolve Stopped do |state, event|
58
+ State.new(speed: 0)
59
+ end
60
+ end
61
+ ```
62
+
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
+ ```
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decider
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
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(initial_state_args:, deciders:, evolvers:, terminal:)
32
+ def initialize(initial_state:, deciders:, evolutions:, terminal:)
9
33
  define_method(:initial_state) do
10
- new(**initial_state_args)
34
+ initial_state
11
35
  end
12
36
 
13
37
  define_method(:commands) do
@@ -15,23 +39,43 @@ module Decider
15
39
  end
16
40
 
17
41
  define_method(:events) do
18
- evolvers.keys
42
+ evolutions.keys
43
+ end
44
+
45
+ define_method(:decide!) do |command, state|
46
+ case deciders.find { |key, _| key === command }
47
+ in [_, handler]
48
+ handler.call(command, state)
49
+ else
50
+ raise ArgumentError, "Unknown command: #{command.inspect}"
51
+ end
19
52
  end
20
53
 
21
54
  define_method(:decide) do |command, state|
22
- handler = deciders.fetch(command.class) {
23
- raise ArgumentError, "Unknown command: #{command.class}"
24
- }
55
+ case deciders.find { |key, _| key === command }
56
+ in [_, handler]
57
+ handler.call(command, state)
58
+ else
59
+ []
60
+ end
61
+ end
25
62
 
26
- handler.call(command, state)
63
+ define_method(:evolve!) do |state, event|
64
+ case evolutions.find { |key, _| key === event }
65
+ in [_, handler]
66
+ handler.call(state, event)
67
+ else
68
+ raise ArgumentError, "Unknown event: #{event.inspect}"
69
+ end
27
70
  end
28
71
 
29
72
  define_method(:evolve) do |state, event|
30
- handler = evolvers.fetch(event.class) {
31
- raise ArgumentError, "Unknown event: #{event.class}"
32
- }
33
-
34
- handler.call(state, event)
73
+ case evolutions.find { |key, _| key === event }
74
+ in [_, handler]
75
+ handler.call(state, event)
76
+ else
77
+ state
78
+ end
35
79
  end
36
80
 
37
81
  define_method(:terminal?) do |state|
@@ -46,38 +90,39 @@ module Decider
46
90
  attr_reader :module
47
91
 
48
92
  def initialize
49
- @state = DEFAULT
93
+ @initial_state = DEFAULT
50
94
  @deciders = {}
51
- @evolvers = {}
95
+ @evolutions = {}
52
96
  @terminal = ->(_state) { false }
53
97
  end
54
98
 
55
99
  def build(&block)
56
100
  instance_exec(&block) if block_given?
57
101
 
58
- raise StateNotDefined if @state == DEFAULT
102
+ raise StateNotDefined if @initial_state == DEFAULT
103
+
104
+ decider = Object.new
59
105
 
60
106
  @module = Module.new(
61
- initial_state_args: initial_state_args,
107
+ initial_state: @initial_state,
62
108
  deciders: deciders,
63
- evolvers: evolvers,
109
+ evolutions: evolutions,
64
110
  terminal: terminal
65
111
  )
66
112
 
67
- @state.extend(@module)
113
+ decider.extend(@module)
68
114
 
69
- @state
115
+ decider
70
116
  end
71
117
 
72
118
  private
73
119
 
74
- attr_reader :initial_state_args, :deciders, :evolvers, :terminal
120
+ attr_reader :deciders, :evolutions, :terminal
75
121
 
76
- def state(**kwargs, &block)
77
- raise StateAlreadyDefined if @state != DEFAULT
122
+ def initial_state(state)
123
+ raise StateAlreadyDefined if @initial_state != DEFAULT
78
124
 
79
- @state = Data.define(*kwargs.keys, &block)
80
- @initial_state_args = kwargs
125
+ @initial_state = state
81
126
  end
82
127
 
83
128
  def decide(command, &block)
@@ -85,7 +130,7 @@ module Decider
85
130
  end
86
131
 
87
132
  def evolve(event, &block)
88
- evolvers[event] = block
133
+ evolutions[event] = block
89
134
  end
90
135
 
91
136
  def terminal?(&block)
@@ -101,7 +146,7 @@ module Decider
101
146
 
102
147
  def self.compose(left, right)
103
148
  define do
104
- state left: left.initial_state, right: right.initial_state
149
+ initial_state Composition.new(left.initial_state, right.initial_state)
105
150
 
106
151
  left.commands.each do |klass|
107
152
  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.2.0
4
+ version: 0.4.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-04 00:00:00.000000000 Z
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