teckel 0.5.0 → 0.6.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.
@@ -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