teckel 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +53 -0
- data/LICENSE_LOGO +4 -0
- data/README.md +4 -4
- data/lib/teckel.rb +9 -3
- data/lib/teckel/chain.rb +99 -271
- data/lib/teckel/chain/result.rb +38 -0
- data/lib/teckel/chain/runner.rb +51 -0
- data/lib/teckel/chain/step.rb +18 -0
- data/lib/teckel/config.rb +1 -23
- data/lib/teckel/contracts.rb +19 -0
- data/lib/teckel/operation.rb +309 -215
- data/lib/teckel/operation/result.rb +92 -0
- data/lib/teckel/operation/runner.rb +70 -0
- data/lib/teckel/result.rb +52 -53
- data/lib/teckel/version.rb +1 -1
- data/spec/chain/inheritance_spec.rb +116 -0
- data/spec/chain/results_spec.rb +53 -0
- data/spec/chain_around_hook_spec.rb +100 -0
- data/spec/chain_spec.rb +180 -0
- data/spec/config_spec.rb +26 -0
- data/spec/doctest_helper.rb +7 -0
- data/spec/operation/inheritance_spec.rb +94 -0
- data/spec/operation/result_spec.rb +34 -0
- data/spec/operation/results_spec.rb +117 -0
- data/spec/operation_spec.rb +485 -0
- data/spec/rb27/pattern_matching_spec.rb +193 -0
- data/spec/result_spec.rb +20 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/dry_base.rb +8 -0
- data/spec/support/fake_db.rb +12 -0
- data/spec/support/fake_models.rb +20 -0
- data/spec/teckel_spec.rb +7 -0
- metadata +52 -25
- data/.codeclimate.yml +0 -3
- data/.github/workflows/ci.yml +0 -92
- data/.github/workflows/pages.yml +0 -50
- data/.gitignore +0 -15
- data/.rspec +0 -3
- data/.rubocop.yml +0 -12
- data/.ruby-version +0 -1
- data/DEVELOPMENT.md +0 -32
- data/Gemfile +0 -16
- data/Rakefile +0 -35
- data/bin/console +0 -15
- data/bin/rake +0 -29
- data/bin/rspec +0 -29
- data/bin/rubocop +0 -18
- data/bin/setup +0 -8
- data/lib/teckel/none.rb +0 -18
- data/lib/teckel/operation/results.rb +0 -72
- data/teckel.gemspec +0 -32
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'support/dry_base'
|
4
|
+
require 'support/fake_db'
|
5
|
+
require 'support/fake_models'
|
6
|
+
|
7
|
+
RSpec.describe Teckel::Chain do
|
8
|
+
module TeckelChainAroundHookTest
|
9
|
+
class CreateUser
|
10
|
+
include ::Teckel::Operation
|
11
|
+
|
12
|
+
result!
|
13
|
+
|
14
|
+
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
|
15
|
+
output Types.Instance(User)
|
16
|
+
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
17
|
+
|
18
|
+
def call(input)
|
19
|
+
user = User.new(name: input[:name], age: input[:age])
|
20
|
+
if user.save
|
21
|
+
success!(user)
|
22
|
+
else
|
23
|
+
fail!(message: "Could not safe User", errors: user.errors)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class AddFriend
|
29
|
+
include ::Teckel::Operation
|
30
|
+
|
31
|
+
result!
|
32
|
+
|
33
|
+
settings Struct.new(:fail_befriend)
|
34
|
+
|
35
|
+
input Types.Instance(User)
|
36
|
+
output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
|
37
|
+
error Types::Hash.schema(message: Types::String)
|
38
|
+
|
39
|
+
def call(user)
|
40
|
+
if settings&.fail_befriend
|
41
|
+
fail!(message: "Did not find a friend.")
|
42
|
+
else
|
43
|
+
{ user: user, friend: User.new(name: "A friend", age: 42) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@stack = []
|
49
|
+
def self.stack
|
50
|
+
@stack
|
51
|
+
end
|
52
|
+
|
53
|
+
class Chain
|
54
|
+
include Teckel::Chain
|
55
|
+
|
56
|
+
around ->(chain, input) {
|
57
|
+
result = nil
|
58
|
+
begin
|
59
|
+
TeckelChainAroundHookTest.stack << :before
|
60
|
+
|
61
|
+
FakeDB.transaction do
|
62
|
+
result = chain.call(input)
|
63
|
+
raise FakeDB::Rollback if result.failure?
|
64
|
+
end
|
65
|
+
|
66
|
+
TeckelChainAroundHookTest.stack << :after
|
67
|
+
result
|
68
|
+
rescue FakeDB::Rollback
|
69
|
+
result
|
70
|
+
end
|
71
|
+
}
|
72
|
+
|
73
|
+
step :create, CreateUser
|
74
|
+
step :befriend, AddFriend
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
before { TeckelChainAroundHookTest.stack.clear }
|
79
|
+
|
80
|
+
context "success" do
|
81
|
+
it "result matches" do
|
82
|
+
result = TeckelChainAroundHookTest::Chain.call(name: "Bob", age: 23)
|
83
|
+
expect(result.success).to include(user: kind_of(User), friend: kind_of(User))
|
84
|
+
end
|
85
|
+
|
86
|
+
it "runs around hook" do
|
87
|
+
TeckelChainAroundHookTest::Chain.call(name: "Bob", age: 23)
|
88
|
+
expect(TeckelChainAroundHookTest.stack).to eq([:before, :after])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "failure" do
|
93
|
+
it "runs around hook" do
|
94
|
+
TeckelChainAroundHookTest::Chain.
|
95
|
+
with(befriend: :fail).
|
96
|
+
call(name: "Bob", age: 23)
|
97
|
+
expect(TeckelChainAroundHookTest.stack).to eq([:before])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/spec/chain_spec.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'support/dry_base'
|
4
|
+
require 'support/fake_models'
|
5
|
+
|
6
|
+
RSpec.describe Teckel::Chain do
|
7
|
+
module TeckelChainTest
|
8
|
+
class CreateUser
|
9
|
+
include ::Teckel::Operation
|
10
|
+
result!
|
11
|
+
|
12
|
+
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
|
13
|
+
output Types.Instance(User)
|
14
|
+
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
15
|
+
|
16
|
+
def call(input)
|
17
|
+
user = User.new(name: input[:name], age: input[:age])
|
18
|
+
if user.save
|
19
|
+
success!(user)
|
20
|
+
else
|
21
|
+
fail!(message: "Could not save User", errors: user.errors)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class LogUser
|
27
|
+
include ::Teckel::Operation
|
28
|
+
|
29
|
+
result!
|
30
|
+
|
31
|
+
input Types.Instance(User)
|
32
|
+
error none
|
33
|
+
output input
|
34
|
+
|
35
|
+
def call(usr)
|
36
|
+
Logger.new(File::NULL).info("User #{usr.name} created")
|
37
|
+
usr
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class AddFriend
|
42
|
+
include ::Teckel::Operation
|
43
|
+
|
44
|
+
result!
|
45
|
+
|
46
|
+
settings Struct.new(:fail_befriend)
|
47
|
+
|
48
|
+
input Types.Instance(User)
|
49
|
+
output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
|
50
|
+
error Types::Hash.schema(message: Types::String)
|
51
|
+
|
52
|
+
def call(user)
|
53
|
+
if settings&.fail_befriend
|
54
|
+
fail!(message: "Did not find a friend.")
|
55
|
+
else
|
56
|
+
{ user: user, friend: User.new(name: "A friend", age: 42) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Chain
|
62
|
+
include Teckel::Chain
|
63
|
+
|
64
|
+
step :create, CreateUser
|
65
|
+
step :log, LogUser
|
66
|
+
step :befriend, AddFriend
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'Chain input points to first step input' do
|
71
|
+
expect(TeckelChainTest::Chain.input).to eq(TeckelChainTest::CreateUser.input)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'Chain output points to last steps output' do
|
75
|
+
expect(TeckelChainTest::Chain.output).to eq(TeckelChainTest::AddFriend.output)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'Chain errors maps all step errors' do
|
79
|
+
expect(TeckelChainTest::Chain.errors).to eq([
|
80
|
+
TeckelChainTest::CreateUser.error,
|
81
|
+
Teckel::Contracts::None,
|
82
|
+
TeckelChainTest::AddFriend.error
|
83
|
+
])
|
84
|
+
end
|
85
|
+
|
86
|
+
context "success" do
|
87
|
+
it "result matches" do
|
88
|
+
result =
|
89
|
+
TeckelChainTest::Chain.
|
90
|
+
with(befriend: nil).
|
91
|
+
call(name: "Bob", age: 23)
|
92
|
+
|
93
|
+
expect(result.success).to include(user: kind_of(User), friend: kind_of(User))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "failure" do
|
98
|
+
it "returns a Result for invalid input" do
|
99
|
+
result =
|
100
|
+
TeckelChainTest::Chain.
|
101
|
+
with(befriend: :fail).
|
102
|
+
call(name: "Bob", age: 0)
|
103
|
+
|
104
|
+
expect(result).to be_a(Teckel::Chain::Result)
|
105
|
+
expect(result).to be_failure
|
106
|
+
expect(result.step).to eq(:create)
|
107
|
+
expect(result.value).to eq(errors: [{ age: "underage" }], message: "Could not save User")
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns a Result for failed step" do
|
111
|
+
result =
|
112
|
+
TeckelChainTest::Chain.
|
113
|
+
with(befriend: :fail).
|
114
|
+
call(name: "Bob", age: 23)
|
115
|
+
|
116
|
+
expect(result).to be_a(Teckel::Chain::Result)
|
117
|
+
expect(result).to be_failure
|
118
|
+
expect(result.step).to eq(:befriend)
|
119
|
+
expect(result.value).to eq(message: "Did not find a friend.")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#finalize!" do
|
124
|
+
let(:frozen_error) do
|
125
|
+
# different ruby versions raise different errors
|
126
|
+
defined?(FrozenError) ? FrozenError : RuntimeError
|
127
|
+
end
|
128
|
+
|
129
|
+
subject { TeckelChainTest::Chain.dup }
|
130
|
+
|
131
|
+
it "freezes the Chain class and operation classes" do
|
132
|
+
subject.finalize!
|
133
|
+
|
134
|
+
steps = subject.steps
|
135
|
+
expect(steps).to be_frozen
|
136
|
+
expect(steps).to all be_frozen
|
137
|
+
end
|
138
|
+
|
139
|
+
it "disallows adding new steps" do
|
140
|
+
subject.class_eval do
|
141
|
+
step :other, TeckelChainTest::AddFriend
|
142
|
+
end
|
143
|
+
|
144
|
+
subject.finalize!
|
145
|
+
|
146
|
+
expect {
|
147
|
+
subject.class_eval do
|
148
|
+
step :yet_other, TeckelChainTest::AddFriend
|
149
|
+
end
|
150
|
+
}.to raise_error(frozen_error)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "disallows changing around hook" do
|
154
|
+
subject.class_eval do
|
155
|
+
around ->{}
|
156
|
+
end
|
157
|
+
|
158
|
+
chain2 = TeckelChainTest::Chain.dup.finalize!
|
159
|
+
expect {
|
160
|
+
chain2.class_eval do
|
161
|
+
around ->{}
|
162
|
+
end
|
163
|
+
}.to raise_error(frozen_error)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "runs" do
|
167
|
+
subject.finalize!
|
168
|
+
|
169
|
+
result = subject.call(name: "Bob", age: 23)
|
170
|
+
expect(result.success).to include(user: kind_of(User), friend: kind_of(User))
|
171
|
+
end
|
172
|
+
|
173
|
+
it "accepts mocks" do
|
174
|
+
subject.finalize!
|
175
|
+
|
176
|
+
allow(subject).to receive(:call) { :mocked }
|
177
|
+
expect(subject.call).to eq(:mocked)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'support/dry_base'
|
4
|
+
require 'support/fake_models'
|
5
|
+
|
6
|
+
RSpec.describe Teckel::Config do
|
7
|
+
let(:sample_config) do
|
8
|
+
Teckel::Config.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it "set and retrieve key" do
|
12
|
+
sample_config.for(:some_key, "some_value")
|
13
|
+
expect(sample_config.for(:some_key)).to eq("some_value")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allow default value via block" do
|
17
|
+
expect(sample_config.for(:some_key) { "default" }).to eq("default")
|
18
|
+
# and sets the block value
|
19
|
+
expect(sample_config.for(:some_key)).to eq("default")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "raises FrozenConfigError when setting a key twice" do
|
23
|
+
sample_config.for(:some_key, "some_value")
|
24
|
+
expect { sample_config.for(:some_key, "other_value") }.to raise_error(Teckel::FrozenConfigError)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Teckel::Operation do
|
4
|
+
context "default settings via base class" do
|
5
|
+
module TeckelOperationDefaultsViaBaseClass
|
6
|
+
DefaultError = Struct.new(:message, :status_code)
|
7
|
+
Settings = Struct.new(:fail_it)
|
8
|
+
|
9
|
+
class ApplicationOperation
|
10
|
+
include Teckel::Operation
|
11
|
+
|
12
|
+
settings Settings
|
13
|
+
settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
|
14
|
+
|
15
|
+
error DefaultError
|
16
|
+
error_constructor ->(data) { error.new(*data.values_at(*error.members)) }
|
17
|
+
|
18
|
+
result!
|
19
|
+
|
20
|
+
# Freeze the base class to make sure it's inheritable configuration is not altered
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
class OperationA < ApplicationOperation
|
25
|
+
input Struct.new(:input_data_a)
|
26
|
+
output Struct.new(:output_data_a)
|
27
|
+
|
28
|
+
def call(input)
|
29
|
+
if settings&.fail_it
|
30
|
+
fail!(message: settings.fail_it, status_code: 400)
|
31
|
+
else
|
32
|
+
input.input_data_a * 2
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
finalize!
|
37
|
+
end
|
38
|
+
|
39
|
+
class OperationB < ApplicationOperation
|
40
|
+
input Struct.new(:input_data_b)
|
41
|
+
output Struct.new(:output_data_b)
|
42
|
+
|
43
|
+
def call(input)
|
44
|
+
if settings&.fail_it
|
45
|
+
fail!(message: settings.fail_it, status_code: 500)
|
46
|
+
else
|
47
|
+
input.input_data_b * 4
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
finalize!
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
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
|
@@ -0,0 +1,34 @@
|
|
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 error" }
|
8
|
+
let(:successful_result) { Teckel::Operation::Result.new(failure_value, true) }
|
9
|
+
|
10
|
+
it { expect(successful_result.successful?).to be(true) }
|
11
|
+
it { expect(failed_result.successful?).to be(false) }
|
12
|
+
|
13
|
+
it { expect(successful_result.failure?).to be(false) }
|
14
|
+
it { expect(failed_result.failure?).to be(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 { expect(successful_result.success).to eq(success_value) }
|
21
|
+
|
22
|
+
it { expect(failed_result.success).to eq(nil) }
|
23
|
+
it { expect(failed_result.success("other")).to eq("other") }
|
24
|
+
it { expect(failed_result.success { |value| "Failed: #{value}" } ).to eq("Failed: some error") }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#failure" do
|
28
|
+
it { expect(failed_result.failure).to eq(failure_value) }
|
29
|
+
|
30
|
+
it { expect(successful_result.failure).to eq(nil) }
|
31
|
+
it { expect(successful_result.failure("other")).to eq("other") }
|
32
|
+
it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some error") }
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'support/dry_base'
|
4
|
+
require 'support/fake_models'
|
5
|
+
|
6
|
+
RSpec.describe Teckel::Operation do
|
7
|
+
context "with build in result object" do
|
8
|
+
class CreateUserWithResult
|
9
|
+
include Teckel::Operation
|
10
|
+
|
11
|
+
result!
|
12
|
+
|
13
|
+
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
|
14
|
+
output Types.Instance(User)
|
15
|
+
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
16
|
+
|
17
|
+
def call(input)
|
18
|
+
user = User.new(name: input[:name], age: input[:age])
|
19
|
+
if user.save
|
20
|
+
user
|
21
|
+
else
|
22
|
+
fail!(message: "Could not save User", errors: user.errors)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "output" do
|
28
|
+
result = CreateUserWithResult.call(name: "Bob", age: 23)
|
29
|
+
expect(result).to be_a(Teckel::Result)
|
30
|
+
expect(result).to be_successful
|
31
|
+
expect(result.success).to be_a(User)
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "errors" do
|
35
|
+
result = CreateUserWithResult.call(name: "Bob", age: 10)
|
36
|
+
expect(result).to be_a(Teckel::Result)
|
37
|
+
expect(result).to be_failure
|
38
|
+
expect(result.failure).to eq(message: "Could not save User", errors: [{ age: "underage" }])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "using custom result" do
|
43
|
+
class CreateUserCustomResult
|
44
|
+
include Teckel::Operation
|
45
|
+
|
46
|
+
class MyResult
|
47
|
+
include Teckel::Result # makes sure this can be used in a Chain
|
48
|
+
|
49
|
+
def initialize(value, success, opts = {})
|
50
|
+
@value, @success, @opts = value, success, opts
|
51
|
+
end
|
52
|
+
|
53
|
+
# implementing Teckel::Result
|
54
|
+
def successful?
|
55
|
+
@success
|
56
|
+
end
|
57
|
+
|
58
|
+
# implementing Teckel::Result
|
59
|
+
attr_reader :value
|
60
|
+
|
61
|
+
attr_reader :opts
|
62
|
+
end
|
63
|
+
|
64
|
+
result MyResult
|
65
|
+
result_constructor ->(value, success) { result.new(value, success, time: Time.now.to_i) }
|
66
|
+
|
67
|
+
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
|
68
|
+
output Types.Instance(User)
|
69
|
+
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
70
|
+
|
71
|
+
def call(input)
|
72
|
+
user = User.new(name: input[:name], age: input[:age])
|
73
|
+
if user.save
|
74
|
+
user
|
75
|
+
else
|
76
|
+
fail!(message: "Could not save User", errors: user.errors)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
specify "output" do
|
82
|
+
result = CreateUserCustomResult.call(name: "Bob", age: 23)
|
83
|
+
expect(result).to be_a(CreateUserCustomResult::MyResult)
|
84
|
+
expect(result).to be_successful
|
85
|
+
expect(result.value).to be_a(User)
|
86
|
+
|
87
|
+
expect(result.opts).to include(time: kind_of(Integer))
|
88
|
+
end
|
89
|
+
|
90
|
+
specify "errors" do
|
91
|
+
result = CreateUserCustomResult.call(name: "Bob", age: 10)
|
92
|
+
expect(result).to be_a(CreateUserCustomResult::MyResult)
|
93
|
+
expect(result).to be_failure
|
94
|
+
expect(result.value).to eq(message: "Could not save User", errors: [{ age: "underage" }])
|
95
|
+
|
96
|
+
expect(result.opts).to include(time: kind_of(Integer))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "overwriting Result" do
|
101
|
+
class CreateUserOverwritingResult
|
102
|
+
include Teckel::Operation
|
103
|
+
|
104
|
+
class Result
|
105
|
+
include Teckel::Result # makes sure this can be used in a Chain
|
106
|
+
|
107
|
+
def initialize(value, success); end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
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
|