decide.rb 0.6.2 → 0.7.1

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: 8ac9878aee3455667ca2fa5e7b4226af3588387e8f42a5a67e102b96c9b2e74d
4
+ data.tar.gz: 3f168429cc20342dca21ab57ad82ab498e3ee2d83623713c3f730966d05e7032
5
5
  SHA512:
6
- metadata.gz: '08fa1bb4c0444cc2a843b853cf9d6050910574c161a409fda2a65f4e628c014abd55416c5e5ef4e4ddeae8b182673186ce61998bba2187cfcb37521ed81dcd60'
7
- data.tar.gz: 968ddeb2cd7fac20a82f409c047083d375be4eec79a3c99c0596b8d1bc78ac14000431ddfdc5d66bd20d3ba23f6a6e173a88ec098245e8b461efcc9af818e713
6
+ metadata.gz: b8b3240f864d16a1c31eb2575ec700a0bd5674feee8818fe2e6342e2c0807cc8f0c6ac9fc780699d6ccc0c896ce615f829b7d121c7d059c2f3dad1aef192e321
7
+ data.tar.gz: 3098b0a090dbe5b71eb90cefa2e57127632d40b333aa678c0a0ee6a09bc26e351b3005554bf259ad2011cc4745aef32a158f57cd8d93980efe2c0df33edf3c53
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,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decider
4
+ module Reactor
5
+ class Module < ::Module
6
+ REACT_FALLBACK = proc { [nil, proc {}] }
7
+
8
+ React = Data.define(:action_result, :_actions) do
9
+ def issue(*actions)
10
+ _actions.push(*actions)
11
+ end
12
+ end
13
+
14
+ def initialize(reactions:)
15
+ define_method(:react) do |action_result|
16
+ context = React.new(action_result: action_result, _actions: [])
17
+
18
+ reactions.find(REACT_FALLBACK) do |arg, _|
19
+ case arg
20
+ in Proc => fn
21
+ context.instance_exec(&fn)
22
+ in artype
23
+ action_result in ^artype
24
+ else
25
+ false
26
+ end
27
+ end => [_, handler]
28
+
29
+ context.instance_exec(&handler)
30
+ context._actions
31
+ end
32
+
33
+ define_method(:lmap_on_action_result) do |fn|
34
+ Decider::Reactor.lmap_on_action_result(fn, self)
35
+ end
36
+
37
+ define_method(:rmap_on_action) do |fn|
38
+ Decider::Reactor.rmap_on_action(fn, self)
39
+ end
40
+
41
+ define_method(:map_on_action) do |fn|
42
+ Decider::Reactor.rmap_on_action(fn, self)
43
+ end
44
+
45
+ define_method(:combine_with_decider) do |decider|
46
+ Decider::Reactor.combine_with_decider(self, decider)
47
+ end
48
+ end
49
+ end
50
+
51
+ class Builder
52
+ DEFAULT = Object.new
53
+
54
+ attr_reader :module
55
+
56
+ def initialize
57
+ @reactions = {}
58
+ end
59
+
60
+ def build(&block)
61
+ instance_exec(&block) if block_given?
62
+
63
+ reactor = Class.new
64
+
65
+ @module = Module.new(
66
+ reactions: reactions
67
+ )
68
+
69
+ reactor.extend(@module)
70
+
71
+ reactor
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :reactions
77
+
78
+ def react(arg, &block)
79
+ reactions[arg] = block
80
+ end
81
+ end
82
+ private_constant :Builder
83
+
84
+ def self.define(&block)
85
+ builder = Builder.new
86
+ builder.build(&block)
87
+ end
88
+
89
+ def self.lmap_on_action_result(fn, reactor)
90
+ define do
91
+ react proc { true } do
92
+ reactor.react(fn.call(action_result)).each do |action|
93
+ issue action
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def self.rmap_on_action(fn, reactor)
100
+ define do
101
+ react proc { true } do
102
+ reactor.react(action_result).each do |action|
103
+ issue fn.call(action)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def self.map_on_action(fn, reactor)
110
+ rmap_on_action(fn, reactor)
111
+ end
112
+
113
+ def self.combine_with_decider(reactor, decider)
114
+ Decider.define do
115
+ initial_state decider.initial_state
116
+
117
+ decide proc { true } do
118
+ fn = ->(commands, events, ds) {
119
+ case commands
120
+ in []
121
+ events
122
+ in [head, *tail]
123
+ new_events = decider.decide(head, ds)
124
+ new_commands = new_events.flat_map { |action_result| reactor.react(action_result) }
125
+ new_state = new_events.reduce(ds, &decider.evolve)
126
+
127
+ fn.call(tail + new_commands, events + new_events, new_state)
128
+ end
129
+ }
130
+
131
+ fn.call([command], [], state).each { |event| emit event }
132
+ end
133
+
134
+ evolve proc { true } do
135
+ decider.evolve(state, event)
136
+ end
137
+
138
+ terminal? do
139
+ decider.terminal?(state)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ 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.1"
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.1
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-23 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