standard-procedure-plumbing 0.3.0 → 0.3.2

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