teckel 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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