teckel 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d55dd7fb782cfdcb3cc960423e46ab2ce8b49c9396273f10749105883d55ed6f
4
- data.tar.gz: e3b7428d98daeb42cc086b4a5cd5081b5c3b4ca90c14b0cf3f4cd9576243d0e3
3
+ metadata.gz: df9c73577c9c93fbaaf2e7661a07bdc3b1b0397832a62a8edbe4eea846c4c029
4
+ data.tar.gz: 037041bf0eb6ae8bc41e354772d3ce1d04002a35ecccc0b1ecefa33a85845c3b
5
5
  SHA512:
6
- metadata.gz: '0148d426e8e7124a08a3a7dd3c42487cac5944d17ba9cabb98a8c378a86a5d9813b7b62c483d2203809644528d2e6b6cc4de51476aa67261b1405f3f41576613'
7
- data.tar.gz: 6792ad4806b977d21d0ea621e311306cb13c239d8604e35e61937e79ce4f5488cbb410aa6ae22e9e12e48b61529f97e924aab22e100717808885c0b0c76980fd
6
+ metadata.gz: 2f6f601e816c972aed8e511b05fdbc418a48aab10c2ca246d108661cc25a8d514a41944b2e01b9294757a2a717696491f20f601f5f60e53faa45651aec48bc7f
7
+ data.tar.gz: 68bc46e5ed21cf5e639ee1fc00265b278f9062f42de9d179a07559862990a078df9dcb2ef26956eb629cf08e71409b9b05bd363a11930cb64ecdee533d63d2ea
@@ -1,5 +1,14 @@
1
1
  # Changes
2
2
 
3
+ ## 0.7.0
4
+
5
+ - Breaking: `Teckel::Chain` will not be required by default. require manually if needed `require "teckel/chain"` [GH-24]
6
+ - Breaking: Internally, `Teckel::Operation::Runner` instead of `:success` and `:failure` now only uses `:halt` as it's throw-catch symbol. [GH-26]
7
+ - Add: Using the default `Teckel::Operation::Runner`, `input_constructor` and `result_constructor` will be executed
8
+ within the context of the operation instance. This allows for `input_constructor` to call `fail!` and `success!`
9
+ without ever `call`ing the operation. [GH-26]
10
+
11
+
3
12
  ## 0.6.0
4
13
 
5
14
  - Breaking: Operations return values will be ignored. [GH-21]
@@ -19,4 +19,3 @@ require_relative "teckel/config"
19
19
  require_relative "teckel/contracts"
20
20
  require_relative "teckel/result"
21
21
  require_relative "teckel/operation"
22
- require_relative "teckel/chain"
@@ -103,13 +103,42 @@ module Teckel
103
103
  # @example
104
104
  # class MyOperation
105
105
  # include Teckel::Operation
106
+ # result!
107
+ #
108
+ # settings Struct.new(:say, :other)
109
+ # settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
110
+ #
111
+ # input none
112
+ # output Hash
113
+ # error none
114
+ #
115
+ # def call(_)
116
+ # success!(settings.to_h)
117
+ # end
118
+ # end
119
+ #
120
+ # class Chain
121
+ # include Teckel::Chain
106
122
  #
107
123
  # class Result < Teckel::Operation::Result
108
- # def initialize(value, success, step, options = {}); end
124
+ # def initialize(value, success, step, opts = {})
125
+ # super(value, success)
126
+ # @step = step
127
+ # @opts = opts
128
+ # end
129
+ #
130
+ # class << self
131
+ # alias :[] :new # Alias the default constructor to :new
132
+ # end
133
+ #
134
+ # attr_reader :opts, :step
109
135
  # end
110
136
  #
111
- # # If you need more control over how to build a new +Settings+ instance
112
- # result_constructor ->(value, success, step) { result.new(value, success, step, {foo: :bar}) }
137
+ # result_constructor ->(value, success, step) {
138
+ # result.new(value, success, step, time: Time.now.to_i)
139
+ # }
140
+ #
141
+ # step :a, MyOperation
113
142
  # end
114
143
  def result_constructor(sym_or_proc = nil)
115
144
  constructor = build_constructor(result, sym_or_proc) unless sym_or_proc.nil?
@@ -16,16 +16,24 @@ module Teckel
16
16
  attr_reader :operation, :settings
17
17
 
18
18
  def call(input = nil)
19
- catch(:failure) do
20
- out = catch(:success) do
21
- run operation.input_constructor.call(input)
22
- return nil # :sic!: return values need to go through +success!+
23
- end
24
-
25
- return out
19
+ catch(:halt) do
20
+ op = instance
21
+ op_input = op.instance_exec(input, &operation.input_constructor)
22
+ op.call(op_input)
23
+ nil # return values need to go through +success!+ or +fail!+
26
24
  end
27
25
  end
28
26
 
27
+ def instance
28
+ return @instance if instance_variable_defined?(:@instance)
29
+
30
+ op = operation.new
31
+ op.runner = self
32
+ op.settings = settings if settings != UNDEFINED
33
+
34
+ @instance = op
35
+ end
36
+
29
37
  # This is just here to raise a meaningful error.
30
38
  # @!visibility private
31
39
  def with(*)
@@ -44,7 +52,7 @@ module Teckel
44
52
  operation.output_constructor.call(*args)
45
53
  end
46
54
 
47
- throw :success, operation.result_constructor.call(value, true)
55
+ throw :halt, instance.instance_exec(value, true, &operation.result_constructor)
48
56
  end
49
57
 
50
58
  # Halt any further execution with an error value
@@ -59,16 +67,7 @@ module Teckel
59
67
  operation.error_constructor.call(*args)
60
68
  end
61
69
 
62
- throw :failure, operation.result_constructor.call(value, false)
63
- end
64
-
65
- private
66
-
67
- def run(input)
68
- op = @operation.new
69
- op.runner = self
70
- op.settings = settings if settings != UNDEFINED
71
- op.call(input)
70
+ throw :halt, instance.instance_exec(value, false, &operation.result_constructor)
72
71
  end
73
72
  end
74
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Teckel
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -5,3 +5,4 @@ require_relative 'support/fake_db'
5
5
  require_relative 'support/fake_models'
6
6
 
7
7
  require "teckel"
8
+ require "teckel/chain"
@@ -0,0 +1,103 @@
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
@@ -3,25 +3,46 @@
3
3
  RSpec.describe Teckel::Operation::Result do
4
4
  let(:failure_value) { "some error" }
5
5
  let(:failed_result) { Teckel::Operation::Result.new(failure_value, false) }
6
+ let(:failed_nil_result) { Teckel::Operation::Result.new(failure_value, nil) }
6
7
 
7
- let(:success_value) { "some error" }
8
- let(:successful_result) { Teckel::Operation::Result.new(failure_value, true) }
8
+ let(:success_value) { "some success" }
9
+ let(:successful_result) { Teckel::Operation::Result.new(success_value, true) }
10
+ let(:successful_1_result) { Teckel::Operation::Result.new(success_value, 1) }
9
11
 
10
- it { expect(successful_result.successful?).to be(true) }
11
- it { expect(failed_result.successful?).to be(false) }
12
+ it { expect(successful_result.successful?).to eq(true) }
13
+ it { expect(successful_1_result.successful?).to eq(true) }
14
+ it { expect(failed_result.successful?).to eq(false) }
15
+ it { expect(failed_nil_result.successful?).to eq(false) }
12
16
 
13
- it { expect(successful_result.failure?).to be(false) }
14
- it { expect(failed_result.failure?).to be(true) }
17
+ it { expect(successful_result.failure?).to eq(false) }
18
+ it { expect(successful_1_result.failure?).to eq(false) }
19
+ it { expect(failed_result.failure?).to eq(true) }
20
+ it { expect(failed_nil_result.failure?).to eq(true) }
15
21
 
16
22
  it { expect(successful_result.value).to eq(success_value) }
17
23
  it { expect(failed_result.value).to eq(failure_value) }
18
24
 
19
25
  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") }
26
+ it("on successful result, returns value") {
27
+ expect(successful_result.success).to eq(success_value)
28
+ }
29
+
30
+ describe "on failed result" do
31
+ it("with no fallbacks, returns nil") {
32
+ expect(failed_result.success).to eq(nil)
33
+ }
34
+ it("with default-argument, returns default-argument") {
35
+ expect(failed_result.success("other")).to eq("other")
36
+ }
37
+ it("with block, returns block return value") {
38
+ expect(failed_result.success { |value| "Failed: #{value}" } ).to eq("Failed: some error")
39
+ }
40
+ it("with default-argument and block given, returns default-argument, skips block") {
41
+ expect { |blk|
42
+ expect(failed_result.success("default", &blk)).to_not eq("default")
43
+ }.to(yield_control)
44
+ }
45
+ end
25
46
  end
26
47
 
27
48
  describe "#failure" do
@@ -29,6 +50,6 @@ RSpec.describe Teckel::Operation::Result do
29
50
 
30
51
  it { expect(successful_result.failure).to eq(nil) }
31
52
  it { expect(successful_result.failure("other")).to eq("other") }
32
- it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some error") }
53
+ it { expect(successful_result.failure { |value| "Failed: #{value}" } ).to eq("Failed: some success") }
33
54
  end
34
55
  end
@@ -44,7 +44,7 @@ class CreateUserCustomResult
44
44
  end
45
45
 
46
46
  result MyResult
47
- result_constructor ->(value, success) { result.new(value, success, time: Time.now.to_i) }
47
+ result_constructor ->(value, success) { MyResult.new(value, success, time: Time.now.to_i) }
48
48
 
49
49
  input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
50
50
  output Types.Instance(User)
@@ -101,7 +101,7 @@ module TeckelOperationKeywordContracts
101
101
  attr_reader :name, :age
102
102
  end
103
103
 
104
- input_constructor ->(data) { input.new(**data) }
104
+ input_constructor ->(data) { Input.new(**data) }
105
105
 
106
106
  Output = ::User
107
107
 
@@ -129,7 +129,7 @@ module TeckelOperationCreateUserSplatInit
129
129
  include Teckel::Operation
130
130
 
131
131
  input Struct.new(:name, :age)
132
- input_constructor ->(data) { input.new(*data) }
132
+ input_constructor ->(data) { self.class.input.new(*data) }
133
133
 
134
134
  Output = ::User
135
135
 
@@ -346,7 +346,7 @@ RSpec.describe Teckel::Operation do
346
346
 
347
347
  expect {
348
348
  op.with(more: :stuff_2)
349
- }.to raise_error(Teckel::Error)
349
+ }.to raise_error(Teckel::Error, "Operation already has settings assigned.")
350
350
  end
351
351
  end
352
352
 
@@ -12,11 +12,11 @@ end
12
12
 
13
13
  RSpec.describe Teckel::Result do
14
14
  describe "missing initialize" do
15
- specify do
15
+ specify "raises NotImplementedError" do
16
16
  result = TeckelResultTest::MissingResultImplementation["value", true]
17
- expect { result.successful? }.to raise_error(NotImplementedError)
18
- expect { result.failure? }.to raise_error(NotImplementedError)
19
- expect { result.value }.to raise_error(NotImplementedError)
17
+ expect { result.successful? }.to raise_error(NotImplementedError, "Result object does not implement `successful?`")
18
+ expect { result.failure? }.to raise_error(NotImplementedError, "Result object does not implement `successful?`")
19
+ expect { result.value }.to raise_error(NotImplementedError, "Result object does not implement `value`")
20
20
  end
21
21
  end
22
22
  end
@@ -3,12 +3,15 @@
3
3
  require "bundler/setup"
4
4
  if ENV['COVERAGE'] == 'true'
5
5
  require 'simplecov'
6
+ require 'simplecov_json_formatter'
7
+ SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter
6
8
  SimpleCov.start do
7
9
  add_filter %r{^/spec/}
8
10
  end
9
11
  end
10
12
 
11
13
  require "teckel"
14
+ require "teckel/chain"
12
15
 
13
16
  RSpec.configure do |config|
14
17
  # Enable flags like --only-failures and --next-failure
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teckel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Schulze
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-03 00:00:00.000000000 Z
11
+ date: 2021-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -107,16 +107,17 @@ files:
107
107
  - lib/teckel/operation/runner.rb
108
108
  - lib/teckel/result.rb
109
109
  - lib/teckel/version.rb
110
+ - spec/chain/around_hook_spec.rb
110
111
  - spec/chain/default_settings_spec.rb
111
112
  - spec/chain/inheritance_spec.rb
112
113
  - spec/chain/none_input_spec.rb
113
114
  - spec/chain/results_spec.rb
114
- - spec/chain_around_hook_spec.rb
115
115
  - spec/chain_spec.rb
116
116
  - spec/config_spec.rb
117
117
  - spec/doctest_helper.rb
118
118
  - spec/operation/contract_trace_spec.rb
119
119
  - spec/operation/default_settings_spec.rb
120
+ - spec/operation/fail_on_input_spec.rb
120
121
  - spec/operation/inheritance_spec.rb
121
122
  - spec/operation/result_spec.rb
122
123
  - spec/operation/results_spec.rb
@@ -132,10 +133,11 @@ homepage: https://github.com/fnordfish/teckel
132
133
  licenses:
133
134
  - Apache-2.0
134
135
  metadata:
135
- changelog_uri: https://github.com/github.com/fnordfish/blob/master/CHANGELOG.md
136
- source_code_uri: https://github.com/github.com/fnordfish
137
- bug_tracker_uri: https://github.com/github.com/fnordfish/issues
138
- documentation_uri: https://www.rubydoc.info/gems/teckel/0.6.0
136
+ changelog_uri: https://github.com/fnordfish/teckel/blob/master/CHANGELOG.md
137
+ source_code_uri: https://github.com/fnordfish/teckel
138
+ bug_tracker_uri: https://github.com/fnordfish/teckel/issues
139
+ documentation_uri: https://www.rubydoc.info/gems/teckel/0.7.0
140
+ user_docs_uri: https://fnordfish.github.io/teckel/
139
141
  post_install_message:
140
142
  rdoc_options: []
141
143
  require_paths:
@@ -151,21 +153,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
153
  - !ruby/object:Gem::Version
152
154
  version: '0'
153
155
  requirements: []
154
- rubygems_version: 3.0.3
156
+ rubygems_version: 3.2.3
155
157
  signing_key:
156
158
  specification_version: 4
157
159
  summary: Operations with enforced in/out/err data structures
158
160
  test_files:
161
+ - spec/chain/around_hook_spec.rb
159
162
  - spec/chain/default_settings_spec.rb
160
163
  - spec/chain/inheritance_spec.rb
161
164
  - spec/chain/none_input_spec.rb
162
165
  - spec/chain/results_spec.rb
163
- - spec/chain_around_hook_spec.rb
164
166
  - spec/chain_spec.rb
165
167
  - spec/config_spec.rb
166
168
  - spec/doctest_helper.rb
167
169
  - spec/operation/contract_trace_spec.rb
168
170
  - spec/operation/default_settings_spec.rb
171
+ - spec/operation/fail_on_input_spec.rb
169
172
  - spec/operation/inheritance_spec.rb
170
173
  - spec/operation/result_spec.rb
171
174
  - spec/operation/results_spec.rb