decide.rb 0.6.2 → 0.7.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: cdfca1b85746ead6ae446e0b9bfb697a4cfb08685581cabe78cf6462d88db9a4
4
- data.tar.gz: ab375b8863a3e88c39e3f8ce3c6c7957758b2d557c7770f260ba4278843a6fea
3
+ metadata.gz: 560335da25429790af3eb660d743eb7233c3dad032f9582462de1142c9cf3d91
4
+ data.tar.gz: c42275236a358e3eac7189cf6e7f57cdc3106aa9e00f203b96d139f24360ee91
5
5
  SHA512:
6
- metadata.gz: '08fa1bb4c0444cc2a843b853cf9d6050910574c161a409fda2a65f4e628c014abd55416c5e5ef4e4ddeae8b182673186ce61998bba2187cfcb37521ed81dcd60'
7
- data.tar.gz: 968ddeb2cd7fac20a82f409c047083d375be4eec79a3c99c0596b8d1bc78ac14000431ddfdc5d66bd20d3ba23f6a6e173a88ec098245e8b461efcc9af818e713
6
+ metadata.gz: 0da501705ecb75d91b1696f468c9360bc950e7cc0f5ac1ce52a3427beccb2bba8eca1688bee73e874c46c40f17d579ddde4b132a2e6fe04e3737b7091f99619d
7
+ data.tar.gz: ea55c13433fb463d9746da6fe08016353bc0d38c3aae988f0b7249cfac86ed5325b0bcbb02c3833e5997f43db61f7b293a822988a851273dc39b087d7e4b2e14
data/CHANGELOG.md CHANGED
@@ -1,3 +1,62 @@
1
+ # 0.7.0
2
+
3
+ * Add reactor that can react to action results and issue actions
4
+
5
+ ```ruby
6
+ ActionResult = Data.define(:value)
7
+ Action = Data.define(:value)
8
+
9
+ reactor = Reactor.define do
10
+ react :action_result do
11
+ issue :action
12
+ issue :another_action
13
+ end
14
+
15
+ react proc { action_result in ActionResult(value: 42) } do
16
+ issue Action.new(value: "the answer")
17
+ end
18
+
19
+ react ActionResult do
20
+ issue Action.new(value: action_result.value)
21
+ end
22
+ end
23
+
24
+ reactor.react(:action_result)
25
+ # => [:action, :another_action]
26
+ reactor.react(ActionResult.new(value: 42)
27
+ # => #<data Action value="the answer">
28
+ reactor.react(ActionResult.new(value: 1)
29
+ # => #<data Action value=1>
30
+ ```
31
+
32
+ * Add `lmap_on_action_result` extension to reactor
33
+ * Add `rmap_on_action` (aliased to `map_on_action`) extensions to reactor
34
+ * Add `combine_with_decider` extension to reactor
35
+
36
+ ```ruby
37
+ decider = Decider.define do
38
+ initial_state 0
39
+
40
+ decide :action do
41
+ emit :result
42
+ end
43
+
44
+ decide :another_action do
45
+ emit :another_result
46
+ end
47
+ end
48
+
49
+ reactor = Decider::Reactor.define do
50
+ react :result do
51
+ issue :another_action
52
+ end
53
+ end
54
+
55
+ decider = reactor.combine_with_decider(decider)
56
+ decider.decide(:action)
57
+ # => [:result, :another_result]
58
+ ```
59
+
1
60
  # 0.6.2
2
61
 
3
62
  * Add `many` extension that takes a decider and manage many instances
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decider::Reactor
4
+ class Module < ::Module
5
+ REACT_FALLBACK = proc { [nil, proc {}] }
6
+
7
+ React = Data.define(:action_result, :_actions) do
8
+ def issue(*actions)
9
+ _actions.push(*actions)
10
+ end
11
+ end
12
+
13
+ def initialize(reactions:)
14
+ define_method(:react) do |action_result|
15
+ context = React.new(action_result: action_result, _actions: [])
16
+
17
+ reactions.find(REACT_FALLBACK) do |arg, _|
18
+ case arg
19
+ in Proc => fn
20
+ context.instance_exec(&fn)
21
+ in artype
22
+ action_result in ^artype
23
+ else
24
+ false
25
+ end
26
+ end => [_, handler]
27
+
28
+ context.instance_exec(&handler)
29
+ context._actions
30
+ end
31
+
32
+ define_method(:lmap_on_action_result) do |fn|
33
+ Decider::Reactor.lmap_on_action_result(fn, self)
34
+ end
35
+
36
+ define_method(:rmap_on_action) do |fn|
37
+ Decider::Reactor.rmap_on_action(fn, self)
38
+ end
39
+
40
+ define_method(:map_on_action) do |fn|
41
+ Decider::Reactor.rmap_on_action(fn, self)
42
+ end
43
+
44
+ define_method(:combine_with_decider) do |decider|
45
+ Decider::Reactor.combine_with_decider(self, decider)
46
+ end
47
+ end
48
+ end
49
+
50
+ class Builder
51
+ DEFAULT = Object.new
52
+
53
+ attr_reader :module
54
+
55
+ def initialize
56
+ @reactions = {}
57
+ end
58
+
59
+ def build(&block)
60
+ instance_exec(&block) if block_given?
61
+
62
+ reactor = Class.new
63
+
64
+ @module = Module.new(
65
+ reactions: reactions
66
+ )
67
+
68
+ reactor.extend(@module)
69
+
70
+ reactor
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :reactions
76
+
77
+ def react(arg, &block)
78
+ reactions[arg] = block
79
+ end
80
+ end
81
+ private_constant :Builder
82
+
83
+ def self.define(&block)
84
+ builder = Builder.new
85
+ builder.build(&block)
86
+ end
87
+
88
+ def self.lmap_on_action_result(fn, reactor)
89
+ define do
90
+ react proc { true } do
91
+ reactor.react(fn.call(action_result)).each do |action|
92
+ issue action
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def self.rmap_on_action(fn, reactor)
99
+ define do
100
+ react proc { true } do
101
+ reactor.react(action_result).each do |action|
102
+ issue fn.call(action)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def self.map_on_action(fn, reactor)
109
+ rmap_on_action(fn, reactor)
110
+ end
111
+
112
+ def self.combine_with_decider(reactor, decider)
113
+ Decider.define do
114
+ initial_state decider.initial_state
115
+
116
+ decide proc { true } do
117
+ fn = ->(commands, events, ds) {
118
+ case commands
119
+ in []
120
+ events
121
+ in [head, *tail]
122
+ new_events = decider.decide(head, ds)
123
+ new_commands = new_events.flat_map { |action_result| reactor.react(action_result) }
124
+ new_state = new_events.reduce(ds, &decider.evolve)
125
+
126
+ fn.call(tail + new_commands, events + new_events, new_state)
127
+ end
128
+ }
129
+
130
+ fn.call([command], [], state).each { |event| emit event }
131
+ end
132
+
133
+ evolve proc { true } do
134
+ decider.evolve(state, event)
135
+ end
136
+
137
+ terminal? do
138
+ decider.terminal?(state)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decider
4
- VERSION = "0.6.2"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/decider.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "decider/reactor"
4
+
3
5
  module Decider
4
6
  StateAlreadyDefined = Class.new(StandardError)
5
7
  StateNotDefined = Class.new(StandardError)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decide.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Dudulski
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-23 00:00:00.000000000 Z
10
+ date: 2025-07-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: concurrent-ruby
@@ -71,6 +71,7 @@ files:
71
71
  - lib/decider.rb
72
72
  - lib/decider/event_sourcing.rb
73
73
  - lib/decider/in_memory.rb
74
+ - lib/decider/reactor.rb
74
75
  - lib/decider/state.rb
75
76
  - lib/decider/version.rb
76
77
  - sig/decider.rbs