decide.rb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: