decide.rb 0.4.0 → 0.5.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: 73423e6911201b60ed5a3333ef636880cc93751425e95b39e522bed75d9ca4a5
4
- data.tar.gz: 2b352d0c34587f8dfe90e819504e3d07093de738ddde24cea4e96a152f09ce0d
3
+ metadata.gz: 41af414c2f92dc8e43dad1ac61cf6ffb939fa65bc7217223b29e9780067ce5d3
4
+ data.tar.gz: 96342dea929121644eca8d924fbcd1112fbb21ec34fb666c00e87261cf7d4dc1
5
5
  SHA512:
6
- metadata.gz: 8f2e2449fac23e3ae81014a6fd0cfefd5a2a888089c3c89b2072cb52592f6ba5bb659c9f2a816c6d8b13eb6082b463f42594dbffedb2add06469857b7bfe07d0
7
- data.tar.gz: 1a310ac9f050f40ba09df6605860fc62b348e093133fd0237618726753d25f5c669f72b723b9072664862d1d80a01898dd963dbd323c97ef30c8533ed9f523e7
6
+ metadata.gz: acec8f3699e15b22ee30780b2b2e30cc95950c9d18701dbcaddc5788723780e0d6c09e16066c000db8a24ec87e62eadf20469c391c5140af662b9b951b61e41b
7
+ data.tar.gz: b088dec6fb91fe9417f83b73ebe58dd638d31ffb78f1245e62afa826a5f416d1bede83c41cc0d612afa29d50afeb119703a42e5ec3570daad08003b1bb304eb0
data/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
- # Unreleased
1
+ # 0.5.0
2
+
3
+ * Support pattern matching for commands and events
4
+ * Support passing state to decider and evolve matchers
5
+ * Remove explicit arguments for handlers
6
+ * Remove redundant bang methods - raise error in catch-all if needed
7
+ * Add Left|Right value wrappers for composition
8
+ * Use `emit` to return events in `decide`
9
+
10
+ # 0.4.1
11
+
12
+ * `define` returns new class, not object
13
+
14
+ # 0.4.0
2
15
 
3
16
  * Accept more data structures for commands and events
4
17
 
data/README.md CHANGED
@@ -49,7 +49,7 @@ ValueDecider = Decider.define do
49
49
  initial_state State.new(value: 0)
50
50
 
51
51
  # decide command with state
52
- decide Commands::Increase do |command, state|
52
+ decide Commands::Increase do
53
53
  # return collection of events
54
54
  if state.max?
55
55
  []
@@ -58,7 +58,7 @@ ValueDecider = Decider.define do
58
58
  end
59
59
  end
60
60
 
61
- decide Commands::Decrease do |command, state|
61
+ decide Commands::Decrease do
62
62
  if state.min?
63
63
  []
64
64
  else
@@ -67,17 +67,17 @@ ValueDecider = Decider.define do
67
67
  end
68
68
 
69
69
  # evolve state with events
70
- evolve Events::ValueIncreased do |state, event|
70
+ evolve Events::ValueIncreased do
71
71
  # return new state
72
72
  state.with(value: state.value + 1)
73
73
  end
74
74
 
75
- evolve Events::ValueDecreased do |state, event|
75
+ evolve Events::ValueDecreased do
76
76
  # state is immutable Data object
77
77
  state.with(value: state.value - 1)
78
78
  end
79
79
 
80
- terminal? do |state|
80
+ terminal? do
81
81
  state <= 0
82
82
  end
83
83
  end
@@ -96,15 +96,15 @@ Right = Data.define(:value)
96
96
  left = Decider.define do
97
97
  initial_state Left.new(value: 0)
98
98
 
99
- decide Commands::LeftCommand do |command, state|
99
+ decide Commands::LeftCommand do
100
100
  [Events::LeftEvent.new(value: command.value)]
101
101
  end
102
102
 
103
- evolve Events::LeftEvent do |state, event|
103
+ evolve Events::LeftEvent do
104
104
  state.with(value: state.value + 1)
105
105
  end
106
106
 
107
- terminal? do |state|
107
+ terminal? do
108
108
  state <= 0
109
109
  end
110
110
  end
@@ -112,15 +112,15 @@ end
112
112
  right = Decider.define do
113
113
  initial_state Right.new(value: 0)
114
114
 
115
- decide Commands::RightCommand do |command, state|
115
+ decide Commands::RightCommand do
116
116
  [Events::RightEvent.new(value: command.value)]
117
117
  end
118
118
 
119
- evolve Events::RightEvent do |state, event|
119
+ evolve Events::RightEvent do
120
120
  state.with(value: state.value + 1)
121
121
  end
122
122
 
123
- terminal? do |state|
123
+ terminal? do
124
124
  state <= 0
125
125
  end
126
126
  end
@@ -128,13 +128,13 @@ end
128
128
  Composition = Decider.compose(left, right)
129
129
 
130
130
  state = Composition.initial_state
131
- #> #<data left=#<data Left value=0>, right=#<data Right value=0>>
131
+ #> #<data Decider::Pair left=#<data Left value=0>, right=#<data Right value=0>>
132
132
 
133
- events = Composition.decide(Commands::LeftCommand.new(value: 1), state)
134
- #> [#<data value=1>]
133
+ events = Composition.decide(Decider::Left.new(Commands::LeftCommand.new(value: 1)), state)
134
+ #> [#<Decider::Left value=#<data value=1>]
135
135
 
136
136
  state = events.reduce(state, &Composition.method(:evolve))
137
- #> #<data left=#<data value=1>, right=#<data value=0>>
137
+ #> #<data Decider::Pair left=#<data value=1>, right=#<data value=0>>
138
138
  ```
139
139
 
140
140
  ## Development
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decider
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/decider.rb CHANGED
@@ -4,82 +4,100 @@ 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
7
+ Pair = Data.define(:left, :right)
10
8
 
11
- def initialize(left, right)
12
- super([left, right]).freeze
9
+ class Left
10
+ attr_reader :value
11
+
12
+ def initialize(value)
13
+ @value = value
13
14
  end
14
15
 
15
- def with(left: BLANK, right: BLANK)
16
- Composition.new(
17
- (left == BLANK) ? self.left : left,
18
- (right == BLANK) ? self.right : right
19
- )
16
+ def deconstruct
17
+ [:left, value]
18
+ end
19
+
20
+ def ==(other)
21
+ value == other.value
20
22
  end
23
+ end
24
+
25
+ class Right
26
+ attr_reader :value
21
27
 
22
- def left
23
- self[0]
28
+ def initialize(value)
29
+ @value = value
24
30
  end
25
31
 
26
- def right
27
- self[1]
32
+ def deconstruct
33
+ [:right, value]
34
+ end
35
+
36
+ def ==(other)
37
+ value == other.value
28
38
  end
29
39
  end
30
40
 
31
41
  class Module < ::Module
32
- def initialize(initial_state:, deciders:, evolutions:, terminal:)
33
- define_method(:initial_state) do
34
- initial_state
35
- end
36
-
37
- define_method(:commands) do
38
- deciders.keys
39
- end
42
+ DECIDE_FALLBACK = proc { [nil, proc {}] }
43
+ EVOLVE_FALLBACK = proc { [nil, proc { state }] }
40
44
 
41
- define_method(:events) do
42
- evolutions.keys
45
+ Decide = Data.define(:command, :state, :_events) do
46
+ def emit(*events)
47
+ _events.push(*events)
43
48
  end
49
+ end
50
+ Evolve = Data.define(:state, :event)
51
+ Terminal = Data.define(:state)
44
52
 
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
53
+ def initialize(initial_state:, deciders:, evolutions:, terminal:)
54
+ define_method(:initial_state) do
55
+ initial_state
52
56
  end
53
57
 
54
58
  define_method(:decide) do |command, state|
55
- case deciders.find { |key, _| key === command }
56
- in [_, handler]
57
- handler.call(command, state)
58
- else
59
- []
60
- end
61
- end
62
-
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
59
+ context = Decide.new(command: command, state: state, _events: [])
60
+
61
+ deciders.find(DECIDE_FALLBACK) do |args, _|
62
+ case args
63
+ in [Proc => fn]
64
+ context.instance_exec(&fn)
65
+ in [ctype]
66
+ command in ^ctype
67
+ in [ctype, stype]
68
+ [command, state] in [^ctype, ^stype]
69
+ else
70
+ false
71
+ end
72
+ end => [_, handler]
73
+
74
+ context.instance_exec(&handler)
75
+ context._events
70
76
  end
71
77
 
72
78
  define_method(:evolve) do |state, event|
73
- case evolutions.find { |key, _| key === event }
74
- in [_, handler]
75
- handler.call(state, event)
76
- else
77
- state
78
- end
79
+ context = Evolve.new(state: state, event: event)
80
+
81
+ evolutions.find(EVOLVE_FALLBACK) do |args, _|
82
+ case args
83
+ in [Proc => fn]
84
+ context.instance_exec(&fn)
85
+ in [etype]
86
+ event in ^etype
87
+ in [stype, etype]
88
+ [state, event] in [^stype, ^etype]
89
+ else
90
+ false
91
+ end
92
+ end => [_, handler]
93
+
94
+ context.instance_exec(&handler)
79
95
  end
80
96
 
81
97
  define_method(:terminal?) do |state|
82
- terminal.call(state)
98
+ context = Terminal.new(state: state)
99
+
100
+ context.instance_exec(&terminal)
83
101
  end
84
102
  end
85
103
  end
@@ -93,7 +111,7 @@ module Decider
93
111
  @initial_state = DEFAULT
94
112
  @deciders = {}
95
113
  @evolutions = {}
96
- @terminal = ->(_state) { false }
114
+ @terminal = proc { false }
97
115
  end
98
116
 
99
117
  def build(&block)
@@ -101,7 +119,7 @@ module Decider
101
119
 
102
120
  raise StateNotDefined if @initial_state == DEFAULT
103
121
 
104
- decider = Object.new
122
+ decider = Class.new
105
123
 
106
124
  @module = Module.new(
107
125
  initial_state: @initial_state,
@@ -125,12 +143,12 @@ module Decider
125
143
  @initial_state = state
126
144
  end
127
145
 
128
- def decide(command, &block)
129
- deciders[command] = block
146
+ def decide(*args, &block)
147
+ deciders[args] = block
130
148
  end
131
149
 
132
- def evolve(event, &block)
133
- evolutions[event] = block
150
+ def evolve(*args, &block)
151
+ evolutions[args] = block
134
152
  end
135
153
 
136
154
  def terminal?(&block)
@@ -146,37 +164,32 @@ module Decider
146
164
 
147
165
  def self.compose(left, right)
148
166
  define do
149
- initial_state Composition.new(left.initial_state, right.initial_state)
167
+ initial_state Pair.new(
168
+ left: left.initial_state,
169
+ right: right.initial_state
170
+ )
150
171
 
151
- left.commands.each do |klass|
152
- decide klass do |command, state|
153
- left.decide(command, state.left)
154
- end
172
+ decide proc { command in [:left, _] } do
173
+ left.decide(command.value, state.left).each { emit Left.new(_1) }
155
174
  end
156
175
 
157
- right.commands.each do |klass|
158
- decide klass do |command, state|
159
- right.decide(command, state.right)
160
- end
176
+ decide proc { command in [:right, _] } do
177
+ right.decide(command.value, state.right).each { emit Right.new(_1) }
161
178
  end
162
179
 
163
- left.events.each do |klass|
164
- evolve klass do |state, event|
165
- state.with(
166
- left: left.evolve(state.left, event)
167
- )
168
- end
180
+ evolve proc { event in [:left, _] } do
181
+ state.with(
182
+ left: left.evolve(state.left, event.value)
183
+ )
169
184
  end
170
185
 
171
- right.events.each do |klass|
172
- evolve klass do |state, event|
173
- state.with(
174
- right: right.evolve(state.right, event)
175
- )
176
- end
186
+ evolve proc { event in [:right, _] } do
187
+ state.with(
188
+ right: right.evolve(state.right, event.value)
189
+ )
177
190
  end
178
191
 
179
- terminal? do |state|
192
+ terminal? do
180
193
  left.terminal?(state.left) && right.terminal?(state.right)
181
194
  end
182
195
  end
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.0
4
+ version: 0.5.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 00:00:00.000000000 Z
11
+ date: 2024-12-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Functional Event Sourcing Decider in Ruby
14
14
  email: