teckel 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/LICENSE_LOGO +4 -0
- data/README.md +4 -4
- data/lib/teckel.rb +9 -3
- data/lib/teckel/chain.rb +99 -271
- data/lib/teckel/chain/result.rb +38 -0
- data/lib/teckel/chain/runner.rb +51 -0
- data/lib/teckel/chain/step.rb +18 -0
- data/lib/teckel/config.rb +1 -23
- data/lib/teckel/contracts.rb +19 -0
- data/lib/teckel/operation.rb +309 -215
- data/lib/teckel/operation/result.rb +92 -0
- data/lib/teckel/operation/runner.rb +70 -0
- data/lib/teckel/result.rb +52 -53
- data/lib/teckel/version.rb +1 -1
- data/spec/chain/inheritance_spec.rb +116 -0
- data/spec/chain/results_spec.rb +53 -0
- data/spec/chain_around_hook_spec.rb +100 -0
- data/spec/chain_spec.rb +180 -0
- data/spec/config_spec.rb +26 -0
- data/spec/doctest_helper.rb +7 -0
- data/spec/operation/inheritance_spec.rb +94 -0
- data/spec/operation/result_spec.rb +34 -0
- data/spec/operation/results_spec.rb +117 -0
- data/spec/operation_spec.rb +485 -0
- data/spec/rb27/pattern_matching_spec.rb +193 -0
- data/spec/result_spec.rb +20 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/dry_base.rb +8 -0
- data/spec/support/fake_db.rb +12 -0
- data/spec/support/fake_models.rb +20 -0
- data/spec/teckel_spec.rb +7 -0
- metadata +52 -25
- data/.codeclimate.yml +0 -3
- data/.github/workflows/ci.yml +0 -92
- data/.github/workflows/pages.yml +0 -50
- data/.gitignore +0 -15
- data/.rspec +0 -3
- data/.rubocop.yml +0 -12
- data/.ruby-version +0 -1
- data/DEVELOPMENT.md +0 -32
- data/Gemfile +0 -16
- data/Rakefile +0 -35
- data/bin/console +0 -15
- data/bin/rake +0 -29
- data/bin/rspec +0 -29
- data/bin/rubocop +0 -18
- data/bin/setup +0 -8
- data/lib/teckel/none.rb +0 -18
- data/lib/teckel/operation/results.rb +0 -72
- data/teckel.gemspec +0 -32
data/.github/workflows/pages.yml
DELETED
@@ -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
data/.rspec
DELETED
data/.rubocop.yml
DELETED
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
ruby-2.6.5
|
data/DEVELOPMENT.md
DELETED
@@ -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
|
data/bin/console
DELETED
@@ -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")
|
data/bin/rubocop
DELETED
@@ -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
data/lib/teckel/none.rb
DELETED
@@ -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
|
data/teckel.gemspec
DELETED
@@ -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
|