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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/LICENSE_LOGO +4 -0
  4. data/README.md +4 -4
  5. data/lib/teckel.rb +9 -3
  6. data/lib/teckel/chain.rb +99 -271
  7. data/lib/teckel/chain/result.rb +38 -0
  8. data/lib/teckel/chain/runner.rb +51 -0
  9. data/lib/teckel/chain/step.rb +18 -0
  10. data/lib/teckel/config.rb +1 -23
  11. data/lib/teckel/contracts.rb +19 -0
  12. data/lib/teckel/operation.rb +309 -215
  13. data/lib/teckel/operation/result.rb +92 -0
  14. data/lib/teckel/operation/runner.rb +70 -0
  15. data/lib/teckel/result.rb +52 -53
  16. data/lib/teckel/version.rb +1 -1
  17. data/spec/chain/inheritance_spec.rb +116 -0
  18. data/spec/chain/results_spec.rb +53 -0
  19. data/spec/chain_around_hook_spec.rb +100 -0
  20. data/spec/chain_spec.rb +180 -0
  21. data/spec/config_spec.rb +26 -0
  22. data/spec/doctest_helper.rb +7 -0
  23. data/spec/operation/inheritance_spec.rb +94 -0
  24. data/spec/operation/result_spec.rb +34 -0
  25. data/spec/operation/results_spec.rb +117 -0
  26. data/spec/operation_spec.rb +485 -0
  27. data/spec/rb27/pattern_matching_spec.rb +193 -0
  28. data/spec/result_spec.rb +20 -0
  29. data/spec/spec_helper.rb +25 -0
  30. data/spec/support/dry_base.rb +8 -0
  31. data/spec/support/fake_db.rb +12 -0
  32. data/spec/support/fake_models.rb +20 -0
  33. data/spec/teckel_spec.rb +7 -0
  34. metadata +52 -25
  35. data/.codeclimate.yml +0 -3
  36. data/.github/workflows/ci.yml +0 -92
  37. data/.github/workflows/pages.yml +0 -50
  38. data/.gitignore +0 -15
  39. data/.rspec +0 -3
  40. data/.rubocop.yml +0 -12
  41. data/.ruby-version +0 -1
  42. data/DEVELOPMENT.md +0 -32
  43. data/Gemfile +0 -16
  44. data/Rakefile +0 -35
  45. data/bin/console +0 -15
  46. data/bin/rake +0 -29
  47. data/bin/rspec +0 -29
  48. data/bin/rubocop +0 -18
  49. data/bin/setup +0 -8
  50. data/lib/teckel/none.rb +0 -18
  51. data/lib/teckel/operation/results.rb +0 -72
  52. data/teckel.gemspec +0 -32
@@ -1,50 +0,0 @@
1
- name: pages
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- paths:
8
- - .github/workflows/pages.yml
9
- - lib/**
10
- - README.md
11
- - .yardopts
12
- - Rakefile
13
-
14
- jobs:
15
- build-docs:
16
- runs-on: ubuntu-latest
17
- steps:
18
- - uses: actions/checkout@v2
19
- - name: Set up Ruby
20
- uses: actions/setup-ruby@v1
21
- with:
22
- ruby-version: 2.6.x
23
- - name: Bundle install
24
- run: |
25
- gem install bundler
26
- bundle install --jobs 4 --retry 3 --without tools docs benchmarks
27
- - name: Build docs
28
- run: 'bin/rake docs:yard'
29
- - name: config git
30
- run: |
31
- git config --local user.email "action@github.com"
32
- git config --local user.name "GitHub Action"
33
- - name: Checkout Pages
34
- run: 'git fetch origin gh-pages && git checkout gh-pages'
35
- - name: Replace and commit docs
36
- run: |
37
- rm -rf doc && mv _yardoc doc
38
- git add -A doc
39
- git commit -m"Update API docs"
40
- - name: Replace index
41
- run: |
42
- git checkout master README.md
43
- mv -f README.md index.md
44
- if [ ! -z "$(git diff --name-only -- index.md)" ]; then
45
- git commit index.md -m"update index"
46
- fi
47
- - name: Push
48
- env:
49
- INPUT_GITHUB_TOKEN: "${{ secrets.DEPLOY_TOKEN }}"
50
- run: git push https://git:${INPUT_GITHUB_TOKEN}@github.com/fnordfish/teckel.git gh-pages
data/.gitignore DELETED
@@ -1,15 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
-
13
- .rubocop-http*
14
-
15
- /Gemfile.lock
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
@@ -1,12 +0,0 @@
1
- inherit_from:
2
- - https://relaxed.ruby.style/rubocop.yml
3
-
4
- AllCops:
5
- TargetRubyVersion: 2.4
6
-
7
- Metrics/BlockLength:
8
- Exclude:
9
- - spec/**/*_spec.rb
10
-
11
- Layout/FirstArrayElementIndentation:
12
- EnforcedStyle: consistent
@@ -1 +0,0 @@
1
- ruby-2.6.5
@@ -1,32 +0,0 @@
1
- # Development Guidelines
2
-
3
- - Keep it simple.
4
- - Favor easy debug-ability over clever solutions.
5
- - Aim to be a 0-dependency lib (at runtime)
6
-
7
- ## Roadmap
8
-
9
- - Add "Settings"/Dependency injection for Operations and Chains
10
-
11
- ```
12
- MyOp.with(foo: "bar").call("input")
13
-
14
- class MyOp
15
- settings Types::Hash.schema(foo: Types::String)
16
-
17
- def call(input)
18
- input == "input"
19
- settings.foo == "bar"
20
- end
21
- end
22
-
23
- MyCain.with(:step1) { { foo: "bar" } }.with(:stepX) { { another: :setting} }.call(params)
24
- ```
25
- - Add a dry-monads mixin to wrap Operations and Chains result/error into a Result Monad (for example see https://dry-rb.org/gems/dry-types/master/extensions/monads/)
26
- ```
27
- MyOp.call("input").to_monad do
28
- end
29
- ```
30
- - Check if/how to deal with inheritance
31
- - ...
32
-
data/Gemfile DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
-
7
- # Specify your gem's dependencies in teckel.gemspec
8
- gemspec
9
-
10
- group :development, :test do
11
- gem "dry-struct", ">= 1.1.1", "< 2"
12
- end
13
-
14
- group :test do
15
- gem "simplecov", require: false
16
- end
data/Rakefile DELETED
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
- require "yard"
6
- require "yard/doctest/rake"
7
-
8
- RSpec::Core::RakeTask.new(:spec)
9
-
10
- task :docs do
11
- Rake::Task["docs:yard"].invoke
12
- Rake::Task["docs:yard:doctest"].invoke
13
- end
14
-
15
- namespace :docs do
16
- YARD::Rake::YardocTask.new do |t|
17
- t.files = ['lib/**/*.rb']
18
- t.options = []
19
- t.stats_options = ['--list-undoc']
20
- end
21
-
22
- task :fswatch do
23
- sh 'fswatch -0 lib | while read -d "" e; do rake docs:yard; done'
24
- end
25
-
26
- YARD::Doctest::RakeTask.new do |task|
27
- task.doctest_opts = %w[-v]
28
- task.pattern = Dir.glob('lib/**/*.rb')
29
- end
30
- end
31
-
32
- task :default do
33
- Rake::Task["spec"].invoke
34
- Rake::Task["docs:yard:doctest"].invoke
35
- end
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "teckel"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
data/bin/rake DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'rake' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path('bundle', __dir__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("rake", "rake")
data/bin/rspec DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'rspec' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path('bundle', __dir__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("rspec-core", "rspec")
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/inline'
5
- require 'bundler'
6
-
7
- # We need the `Bundler.settings.temporary` for a bundler bug:
8
- # https://github.com/bundler/bundler/issues/7114
9
- # Will get fixed in bundler version 2.1.0
10
- Bundler.settings.temporary(frozen: false) do
11
- gemfile do
12
- source 'https://rubygems.org'
13
- gem 'rubocop', '~> 0.78.0'
14
- gem 'relaxed-rubocop', '2.4'
15
- end
16
- end
17
-
18
- load Gem.bin_path("rubocop", "rubocop")
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Teckel
4
- # Simple type object for enforcing +input+, +output+ or +error+ data to be
5
- # not set (or +nil+)
6
- class None
7
- class << self
8
- # Always return nil
9
- # @return nil
10
- # @raise [ArgumentError] when called with any non-nil arguments
11
- def [](*args)
12
- raise ArgumentError, "None called with arguments" if args.any?(&:itself)
13
- end
14
-
15
- alias :new :[]
16
- end
17
- end
18
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Teckel
4
- module Operation
5
- # Works just like {Teckel::Operation}, but wraps +output+ and +error+ into a
6
- # {Teckel::Result Teckel::Result}.
7
- #
8
- # If a {Teckel::Result Teckel::Result} is given as +input+, it will get unwrapped,
9
- # so that the original {Teckel::Result#value} gets passed to your Operation code.
10
- #
11
- # @example
12
- #
13
- # class CreateUser
14
- # include Teckel::Operation::Results
15
- #
16
- # input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
17
- # output Types.Instance(User)
18
- # error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
19
- #
20
- # # @param [Hash<name: String, age: Integer>]
21
- # # @return [User,Hash<message: String, errors: [Hash]>]
22
- # def call(input)
23
- # user = User.new(name: input[:name], age: input[:age])
24
- # if user.save
25
- # success!(user) # exits early with success, prevents any further execution
26
- # else
27
- # fail!(message: "Could not save User", errors: user.errors)
28
- # end
29
- # end
30
- # end
31
- #
32
- # # A success call:
33
- # CreateUser.call(name: "Bob", age: 23).is_a?(Teckel::Result) #=> true
34
- # CreateUser.call(name: "Bob", age: 23).success.is_a?(User) #=> true
35
- #
36
- # # A failure call:
37
- # CreateUser.call(name: "Bob", age: 10).is_a?(Teckel::Result) #=> true
38
- # CreateUser.call(name: "Bob", age: 10).failure.is_a?(Hash) #=> true
39
- #
40
- # # Unwrapping success input:
41
- # CreateUser.call(Teckel::Result.new({name: "Bob", age: 23}, true)).success.is_a?(User) #=> true
42
- #
43
- # # Unwrapping failure input:
44
- # CreateUser.call(Teckel::Result.new({name: "Bob", age: 23}, false)).success.is_a?(User) #=> true
45
- #
46
- # @!visibility public
47
- module Results
48
- # Overwrites the defaults {Teckel::Operation::Runner} to un/wrap input, output and error
49
- class Runner < Teckel::Operation::Runner
50
- private
51
-
52
- def build_input(input)
53
- input = input.value if input.is_a?(Teckel::Result)
54
- super(input)
55
- end
56
-
57
- def build_output(*args)
58
- Teckel::Result.new(super, true)
59
- end
60
-
61
- def build_error(*args)
62
- Teckel::Result.new(super, false)
63
- end
64
- end
65
-
66
- def self.included(receiver)
67
- receiver.send :include, Teckel::Operation unless Teckel::Operation >= receiver
68
- receiver.send :runner, Runner
69
- end
70
- end
71
- end
72
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "teckel/version"
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = "teckel"
9
- spec.version = Teckel::VERSION
10
- spec.authors = ["Robert Schulze"]
11
- spec.email = ["robert@dotless.de"]
12
- spec.licenses = ['Apache-2.0']
13
-
14
- spec.summary = 'Operations with enforced in/out/err data structures'
15
- spec.description = 'Wrap your business logic into a common interface with enforced input, output and error data structures'
16
- spec.homepage = "https://github.com/dotless-de/teckel"
17
-
18
- # Specify which files should be added to the gem when it is released.
19
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
- end
23
- spec.bindir = "exe"
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ["lib"]
26
-
27
- spec.add_development_dependency "bundler", "~> 2.0"
28
- spec.add_development_dependency "rake", ">= 10.0"
29
- spec.add_development_dependency "rspec", "~> 3.0"
30
- spec.add_development_dependency "yard"
31
- spec.add_development_dependency "yard-doctest"
32
- end