teckel 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,27 +3,75 @@
3
3
  require 'support/dry_base'
4
4
  require 'support/fake_models'
5
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
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
25
33
  end
26
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) { result.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
27
75
  specify "output" do
28
76
  result = CreateUserWithResult.call(name: "Bob", age: 23)
29
77
  expect(result).to be_a(Teckel::Result)
@@ -40,44 +88,6 @@ RSpec.describe Teckel::Operation do
40
88
  end
41
89
 
42
90
  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
91
  specify "output" do
82
92
  result = CreateUserCustomResult.call(name: "Bob", age: 23)
83
93
  expect(result).to be_a(CreateUserCustomResult::MyResult)
@@ -98,16 +108,6 @@ RSpec.describe Teckel::Operation do
98
108
  end
99
109
 
100
110
  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
111
  it "uses the class definition" do
112
112
  expect(CreateUserOverwritingResult.result).to_not eq(Teckel::Operation::Result)
113
113
  expect(CreateUserOverwritingResult.result).to eq(CreateUserOverwritingResult::Result)
@@ -3,44 +3,233 @@
3
3
  require 'support/dry_base'
4
4
  require 'support/fake_models'
5
5
 
6
- RSpec.describe Teckel::Operation do
7
- context "predefined classes" do
8
- module TeckelOperationPredefinedClassesTest
9
- class CreateUserInput < Dry::Struct
10
- attribute :name, Types::String
11
- attribute :age, Types::Coercible::Integer
6
+ module TeckelOperationPredefinedClassesTest
7
+ class CreateUserInput < Dry::Struct
8
+ attribute :name, Types::String
9
+ attribute :age, Types::Coercible::Integer
10
+ end
11
+
12
+ CreateUserOutput = Types.Instance(User)
13
+
14
+ class CreateUserError < Dry::Struct
15
+ attribute :message, Types::String
16
+ attribute :status_code, Types::Integer
17
+ attribute :meta, Types::Hash.optional
18
+ end
19
+
20
+ class CreateUser
21
+ include Teckel::Operation
22
+
23
+ input CreateUserInput
24
+ output CreateUserOutput
25
+ error CreateUserError
26
+
27
+ def call(input)
28
+ user = User.new(**input.attributes)
29
+ if user.save
30
+ success!(user)
31
+ else
32
+ fail!(
33
+ message: "Could not create User",
34
+ status_code: 400,
35
+ meta: { validation: user.errors }
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module TeckelOperationInlineClassesTest
43
+ class CreateUser
44
+ include Teckel::Operation
45
+
46
+ class Input < Dry::Struct
47
+ attribute :name, Types::String
48
+ attribute :age, Types::Coercible::Integer
49
+ end
50
+
51
+ Output = Types.Instance(User)
52
+
53
+ class Error < Dry::Struct
54
+ attribute :message, Types::String
55
+ attribute :status_code, Types::Integer
56
+ attribute :meta, Types::Hash.optional
57
+ end
58
+
59
+ def call(input)
60
+ user = User.new(**input.attributes)
61
+ if user.save
62
+ success!(user)
63
+ else
64
+ fail!(
65
+ message: "Could not create User",
66
+ status_code: 400,
67
+ meta: { validation: user.errors }
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ module TeckelOperationAnnonClassesTest
75
+ class CreateUser
76
+ include ::Teckel::Operation
77
+
78
+ input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
79
+ output Types.Instance(User)
80
+ error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
81
+
82
+ def call(input)
83
+ user = User.new(name: input[:name], age: input[:age])
84
+ if user.save
85
+ success!(user)
86
+ else
87
+ fail!(message: "Could not save User", errors: user.errors)
12
88
  end
89
+ end
90
+ end
91
+ end
13
92
 
14
- CreateUserOutput = Types.Instance(User)
93
+ module TeckelOperationKeywordContracts
94
+ class MyOperation
95
+ include Teckel::Operation
15
96
 
16
- class CreateUserError < Dry::Struct
17
- attribute :message, Types::String
18
- attribute :status_code, Types::Integer
19
- attribute :meta, Types::Hash.optional
97
+ class Input
98
+ def initialize(name:, age:)
99
+ @name, @age = name, age
20
100
  end
101
+ attr_reader :name, :age
102
+ end
21
103
 
22
- class CreateUser
23
- include Teckel::Operation
104
+ input_constructor ->(data) { input.new(**data) }
24
105
 
25
- input CreateUserInput
26
- output CreateUserOutput
27
- error CreateUserError
106
+ Output = ::User
28
107
 
29
- def call(input)
30
- user = User.new(**input.attributes)
31
- if user.save
32
- success!(user)
33
- else
34
- fail!(
35
- message: "Could not create User",
36
- status_code: 400,
37
- meta: { validation: user.errors }
38
- )
39
- end
108
+ class Error
109
+ def initialize(message, errors)
110
+ @message, @errors = message, errors
111
+ end
112
+ attr_reader :message, :errors
113
+ end
114
+ error_constructor :new
115
+
116
+ def call(input)
117
+ user = ::User.new(name: input.name, age: input.age)
118
+ if user.save
119
+ success!(user)
120
+ else
121
+ fail!(message: "Could not save User", errors: user.errors)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ module TeckelOperationCreateUserSplatInit
128
+ class MyOperation
129
+ include Teckel::Operation
130
+
131
+ input Struct.new(:name, :age)
132
+ input_constructor ->(data) { input.new(*data) }
133
+
134
+ Output = ::User
135
+
136
+ class Error
137
+ def initialize(message, errors)
138
+ @message, @errors = message, errors
139
+ end
140
+ attr_reader :message, :errors
141
+ end
142
+ error_constructor :new
143
+
144
+ def call(input)
145
+ user = ::User.new(name: input.name, age: input.age)
146
+ if user.save
147
+ success!(user)
148
+ else
149
+ fail!(message: "Could not save User", errors: user.errors)
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ module TeckelOperationGeneratedOutputTest
156
+ class MyOperation
157
+ include ::Teckel::Operation
158
+
159
+ input none
160
+ output Struct.new(:some_key)
161
+ output_constructor ->(data) { output.new(*data.values_at(*output.members)) } # ruby 2.4 way for `keyword_init: true`
162
+ error none
163
+
164
+ def call(_input)
165
+ success!(some_key: "some_value")
166
+ end
167
+ end
168
+ end
169
+
170
+ module TeckelOperationNoSettingsTest
171
+ class MyOperation
172
+ include ::Teckel::Operation
173
+
174
+ input none
175
+ output none
176
+ error none
177
+
178
+ def call(_input); end
179
+ end
180
+ MyOperation.finalize!
181
+ end
182
+
183
+ module TeckelOperationNoneDataTest
184
+ class MyOperation
185
+ include ::Teckel::Operation
186
+
187
+ settings Struct.new(:fail_it, :fail_data, :success_it, :success_data)
188
+ settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
189
+
190
+ input none
191
+ output none
192
+ error none
193
+
194
+ def call(_input)
195
+ if settings&.fail_it
196
+ if settings&.fail_data
197
+ fail!(settings.fail_data)
198
+ else
199
+ fail!
40
200
  end
201
+ elsif settings&.success_it
202
+ if settings&.success_data
203
+ success!(settings.success_data)
204
+ else
205
+ success!
206
+ end
207
+ else
208
+ settings&.success_data
41
209
  end
42
210
  end
211
+ end
212
+ end
213
+
214
+ module TeckelOperationInjectSettingsTest
215
+ class MyOperation
216
+ include ::Teckel::Operation
217
+
218
+ settings Struct.new(:injected)
219
+ settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
220
+
221
+ input none
222
+ output Array
223
+ error none
224
+
225
+ def call(_input)
226
+ success!((settings&.injected || []) << :operation_data)
227
+ end
228
+ end
229
+ end
43
230
 
231
+ RSpec.describe Teckel::Operation do
232
+ context "predefined classes" do
44
233
  specify "Input" do
45
234
  expect(TeckelOperationPredefinedClassesTest::CreateUser.input).to eq(TeckelOperationPredefinedClassesTest::CreateUserInput)
46
235
  end
@@ -74,38 +263,6 @@ RSpec.describe Teckel::Operation do
74
263
  end
75
264
 
76
265
  context "inline classes" do
77
- module TeckelOperationInlineClassesTest
78
- class CreateUser
79
- include Teckel::Operation
80
-
81
- class Input < Dry::Struct
82
- attribute :name, Types::String
83
- attribute :age, Types::Coercible::Integer
84
- end
85
-
86
- Output = Types.Instance(User)
87
-
88
- class Error < Dry::Struct
89
- attribute :message, Types::String
90
- attribute :status_code, Types::Integer
91
- attribute :meta, Types::Hash.optional
92
- end
93
-
94
- def call(input)
95
- user = User.new(**input.attributes)
96
- if user.save
97
- user
98
- else
99
- fail!(
100
- message: "Could not create User",
101
- status_code: 400,
102
- meta: { validation: user.errors }
103
- )
104
- end
105
- end
106
- end
107
- end
108
-
109
266
  specify "Input" do
110
267
  expect(TeckelOperationInlineClassesTest::CreateUser.input).to be <= Dry::Struct
111
268
  end
@@ -138,25 +295,6 @@ RSpec.describe Teckel::Operation do
138
295
  end
139
296
 
140
297
  context "annon classes" do
141
- module TeckelOperationAnnonClassesTest
142
- class CreateUser
143
- include ::Teckel::Operation
144
-
145
- input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
146
- output Types.Instance(User)
147
- error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
148
-
149
- def call(input)
150
- user = User.new(name: input[:name], age: input[:age])
151
- if user.save
152
- user
153
- else
154
- fail!(message: "Could not save User", errors: user.errors)
155
- end
156
- end
157
- end
158
- end
159
-
160
298
  specify "output" do
161
299
  expect(TeckelOperationAnnonClassesTest::CreateUser.call(name: "Bob", age: 23)).to be_a(User)
162
300
  end
@@ -167,91 +305,18 @@ RSpec.describe Teckel::Operation do
167
305
  end
168
306
 
169
307
  context "keyword contracts" do
170
- class CreateUserKeywordInit
171
- include Teckel::Operation
172
-
173
- class Input
174
- def initialize(name:, age:)
175
- @name, @age = name, age
176
- end
177
- attr_reader :name, :age
178
- end
179
-
180
- input_constructor ->(data) { input.new(**data) }
181
-
182
- Output = ::User
183
-
184
- class Error
185
- def initialize(message, errors)
186
- @message, @errors = message, errors
187
- end
188
- attr_reader :message, :errors
189
- end
190
- error_constructor :new
191
-
192
- def call(input)
193
- user = ::User.new(name: input.name, age: input.age)
194
- if user.save
195
- user
196
- else
197
- fail!(message: "Could not save User", errors: user.errors)
198
- end
199
- end
200
- end
201
-
202
308
  specify do
203
- expect(CreateUserKeywordInit.call(name: "Bob", age: 23)).to be_a(User)
309
+ expect(TeckelOperationKeywordContracts::MyOperation.call(name: "Bob", age: 23)).to be_a(User)
204
310
  end
205
311
  end
206
312
 
207
313
  context "splat contracts" do
208
- class CreateUserSplatInit
209
- include Teckel::Operation
210
-
211
- input Struct.new(:name, :age)
212
- input_constructor ->(data) { input.new(*data) }
213
-
214
- Output = ::User
215
-
216
- class Error
217
- def initialize(message, errors)
218
- @message, @errors = message, errors
219
- end
220
- attr_reader :message, :errors
221
- end
222
- error_constructor :new
223
-
224
- def call(input)
225
- user = ::User.new(name: input.name, age: input.age)
226
- if user.save
227
- user
228
- else
229
- fail!(message: "Could not save User", errors: user.errors)
230
- end
231
- end
232
- end
233
-
234
314
  specify do
235
- expect(CreateUserSplatInit.call(["Bob", 23])).to be_a(User)
315
+ expect(TeckelOperationCreateUserSplatInit::MyOperation.call(["Bob", 23])).to be_a(User)
236
316
  end
237
317
  end
238
318
 
239
319
  context "generated output" do
240
- module TeckelOperationGeneratedOutputTest
241
- class MyOperation
242
- include ::Teckel::Operation
243
-
244
- input none
245
- output Struct.new(:some_key)
246
- output_constructor ->(data) { output.new(*data.values_at(*output.members)) } # ruby 2.4 way for `keyword_init: true`
247
- error none
248
-
249
- def call(_input)
250
- { some_key: "some_value" }
251
- end
252
- end
253
- end
254
-
255
320
  specify "result" do
256
321
  result = TeckelOperationGeneratedOutputTest::MyOperation.call
257
322
  expect(result).to be_a(Struct)
@@ -260,23 +325,6 @@ RSpec.describe Teckel::Operation do
260
325
  end
261
326
 
262
327
  context "inject settings" do
263
- module TeckelOperationInjectSettingsTest
264
- class MyOperation
265
- include ::Teckel::Operation
266
-
267
- settings Struct.new(:injected)
268
- settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
269
-
270
- input none
271
- output Array
272
- error none
273
-
274
- def call(_input)
275
- (settings&.injected || []) << :operation_data
276
- end
277
- end
278
- end
279
-
280
328
  it "settings in operation instances are nil by default" do
281
329
  op = TeckelOperationInjectSettingsTest::MyOperation.new
282
330
  expect(op.settings).to be_nil
@@ -303,19 +351,6 @@ RSpec.describe Teckel::Operation do
303
351
  end
304
352
 
305
353
  context "operation with no settings" do
306
- module TeckelOperationNoSettingsTest
307
- class MyOperation
308
- include ::Teckel::Operation
309
-
310
- input none
311
- output none
312
- error none
313
-
314
- def call(_input); end
315
- end
316
- MyOperation.finalize!
317
- end
318
-
319
354
  it "uses None as default settings class" do
320
355
  expect(TeckelOperationNoSettingsTest::MyOperation.settings).to eq(Teckel::Contracts::None)
321
356
  expect(TeckelOperationNoSettingsTest::MyOperation.new.settings).to be_nil
@@ -329,37 +364,6 @@ RSpec.describe Teckel::Operation do
329
364
  end
330
365
 
331
366
  context "None in, out, err" do
332
- module TeckelOperationNoneDataTest
333
- class MyOperation
334
- include ::Teckel::Operation
335
-
336
- settings Struct.new(:fail_it, :fail_data, :success_it, :success_data)
337
- settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
338
-
339
- input none
340
- output none
341
- error none
342
-
343
- def call(_input)
344
- if settings&.fail_it
345
- if settings&.fail_data
346
- fail!(settings.fail_data)
347
- else
348
- fail!
349
- end
350
- elsif settings&.success_it
351
- if settings&.success_data
352
- success!(settings.success_data)
353
- else
354
- success!
355
- end
356
- else
357
- settings&.success_data
358
- end
359
- end
360
- end
361
- end
362
-
363
367
  let(:operation) { TeckelOperationNoneDataTest::MyOperation }
364
368
 
365
369
  it "raises error when called with input data" do
@@ -386,12 +390,6 @@ RSpec.describe Teckel::Operation do
386
390
  expect(operation.with(success_it: true).call).to be_nil
387
391
  end
388
392
 
389
- it "raises error when returning data" do
390
- expect {
391
- operation.with(success_it: false, success_data: "stuff").call
392
- }.to raise_error(ArgumentError)
393
- end
394
-
395
393
  it "returns nil as success result when returning nil" do
396
394
  expect(operation.call).to be_nil
397
395
  end