teckel 0.8.0 → 0.9.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.
@@ -1,120 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TeckelOperationDefaultSettings
4
- class BaseOperation
5
- include ::Teckel::Operation
6
-
7
- input none
8
- output Symbol
9
- error none
10
-
11
- def call(_input)
12
- success! settings.injected
13
- end
14
- end
15
- end
16
-
17
- RSpec.describe Teckel::Operation do
18
- context "default settings" do
19
- shared_examples "operation with default settings" do |operation|
20
- subject { operation }
21
-
22
- it "with no settings" do
23
- expect(subject.call).to eq(:default_value)
24
- end
25
-
26
- it "with settings" do
27
- expect(subject.with(:injected_value).call).to eq(:injected_value)
28
- end
29
- end
30
-
31
- describe "with default constructor and clever Settings class" do
32
- it_behaves_like(
33
- "operation with default settings",
34
- Class.new(TeckelOperationDefaultSettings::BaseOperation) do
35
- settings(Class.new do
36
- def initialize(injected = nil)
37
- @injected = injected
38
- end
39
-
40
- def injected
41
- @injected || :default_value
42
- end
43
-
44
- class << self
45
- alias :[] :new # make us respond to the default constructor
46
- end
47
- end)
48
-
49
- default_settings!
50
- end
51
- )
52
- end
53
-
54
- describe "with custom constructor and clever Settings class" do
55
- it_behaves_like(
56
- "operation with default settings",
57
- Class.new(TeckelOperationDefaultSettings::BaseOperation) do
58
- settings(Class.new do
59
- def initialize(injected = nil)
60
- @injected = injected
61
- end
62
-
63
- def injected
64
- @injected || :default_value
65
- end
66
- end)
67
-
68
- settings_constructor :new
69
- default_settings!
70
- end
71
- )
72
- end
73
-
74
- describe "with default constructor and simple Settings class" do
75
- it_behaves_like(
76
- "operation with default settings",
77
- Class.new(TeckelOperationDefaultSettings::BaseOperation) do
78
- settings Struct.new(:injected)
79
-
80
- default_settings! -> { settings.new(:default_value) }
81
- end
82
- )
83
-
84
- it_behaves_like(
85
- "operation with default settings",
86
- Class.new(TeckelOperationDefaultSettings::BaseOperation) do
87
- settings Struct.new(:injected)
88
-
89
- default_settings!(:default_value)
90
- end
91
- )
92
-
93
- it_behaves_like(
94
- "operation with default settings",
95
- Class.new(TeckelOperationDefaultSettings::BaseOperation) do
96
- settings Struct.new(:injected)
97
-
98
- default_settings!("default_value")
99
-
100
- output_constructor ->(out) { out&.to_sym }
101
- end
102
- )
103
- end
104
-
105
- describe "with default constructor and simple Settings class responding to passed default setting" do
106
- it_behaves_like(
107
- "operation with default settings",
108
- Class.new(TeckelOperationDefaultSettings::BaseOperation) do
109
- settings(Struct.new(:injected) do
110
- def self.default
111
- new(:default_value)
112
- end
113
- end)
114
-
115
- default_settings!(:default)
116
- end
117
- )
118
- end
119
- end
120
- end
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry/validation"
4
- require 'support/dry_base'
5
- require 'support/fake_models'
6
-
7
- module TeckelOperationFailOnOInput
8
- class NewUserContract < Dry::Validation::Contract
9
- schema do
10
- required(:name).filled(:string)
11
- required(:age).value(:integer)
12
- end
13
- end
14
-
15
- class CreateUser
16
- include Teckel::Operation
17
-
18
- result!
19
-
20
- input(NewUserContract.new)
21
- input_constructor(->(input){
22
- result = self.class.input.call(input)
23
- if result.success?
24
- result.to_h
25
- else
26
- fail!(message: "Input data validation failed", errors: [result.errors.to_h])
27
- end
28
- })
29
-
30
- output Types.Instance(User)
31
- error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
32
-
33
- def call(input)
34
- user = User.new(name: input[:name], age: input[:age])
35
- if user.save
36
- success! user
37
- else
38
- fail!(message: "Could not save User", errors: user.errors)
39
- end
40
- end
41
-
42
- finalize!
43
- end
44
-
45
- class CreateUserIncorrectFailure
46
- include Teckel::Operation
47
-
48
- result!
49
-
50
- input(->(input) { input }) # NoOp
51
- input_constructor(->(_input) {
52
- fail!("Input data validation failed")
53
- })
54
-
55
- output none
56
- error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
57
-
58
- def call(_); end
59
- finalize!
60
- end
61
- end
62
-
63
- RSpec.describe Teckel::Operation do
64
- specify "successs" do
65
- result = TeckelOperationFailOnOInput::CreateUser.call(name: "Bob", age: 23)
66
- expect(result).to be_successful
67
- expect(result.success).to be_a(User)
68
- end
69
-
70
- describe "failing in input_constructor" do
71
- let(:failure_input) do
72
- { name: "", age: "incorrect type" }
73
- end
74
-
75
- it "returns the failure thrown in input_constructor" do
76
- result = TeckelOperationFailOnOInput::CreateUser.call(failure_input)
77
- expect(result).to be_a(Teckel::Operation::Result)
78
- expect(result).to be_failure
79
- expect(result.failure).to eq(
80
- message: "Input data validation failed",
81
- errors: [
82
- { name: ["must be filled"], age: ["must be an integer"] }
83
- ]
84
- )
85
- end
86
-
87
- it "does not run .call" do
88
- expect(TeckelOperationFailOnOInput::CreateUser).to receive(:new).and_wrap_original do |m, *args|
89
- op_instance = m.call(*args)
90
- expect(op_instance).to_not receive(:call)
91
- op_instance
92
- end
93
-
94
- TeckelOperationFailOnOInput::CreateUser.call(failure_input)
95
- end
96
- end
97
-
98
- specify "thrown failure needs to conform to :error" do
99
- expect {
100
- TeckelOperationFailOnOInput::CreateUserIncorrectFailure.call(name: "Bob", age: 23)
101
- }.to raise_error(Dry::Types::ConstraintError, /violates constraints/)
102
- end
103
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TeckelOperationDefaultsViaBaseClass
4
- DefaultError = Struct.new(:message, :status_code)
5
- Settings = Struct.new(:fail_it)
6
-
7
- class ApplicationOperation
8
- include Teckel::Operation
9
-
10
- settings Settings
11
- settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
12
-
13
- error DefaultError
14
- error_constructor ->(data) { error.new(*data.values_at(*error.members)) }
15
-
16
- result!
17
-
18
- # Freeze the base class to make sure it's inheritable configuration is not altered
19
- freeze
20
- end
21
-
22
- class OperationA < ApplicationOperation
23
- input Struct.new(:input_data_a)
24
- output Struct.new(:output_data_a)
25
-
26
- def call(input)
27
- if settings&.fail_it
28
- fail!(message: settings.fail_it, status_code: 400)
29
- else
30
- success!(input.input_data_a * 2)
31
- end
32
- end
33
-
34
- finalize!
35
- end
36
-
37
- class OperationB < ApplicationOperation
38
- input Struct.new(:input_data_b)
39
- output Struct.new(:output_data_b)
40
-
41
- def call(input)
42
- if settings&.fail_it
43
- fail!(message: settings.fail_it, status_code: 500)
44
- else
45
- success!(input.input_data_b * 4)
46
- end
47
- end
48
-
49
- finalize!
50
- end
51
- end
52
-
53
- RSpec.describe Teckel::Operation do
54
- context "default settings via base class" do
55
- let(:operation_a) { TeckelOperationDefaultsViaBaseClass::OperationA }
56
- let(:operation_b) { TeckelOperationDefaultsViaBaseClass::OperationB }
57
-
58
- it "inherits config" do
59
- expect(operation_a.result).to eq(Teckel::Operation::Result)
60
- expect(operation_a.settings).to eq(TeckelOperationDefaultsViaBaseClass::Settings)
61
-
62
- expect(operation_b.result).to eq(Teckel::Operation::Result)
63
- expect(operation_b.settings).to eq(TeckelOperationDefaultsViaBaseClass::Settings)
64
- end
65
-
66
- context "operation_a" do
67
- it "can run" do
68
- result = operation_a.call(10)
69
- expect(result.success.to_h).to eq(output_data_a: 20)
70
- end
71
-
72
- it "can fail" do
73
- result = operation_a.with(fail_it: "D'oh!").call(10)
74
- expect(result.failure.to_h).to eq(
75
- message: "D'oh!", status_code: 400
76
- )
77
- end
78
- end
79
-
80
- context "operation_b" do
81
- it "can run" do
82
- result = operation_b.call(10)
83
- expect(result.success.to_h).to eq(output_data_b: 40)
84
- end
85
-
86
- it "can fail" do
87
- result = operation_b.with(fail_it: "D'oh!").call(10)
88
- expect(result.failure.to_h).to eq(
89
- message: "D'oh!", status_code: 500
90
- )
91
- end
92
- end
93
- end
94
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Teckel::Operation::Result do
4
- let(:failure_value) { "some error" }
5
- let(:failed_result) { Teckel::Operation::Result.new(failure_value, false) }
6
-
7
- let(:success_value) { "some success" }
8
- let(:successful_result) { Teckel::Operation::Result.new(success_value, true) }
9
-
10
- it { expect(successful_result.successful?).to eq(true) }
11
- it { expect(failed_result.successful?).to eq(false) }
12
-
13
- it { expect(successful_result.failure?).to eq(false) }
14
- it { expect(failed_result.failure?).to eq(true) }
15
-
16
- it { expect(successful_result.value).to eq(success_value) }
17
- it { expect(failed_result.value).to eq(failure_value) }
18
-
19
- describe "#success" do
20
- it("on successful result, returns value") {
21
- expect(successful_result.success).to eq(success_value)
22
- }
23
-
24
- describe "on failed result" do
25
- it("with no fallbacks, returns nil") {
26
- expect(failed_result.success).to eq(nil)
27
- }
28
- it("with default-argument, returns default-argument") {
29
- expect(failed_result.success("other")).to eq("other")
30
- }
31
- it("with block, returns block return value") {
32
- expect(failed_result.success { |value| "Failed: #{value}" } ).to eq("Failed: some error")
33
- }
34
- it("with default-argument and block given, returns default-argument, skips block") {
35
- expect { |blk|
36
- expect(failed_result.success("default", &blk)).to_not eq("default")
37
- }.to(yield_control)
38
- }
39
- end
40
- end
41
-
42
- describe "#failure" do
43
- it { expect(failed_result.failure).to eq(failure_value) }
44
-
45
- it { expect(successful_result.failure).to eq(nil) }
46
- it { expect(successful_result.failure("other")).to eq("other") }
47
- it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some success") }
48
- end
49
- end
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'support/dry_base'
4
- require 'support/fake_models'
5
-
6
- class CreateUserWithResult
7
- include Teckel::Operation
8
-
9
- result!
10
-
11
- input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
12
- output Types.Instance(User)
13
- error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
14
-
15
- def call(input)
16
- user = User.new(name: input[:name], age: input[:age])
17
- if user.save
18
- success! user
19
- else
20
- fail!(message: "Could not save User", errors: user.errors)
21
- end
22
- end
23
- end
24
-
25
- class CreateUserCustomResult
26
- include Teckel::Operation
27
-
28
- class MyResult
29
- include Teckel::Result # makes sure this can be used in a Chain
30
-
31
- def initialize(value, success, opts = {})
32
- @value, @success, @opts = value, success, opts
33
- end
34
-
35
- # implementing Teckel::Result
36
- def successful?
37
- @success
38
- end
39
-
40
- # implementing Teckel::Result
41
- attr_reader :value
42
-
43
- attr_reader :opts
44
- end
45
-
46
- result MyResult
47
- result_constructor ->(value, success) { MyResult.new(value, success, time: Time.now.to_i) }
48
-
49
- input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
50
- output Types.Instance(User)
51
- error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
52
-
53
- def call(input)
54
- user = User.new(name: input[:name], age: input[:age])
55
- if user.save
56
- success! user
57
- else
58
- fail!(message: "Could not save User", errors: user.errors)
59
- end
60
- end
61
- end
62
-
63
- class CreateUserOverwritingResult
64
- include Teckel::Operation
65
-
66
- class Result
67
- include Teckel::Result # makes sure this can be used in a Chain
68
-
69
- def initialize(value, success); end
70
- end
71
- end
72
-
73
- RSpec.describe Teckel::Operation do
74
- context "with build in result object" do
75
- specify "output" do
76
- result = CreateUserWithResult.call(name: "Bob", age: 23)
77
- expect(result).to be_a(Teckel::Result)
78
- expect(result).to be_successful
79
- expect(result.success).to be_a(User)
80
- end
81
-
82
- specify "errors" do
83
- result = CreateUserWithResult.call(name: "Bob", age: 10)
84
- expect(result).to be_a(Teckel::Result)
85
- expect(result).to be_failure
86
- expect(result.failure).to eq(message: "Could not save User", errors: [{ age: "underage" }])
87
- end
88
- end
89
-
90
- context "using custom result" do
91
- specify "output" do
92
- result = CreateUserCustomResult.call(name: "Bob", age: 23)
93
- expect(result).to be_a(CreateUserCustomResult::MyResult)
94
- expect(result).to be_successful
95
- expect(result.value).to be_a(User)
96
-
97
- expect(result.opts).to include(time: kind_of(Integer))
98
- end
99
-
100
- specify "errors" do
101
- result = CreateUserCustomResult.call(name: "Bob", age: 10)
102
- expect(result).to be_a(CreateUserCustomResult::MyResult)
103
- expect(result).to be_failure
104
- expect(result.value).to eq(message: "Could not save User", errors: [{ age: "underage" }])
105
-
106
- expect(result.opts).to include(time: kind_of(Integer))
107
- end
108
- end
109
-
110
- context "overwriting Result" do
111
- it "uses the class definition" do
112
- expect(CreateUserOverwritingResult.result).to_not eq(Teckel::Operation::Result)
113
- expect(CreateUserOverwritingResult.result).to eq(CreateUserOverwritingResult::Result)
114
- expect(CreateUserOverwritingResult.result_constructor).to eq(CreateUserOverwritingResult::Result.method(:[]))
115
- end
116
- end
117
- end