decide.rb 0.1.0 → 0.2.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: 7b7afded6cbe7cf6fd572d9a7779875e901dbe81b25292847eea0377a549538c
4
- data.tar.gz: 713abc6c3365c06b611a254859bb83fe0a642c26bef2ee51c8c53eb075c47e72
3
+ metadata.gz: 1d042a0810111e7fcb0f9226ad109b20903a8305689df72d25a04caaa17deeff
4
+ data.tar.gz: d45f4d9aa5edc832dbdf303c8b0ba2e5aa6b7293c8c00614f9f8dfc69186c5c3
5
5
  SHA512:
6
- metadata.gz: 969092990e15943086821f2821d44d73e6bff46e77aa865904cf8152dd4cdadc65a979bfa6edd241583f2aee2748fd229b86f5e2a188fd04c9306c1b5209c963
7
- data.tar.gz: 29b205b6af520c32e971c6e6a5691f9d4cd4e745a0290fd932ee96217018a69a7429722eb7fa3c66b74e204f10529406075cf6e3e2aa8e89af053139f86c9838
6
+ metadata.gz: bf2b770bdabf04def99cbb942d6de633b00a9be6637adec05e81ad52228022e92e0cfc8e71e2e9ac12501febcdb411386242eb341d25e4d0be8f52b62f761ff6
7
+ data.tar.gz: 614461548e7859a8524cbd6e29961790443e89a10e69d49ec690448af7204afb04a71bf984e8825de3d7b57496753e836e98c0b7ff9c4475d2715838ffaa0075
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.2.0
2
+
3
+ * Added `terminal?` function
4
+ * `Decider.compose(left, right)`
5
+
1
6
  # 0.1.0
2
7
 
3
8
  * Initial release
data/README.md CHANGED
@@ -19,7 +19,7 @@ gem "decide.rb"
19
19
  ## Usage
20
20
 
21
21
  ```ruby
22
- require "decide.rb"
22
+ require "decider"
23
23
 
24
24
  module Commands
25
25
  Increase = Data.define
@@ -31,10 +31,10 @@ module Events
31
31
  ValueDecreased = Data.define
32
32
  end
33
33
 
34
- ValueDecider = Decider.define do
35
- MIN = 0
36
- MAX = 100
34
+ MIN = 0
35
+ MAX = 100
37
36
 
37
+ ValueDecider = Decider.define do
38
38
  # define intial state
39
39
  state value: 0 do
40
40
  # you can define custom methods on state
@@ -75,6 +75,10 @@ ValueDecider = Decider.define do
75
75
  # state is immutable Data object
76
76
  state.with(value: state.value - 1)
77
77
  end
78
+
79
+ terminal? do |state|
80
+ state <= 0
81
+ end
78
82
  end
79
83
 
80
84
  state = ValueDecider.initial_state
@@ -82,6 +86,53 @@ events = ValueDecider.decide(Commands::Increase.new, state)
82
86
  new_state = events.reduce(state) { |state, event| ValueDecider.evolve(state, events)
83
87
  ```
84
88
 
89
+ You can also compose deciders:
90
+
91
+ ```ruby
92
+ left = Decider.define do
93
+ state value: 0
94
+
95
+ decide Commands::LeftCommand do |command, state|
96
+ [Events::LeftEvent.new(value: command.value)]
97
+ end
98
+
99
+ evolve Events::LeftEvent do |state, event|
100
+ state.with(value: state.value + 1)
101
+ end
102
+
103
+ terminal? do |state|
104
+ state <= 0
105
+ end
106
+ end
107
+
108
+ right = Decider.define do
109
+ state value: 0
110
+
111
+ decide Commands::RightCommand do |command, state|
112
+ [Events::RightEvent.new(value: command.value)]
113
+ end
114
+
115
+ evolve Events::RightEvent do |state, event|
116
+ state.with(value: state.value + 1)
117
+ end
118
+
119
+ terminal? do |state|
120
+ state <= 0
121
+ end
122
+ end
123
+
124
+ Composition = Decider.compose(left, right)
125
+
126
+ state = Composition.initial_state
127
+ #> #<data left=#<data value=0>, right=#<data value=0>>
128
+
129
+ events = Composition.decide(Commands::LeftCommand.new(value: 1), state)
130
+ #> [#<data value=1>]
131
+
132
+ state = events.reduce(state, &Composition.method(:evolve))
133
+ #> #<data left=#<data value=1>, right=#<data value=0>>
134
+ ```
135
+
85
136
  ## Development
86
137
 
87
138
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Steepfile ADDED
@@ -0,0 +1,26 @@
1
+ D = Steep::Diagnostic
2
+
3
+ target :lib do
4
+ signature "sig"
5
+
6
+ check "lib"
7
+ check "Gemfile"
8
+
9
+ # library "pathname" # Standard libraries
10
+ # library "strong_json" # Gems
11
+
12
+ # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
13
+ configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
14
+ # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
15
+ # configure_code_diagnostics do |hash| # You can setup everything yourself
16
+ # hash[D::Ruby::NoMethod] = :information
17
+ # end
18
+ end
19
+
20
+ # target :test do
21
+ # signature "sig", "sig-private"
22
+
23
+ # check "test"
24
+
25
+ # # library "pathname" # Standard libraries
26
+ # end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decider
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/decider.rb CHANGED
@@ -5,11 +5,19 @@ module Decider
5
5
  StateNotDefined = Class.new(StandardError)
6
6
 
7
7
  class Module < ::Module
8
- def initialize(initial_state_args:, deciders:, evolvers:)
8
+ def initialize(initial_state_args:, deciders:, evolvers:, terminal:)
9
9
  define_method(:initial_state) do
10
10
  new(**initial_state_args)
11
11
  end
12
12
 
13
+ define_method(:commands) do
14
+ deciders.keys
15
+ end
16
+
17
+ define_method(:events) do
18
+ evolvers.keys
19
+ end
20
+
13
21
  define_method(:decide) do |command, state|
14
22
  handler = deciders.fetch(command.class) {
15
23
  raise ArgumentError, "Unknown command: #{command.class}"
@@ -25,6 +33,10 @@ module Decider
25
33
 
26
34
  handler.call(state, event)
27
35
  end
36
+
37
+ define_method(:terminal?) do |state|
38
+ terminal.call(state)
39
+ end
28
40
  end
29
41
  end
30
42
 
@@ -37,6 +49,7 @@ module Decider
37
49
  @state = DEFAULT
38
50
  @deciders = {}
39
51
  @evolvers = {}
52
+ @terminal = ->(_state) { false }
40
53
  end
41
54
 
42
55
  def build(&block)
@@ -47,7 +60,8 @@ module Decider
47
60
  @module = Module.new(
48
61
  initial_state_args: initial_state_args,
49
62
  deciders: deciders,
50
- evolvers: evolvers
63
+ evolvers: evolvers,
64
+ terminal: terminal
51
65
  )
52
66
 
53
67
  @state.extend(@module)
@@ -57,7 +71,7 @@ module Decider
57
71
 
58
72
  private
59
73
 
60
- attr_reader :initial_state_args, :deciders, :evolvers
74
+ attr_reader :initial_state_args, :deciders, :evolvers, :terminal
61
75
 
62
76
  def state(**kwargs, &block)
63
77
  raise StateAlreadyDefined if @state != DEFAULT
@@ -73,6 +87,10 @@ module Decider
73
87
  def evolve(event, &block)
74
88
  evolvers[event] = block
75
89
  end
90
+
91
+ def terminal?(&block)
92
+ @terminal = block
93
+ end
76
94
  end
77
95
  private_constant :Builder
78
96
 
@@ -80,4 +98,42 @@ module Decider
80
98
  builder = Builder.new
81
99
  builder.build(&block)
82
100
  end
101
+
102
+ def self.compose(left, right)
103
+ define do
104
+ state left: left.initial_state, right: right.initial_state
105
+
106
+ left.commands.each do |klass|
107
+ decide klass do |command, state|
108
+ left.decide(command, state.left)
109
+ end
110
+ end
111
+
112
+ right.commands.each do |klass|
113
+ decide klass do |command, state|
114
+ right.decide(command, state.right)
115
+ end
116
+ end
117
+
118
+ left.events.each do |klass|
119
+ evolve klass do |state, event|
120
+ state.with(
121
+ left: left.evolve(state.left, event)
122
+ )
123
+ end
124
+ end
125
+
126
+ right.events.each do |klass|
127
+ evolve klass do |state, event|
128
+ state.with(
129
+ right: right.evolve(state.right, event)
130
+ )
131
+ end
132
+ end
133
+
134
+ terminal? do |state|
135
+ left.terminal?(state.left) && right.terminal?(state.right)
136
+ end
137
+ end
138
+ end
83
139
  end
data/sig/decider.rbs CHANGED
@@ -1,3 +1,16 @@
1
1
  module Decider
2
2
  VERSION: String
3
+
4
+ interface _Decider[C, S, E]
5
+ def decide: (C, S) -> Array[E]
6
+
7
+ def evolve: (S, E) -> S
8
+
9
+ def initial_state: () -> S
10
+
11
+ def terminal?: (S) -> bool
12
+ end
13
+
14
+ def self.compose: [C1, S1, E1, C2, S2, E2] (_Decider[C1, S1, E1], _Decider[C2, S2, E2]) -> _Decider[C1 | C2, S1 & S2, E1 | E2]
15
+ def self.define: [C, S, E] () -> _Decider[C, S, E]
3
16
  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.1.0
4
+ version: 0.2.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-10-20 00:00:00.000000000 Z
11
+ date: 2024-11-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Functional Event Sourcing Decider in Ruby
14
14
  email:
@@ -22,19 +22,20 @@ files:
22
22
  - LICENSE.txt
23
23
  - README.md
24
24
  - Rakefile
25
+ - Steepfile
25
26
  - lib/decider.rb
26
27
  - lib/decider/version.rb
27
28
  - sig/decider.rbs
28
- homepage: https://github.com/jandudulski/decider.rb
29
+ homepage: https://github.com/jandudulski/decide.rb
29
30
  licenses:
30
31
  - MIT
31
32
  metadata:
32
33
  allowed_push_host: https://rubygems.org
33
- bug_tracker_uri: https://github.com/jandudulski/decider.rb/issues
34
- changelog_uri: https://github.com/jandudulski/decider.rb/CHANGELOG.md
35
- documentation_uri: https://github.com/jandudulski/decider.rb
36
- homepage_uri: https://github.com/jandudulski/decider.rb
37
- source_code_uri: https://github.com/jandudulski/decider.rb
34
+ bug_tracker_uri: https://github.com/jandudulski/decide.rb/issues
35
+ changelog_uri: https://github.com/jandudulski/decide.rb/CHANGELOG.md
36
+ documentation_uri: https://github.com/jandudulski/decide.rb
37
+ homepage_uri: https://github.com/jandudulski/decide.rb
38
+ source_code_uri: https://github.com/jandudulski/decide.rb
38
39
  post_install_message:
39
40
  rdoc_options: []
40
41
  require_paths: