standard-procedure-plumbing 0.3.0 → 0.3.2

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.
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+ require_relative "a_pipe"
3
+ require "async"
4
+
5
+ RSpec.describe Plumbing::Pipe do
6
+ context "inline" do
7
+ around :example do |example|
8
+ Plumbing.configure mode: :inline, &example
9
+ end
10
+
11
+ it_behaves_like "a pipe"
12
+ end
13
+
14
+ context "async" do
15
+ around :example do |example|
16
+ Sync do
17
+ Plumbing.configure mode: :async, &example
18
+ end
19
+ end
20
+
21
+ it_behaves_like "a pipe"
22
+ end
23
+ end
@@ -0,0 +1,208 @@
1
+ require "spec_helper"
2
+ require "dry/validation"
3
+
4
+ RSpec.describe Plumbing::Pipeline do
5
+ it "defines a single operation that returns a value based upon the input parameters" do
6
+ # standard:disable Lint/ConstantDefinitionInBlock
7
+ class Addition < Plumbing::Pipeline
8
+ perform :addition
9
+
10
+ private
11
+
12
+ def addition number
13
+ number + 1
14
+ end
15
+ end
16
+ # standard:enable Lint/ConstantDefinitionInBlock
17
+
18
+ expect(Addition.new.call(5)).to eq 6
19
+ end
20
+
21
+ it "defines a sequence of commands that are executed in order" do
22
+ # standard:disable Lint/ConstantDefinitionInBlock
23
+ class Sequence < Plumbing::Pipeline
24
+ perform :first_step
25
+ perform :second_step
26
+ perform :third_step
27
+
28
+ private
29
+
30
+ def first_step value = []
31
+ value << "first"
32
+ end
33
+
34
+ def second_step value = []
35
+ value << "second"
36
+ end
37
+
38
+ def third_step value = []
39
+ value << "third"
40
+ end
41
+ end
42
+ # standard:enable Lint/ConstantDefinitionInBlock
43
+
44
+ expect(Sequence.new.call([])).to eq ["first", "second", "third"]
45
+ end
46
+
47
+ context "embedding an external command" do
48
+ it "specifies the command with a string" do
49
+ # standard:disable Lint/ConstantDefinitionInBlock
50
+ class InnerByName < Plumbing::Pipeline
51
+ perform :embedded_step
52
+
53
+ private
54
+
55
+ def embedded_step value = []
56
+ value << "embedded"
57
+ end
58
+ end
59
+
60
+ class OuterByName < Plumbing::Pipeline
61
+ perform :first_step
62
+ perform :second_step, using: "InnerByName"
63
+ perform :third_step
64
+
65
+ private
66
+
67
+ def first_step value = []
68
+ value << "first"
69
+ end
70
+
71
+ def third_step value = []
72
+ value << "third"
73
+ end
74
+ end
75
+ # standard:enable Lint/ConstantDefinitionInBlock
76
+
77
+ expect(OuterByName.new.call([])).to eq ["first", "embedded", "third"]
78
+ end
79
+
80
+ it "specifies the command with a class" do
81
+ # standard:disable Lint/ConstantDefinitionInBlock
82
+ class InnerByClass < Plumbing::Pipeline
83
+ perform :embedded_step
84
+
85
+ private
86
+
87
+ def embedded_step value = []
88
+ value << "embedded"
89
+ end
90
+ end
91
+
92
+ class OuterByClass < Plumbing::Pipeline
93
+ perform :first_step
94
+ perform :second_step, using: InnerByClass
95
+ perform :third_step
96
+
97
+ private
98
+
99
+ def first_step value = []
100
+ value << "first"
101
+ end
102
+
103
+ def third_step value = []
104
+ value << "third"
105
+ end
106
+ end
107
+ # standard:enable Lint/ConstantDefinitionInBlock
108
+
109
+ expect(OuterByClass.new.call([])).to eq ["first", "embedded", "third"]
110
+ end
111
+ end
112
+
113
+ it "defines an operation that does something but returns the provided input untouched" do
114
+ # standard:disable Lint/ConstantDefinitionInBlock
115
+ class Passthrough < Plumbing::Pipeline
116
+ execute :external_operation
117
+
118
+ private
119
+
120
+ def external_operation input
121
+ # SomeApi.do_some_stuff input
122
+ nil
123
+ end
124
+ end
125
+ # standard:enable Lint/ConstantDefinitionInBlock
126
+
127
+ expect(Passthrough.new.call("some parameters")).to eq "some parameters"
128
+ end
129
+
130
+ it "raises a PreConditionError if the input fails the precondition test" do
131
+ # standard:disable Lint/ConstantDefinitionInBlock
132
+ class PreConditionCheck < Plumbing::Pipeline
133
+ pre_condition :has_first_key do |input|
134
+ input.key?(:first)
135
+ end
136
+
137
+ pre_condition :has_second_key do |input|
138
+ input.key?(:second)
139
+ end
140
+
141
+ perform :do_something
142
+
143
+ private
144
+
145
+ def do_something input
146
+ "#{input[:first]} #{input[:second]}"
147
+ end
148
+ end
149
+ # standard:enable Lint/ConstantDefinitionInBlock
150
+
151
+ expect(PreConditionCheck.new.call(first: "First", second: "Second")).to eq "First Second"
152
+ expect { PreConditionCheck.new.call(first: "First") }.to raise_error(Plumbing::PreConditionError, "has_second_key")
153
+ end
154
+
155
+ it "raises a PreConditionError if the input fails to validate against a Dry::Validation::Contract" do
156
+ # standard:disable Lint/ConstantDefinitionInBlock
157
+ class Validated < Plumbing::Pipeline
158
+ validate_with "Validated::Input"
159
+ perform :say_hello
160
+
161
+ private
162
+
163
+ def say_hello input
164
+ "Hello #{input[:name]} (#{input[:email]})"
165
+ end
166
+
167
+ class Input < Dry::Validation::Contract
168
+ params do
169
+ required(:name).filled(:string)
170
+ required(:email).filled(:string)
171
+ end
172
+ rule :email do
173
+ key.failure("must be a valid email") unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match? value
174
+ end
175
+ end
176
+ end
177
+ # standard:enable Lint/ConstantDefinitionInBlock
178
+
179
+ expect(Validated.new.call(name: "Alice", email: "alice@example.com")).to eq "Hello Alice (alice@example.com)"
180
+ expect { Validated.new.call(email: "alice@example.com") }.to raise_error(Plumbing::PreConditionError, {name: ["is missing"]}.to_yaml)
181
+ expect { Validated.new.call(name: "Bob", email: "bob-has-fat-fingers-and-cant-type") }.to raise_error(Plumbing::PreConditionError, {email: ["must be a valid email"]}.to_yaml)
182
+ end
183
+
184
+ it "raises a PostConditionError if the outputs fail the postcondition test" do
185
+ # standard:disable Lint/ConstantDefinitionInBlock
186
+ class PostConditionCheck < Plumbing::Pipeline
187
+ post_condition :should_be_integer do |result|
188
+ result.instance_of? Integer
189
+ end
190
+
191
+ post_condition :should_be_greater_than_zero do |result|
192
+ result > 0
193
+ end
194
+
195
+ perform :do_something
196
+
197
+ private
198
+
199
+ def do_something value
200
+ value.to_i
201
+ end
202
+ end
203
+ # standard:enable Lint/ConstantDefinitionInBlock
204
+
205
+ expect(PostConditionCheck.new.call("23")).to eq 23
206
+ expect { PostConditionCheck.new.call("NOT A NUMBER") }.to raise_error(Plumbing::PostConditionError, "should_be_greater_than_zero")
207
+ end
208
+ end
@@ -0,0 +1,76 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Plumbing::RubberDuck do
4
+ # standard:disable Lint/ConstantDefinitionInBlock
5
+ class Duck
6
+ def quack = "Quack"
7
+
8
+ def swim place
9
+ "Swim in #{place}"
10
+ end
11
+
12
+ def fly &block
13
+ "Fly #{block.call}"
14
+ end
15
+ end
16
+ # standard:enable Lint/ConstantDefinitionInBlock
17
+
18
+ it "verifies that an object matches the RubberDuck type" do
19
+ @duck_type = described_class.define :quack, :swim, :fly
20
+ @duck = Duck.new
21
+
22
+ expect(@duck_type.verify(@duck)).to eq @duck
23
+ end
24
+
25
+ it "casts the object to a duck type" do
26
+ @duck_type = described_class.define :quack, :swim, :fly
27
+ @duck = Duck.new
28
+
29
+ @proxy = @duck.as @duck_type
30
+
31
+ expect(@proxy).to be_kind_of Plumbing::RubberDuck::Proxy
32
+ expect(@proxy).to respond_to :quack
33
+ expect(@proxy.quack).to eq "Quack"
34
+ expect(@proxy).to respond_to :swim
35
+ expect(@proxy.swim("the river")).to eq "Swim in the river"
36
+ expect(@proxy).to respond_to :fly
37
+ expect(@proxy.fly { "ducky fly" }).to eq "Fly ducky fly"
38
+ end
39
+
40
+ it "does not forward methods that are not part of the duck type" do
41
+ @duck_type = described_class.define :swim, :fly
42
+ @duck = Duck.new
43
+
44
+ @proxy = @duck.as @duck_type
45
+
46
+ expect(@proxy).to_not respond_to :quack
47
+ end
48
+
49
+ it "does not wrap rubber ducks in a proxy" do
50
+ @duck_type = described_class.define :swim, :fly
51
+ @duck = Duck.new
52
+
53
+ @proxy = @duck.as @duck_type
54
+
55
+ expect(@proxy.as(@duck_type)).to eq @proxy
56
+ end
57
+
58
+ it "allows rubber ducks to be expanded and cast to other types" do
59
+ @quackers = described_class.define :quack
60
+ @swimming_bird = described_class.define :swim, :fly
61
+ @duck = Duck.new
62
+
63
+ @swimmer = @duck.as @swimming_bird
64
+ @quacker = @swimmer.as @quackers
65
+
66
+ expect(@swimmer).to respond_to :swim
67
+ expect(@quacker).to respond_to :quack
68
+ end
69
+
70
+ it "raises a TypeError if the class responds to the given methods" do
71
+ cow_type = described_class.define :moo, :chew
72
+ duck = Duck.new
73
+
74
+ expect { cow_type.verify(duck) }.to raise_error(TypeError)
75
+ end
76
+ end
@@ -0,0 +1,167 @@
1
+ require "spec_helper"
2
+ require "async"
3
+ require_relative "../../lib/plumbing/valve/async"
4
+
5
+ RSpec.describe Plumbing::Valve do
6
+ # standard:disable Lint/ConstantDefinitionInBlock
7
+ class Counter
8
+ include Plumbing::Valve
9
+ query :name, :count, :am_i_failing?, :slow_query
10
+ command "slowly_increment", "raises_error"
11
+ attr_reader :name, :count
12
+
13
+ def initialize name, initial_value: 0
14
+ @name = name
15
+ @count = initial_value
16
+ end
17
+
18
+ def slowly_increment
19
+ sleep 0.5
20
+ @count += 1
21
+ end
22
+
23
+ def slow_query
24
+ sleep 0.5
25
+ @count
26
+ end
27
+
28
+ def am_i_failing? = raise "I'm a failure"
29
+
30
+ def raises_error = raise "I'm an error"
31
+ end
32
+
33
+ class StepCounter < Counter
34
+ query :step_value
35
+ attr_reader :step_value
36
+
37
+ def initialize name, initial_value: 0, step_value: 5
38
+ super(name, initial_value: initial_value)
39
+ @step_value = step_value
40
+ end
41
+
42
+ def slowly_increment
43
+ sleep 0.5
44
+ @count += @step_value
45
+ end
46
+
47
+ def failing_query
48
+ raise "I'm a failure"
49
+ end
50
+ end
51
+ # standard:enable Lint/ConstantDefinitionInBlock
52
+
53
+ it "knows which queries are defined" do
54
+ expect(Counter.queries).to eq [:name, :count, :am_i_failing?, :slow_query]
55
+ end
56
+
57
+ it "knows which commands are defined" do
58
+ expect(Counter.commands).to eq [:slowly_increment, :raises_error]
59
+ end
60
+
61
+ it "raises exceptions from queries" do
62
+ @counter = Counter.start "failure"
63
+
64
+ expect { @counter.am_i_failing? }.to raise_error "I'm a failure"
65
+ end
66
+
67
+ it "does not raise exceptions from commands" do
68
+ @counter = Counter.start "failure"
69
+
70
+ expect { @counter.raises_error }.not_to raise_error
71
+ end
72
+
73
+ it "raises exceptions from queries" do
74
+ @counter = Counter.start "failure"
75
+
76
+ expect { @counter.am_i_failing? }.to raise_error "I'm a failure"
77
+ end
78
+
79
+ it "reuses existing proxy classes" do
80
+ @counter = Counter.start "inline counter", initial_value: 100
81
+ @proxy_class = @counter.class
82
+
83
+ @counter = Counter.start "another inline counter", initial_value: 200
84
+ expect(@counter.class).to eq @proxy_class
85
+ end
86
+
87
+ it "includes commands and queries from the superclass" do
88
+ expect(StepCounter.queries).to eq [:name, :count, :am_i_failing?, :slow_query, :step_value]
89
+ expect(StepCounter.commands).to eq [:slowly_increment, :raises_error]
90
+
91
+ @step_counter = StepCounter.start "step counter", initial_value: 100, step_value: 10
92
+
93
+ expect(@step_counter.count).to eq 100
94
+ expect(@step_counter.step_value).to eq 10
95
+ @step_counter.slowly_increment
96
+ expect(@step_counter.count).to eq 110
97
+ end
98
+
99
+ context "inline" do
100
+ around :example do |example|
101
+ Plumbing.configure mode: :inline, &example
102
+ end
103
+
104
+ it "sends all queries immediately" do
105
+ @counter = Counter.start "inline counter", initial_value: 100
106
+ @time = Time.now
107
+
108
+ expect(@counter.name).to eq "inline counter"
109
+ expect(@counter.count).to eq 100
110
+ expect(Time.now - @time).to be < 0.1
111
+
112
+ expect(@counter.slow_query).to eq 100
113
+ expect(Time.now - @time).to be > 0.4
114
+ end
115
+
116
+ it "sends all commands immediately" do
117
+ @counter = Counter.start "inline counter", initial_value: 100
118
+ @time = Time.now
119
+
120
+ @counter.slowly_increment
121
+
122
+ expect(@counter.count).to eq 101
123
+ expect(Time.now - @time).to be > 0.4
124
+ end
125
+ end
126
+
127
+ context "async" do
128
+ around :example do |example|
129
+ Sync do
130
+ Plumbing.configure mode: :async, &example
131
+ end
132
+ end
133
+
134
+ it "sends all queries using fibers and waits for the response" do
135
+ @counter = Counter.start "async counter", initial_value: 100
136
+ @time = Time.now
137
+
138
+ expect(@counter.name).to eq "async counter"
139
+ expect(@counter.count).to eq 100
140
+ expect(Time.now - @time).to be < 0.1
141
+
142
+ expect(@counter.slow_query).to eq 100
143
+ expect(Time.now - @time).to be > 0.4
144
+ end
145
+
146
+ it "ignores the response from a query and returns immediately" do
147
+ @counter = Counter.start "async counter", initial_value: 100
148
+ @time = Time.now
149
+
150
+ expect(@counter.slow_query(ignore_result: true)).to be_nil
151
+
152
+ expect(Time.now - @time).to be < 0.1
153
+ end
154
+
155
+ it "sends all commands using fibers without waiting for the response" do
156
+ @counter = Counter.start "async counter", initial_value: 100
157
+ @time = Time.now
158
+
159
+ @counter.slowly_increment
160
+ expect(Time.now - @time).to be < 0.1
161
+
162
+ # wait for the async task to complete
163
+ expect(@counter.count).to become_equal_to { 101 }
164
+ expect(Time.now - @time).to be > 0.4
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Plumbing do
4
+ it "has a version number" do
5
+ expect(Plumbing::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "plumbing"
4
+ require_relative "become_equal_to_matcher"
5
+
6
+ RSpec.configure do |config|
7
+ # Enable flags like --only-failures and --next-failure
8
+ config.example_status_persistence_file_path = ".rspec_status"
9
+
10
+ # Disable RSpec exposing methods globally on `Module` and `main`
11
+ config.disable_monkey_patching!
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-procedure-plumbing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-28 00:00:00.000000000 Z
11
+ date: 2024-09-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A composable event pipeline and sequential pipelines of operations
14
14
  email:
@@ -17,22 +17,8 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - ".rspec"
21
- - ".rubocop.yml"
22
- - ".solargraph.yml"
23
- - ".standard.yml"
24
- - ".vscode/tasks.json"
25
- - CHANGELOG.md
26
- - CODE_OF_CONDUCT.md
27
- - LICENSE
28
20
  - README.md
29
21
  - Rakefile
30
- - checksums/standard-procedure-plumbing-0.1.1.gem.sha512
31
- - checksums/standard-procedure-plumbing-0.1.2.gem.sha512
32
- - checksums/standard-procedure-plumbing-0.2.0.gem.sha512
33
- - checksums/standard-procedure-plumbing-0.2.1.gem.sha512
34
- - checksums/standard-procedure-plumbing-0.2.2.gem.sha512
35
- - checksums/standard-procedure-plumbing-0.3.0.gem.sha512
36
22
  - lib/plumbing.rb
37
23
  - lib/plumbing/config.rb
38
24
  - lib/plumbing/custom_filter.rb
@@ -53,7 +39,21 @@ files:
53
39
  - lib/plumbing/valve/inline.rb
54
40
  - lib/plumbing/valve/message.rb
55
41
  - lib/plumbing/version.rb
56
- - sig/plumbing.rbs
42
+ - spec/become_equal_to_matcher.rb
43
+ - spec/examples/pipe_spec.rb
44
+ - spec/examples/pipeline_spec.rb
45
+ - spec/examples/rubber_duck_spec.rb
46
+ - spec/examples/valve_spec.rb
47
+ - spec/plumbing/a_pipe.rb
48
+ - spec/plumbing/custom_filter_spec.rb
49
+ - spec/plumbing/filter_spec.rb
50
+ - spec/plumbing/junction_spec.rb
51
+ - spec/plumbing/pipe_spec.rb
52
+ - spec/plumbing/pipeline_spec.rb
53
+ - spec/plumbing/rubber_duck_spec.rb
54
+ - spec/plumbing/valve_spec.rb
55
+ - spec/plumbing_spec.rb
56
+ - spec/spec_helper.rb
57
57
  homepage: https://github.com/standard-procedure/plumbing
58
58
  licenses: []
59
59
  metadata:
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,24 +0,0 @@
1
- # We want Exclude directives from different
2
- # config files to get merged, not overwritten
3
- inherit_mode:
4
- merge:
5
- - Exclude
6
-
7
- require:
8
- # Standard's config uses custom cops,
9
- # so it must be loaded along with custom Standard gems
10
- - standard
11
- - standard-custom
12
- - standard-performance
13
- # rubocop-performance is required when using Performance cops
14
- - rubocop-performance
15
-
16
- inherit_gem:
17
- standard: config/base.yml
18
- standard-performance: config/base.yml
19
- standard-custom: config/base.yml
20
-
21
- # Global options, like Ruby version
22
- AllCops:
23
- SuggestExtensions: false
24
- TargetRubyVersion: 3.2
data/.solargraph.yml DELETED
@@ -1,32 +0,0 @@
1
- ---
2
- include:
3
- - "**/*.rb"
4
- exclude:
5
- - spec/**/*
6
- - test/**/*
7
- - vendor/**/*
8
- - ".bundle/**/*"
9
- require:
10
- - actioncable
11
- - actionmailer
12
- - actionpack
13
- - actionview
14
- - activejob
15
- - activemodel
16
- - activerecord
17
- - activestorage
18
- - activesupport
19
- domains: []
20
- reporters:
21
- - rubocop
22
- - require_not_found
23
- formatter:
24
- rubocop:
25
- cops: safe
26
- except: []
27
- only: []
28
- extra_args: []
29
- require_paths: []
30
- plugins:
31
- - solargraph-rails
32
- max_files: 5000
data/.standard.yml DELETED
@@ -1,9 +0,0 @@
1
- fix: true
2
- parallel: true
3
- format: progress
4
- default_ignores: true
5
-
6
- ignore:
7
- - 'vendor/**/*'
8
- - 'Gemfile.lock'
9
-
data/.vscode/tasks.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "version": "2.0.0",
3
- "tasks": [
4
- {
5
- "label": "rspec",
6
- "type": "shell",
7
- "command": "rspec",
8
- "problemMatcher": []
9
- }
10
- ]
11
- }
data/CHANGELOG.md DELETED
@@ -1,36 +0,0 @@
1
- ## [0.3.0] - 2024-08-28
2
-
3
- - Added Plumbing::Valve
4
- - Reimplemented Plumbing::Pipe to use Plumbing::Valve
5
-
6
- ## [0.2.2] - 2024-08-25
7
-
8
- - Added Plumbing::RubberDuck
9
-
10
- ## [0.2.1] - 2024-08-25
11
-
12
- - Split the Pipe implementation between the Pipe and EventDispatcher
13
- - Use different EventDispatchers to handle fibers or inline pipes
14
- - Renamed Chain to Pipeline
15
-
16
- ## [0.2.0] - 2024-08-14
17
-
18
- - Added optional Dry::Validation support
19
- - Use Async for fiber-based pipes
20
-
21
- ## [0.1.2] - 2024-08-14
22
-
23
- - Removed dependencies
24
- - Removed Ractor-based concurrent pipe (as I don't trust it yet)
25
-
26
- ## [0.1.1] - 2024-08-14
27
-
28
- - Tidied up the code
29
- - Added Plumbing::Chain
30
-
31
- ## [0.1.0] - 2024-04-13
32
-
33
- - Initial release
34
-
35
- ## [Unreleased]
36
-
data/CODE_OF_CONDUCT.md DELETED
@@ -1,5 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- **BE NICE**
4
-
5
- If people think you're being a dick, you're probably being a dick, so stop it.