flows 0.1.0 → 0.5.1

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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +38 -0
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -0
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +44 -2
  7. data/.ruby-version +1 -1
  8. data/.yardopts +1 -0
  9. data/CHANGELOG.md +65 -0
  10. data/README.md +186 -256
  11. data/Rakefile +35 -1
  12. data/bin/.rubocop.yml +5 -0
  13. data/bin/all_the_errors +55 -0
  14. data/bin/benchmark +69 -78
  15. data/bin/benchmark_cli/compare.rb +118 -0
  16. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  17. data/bin/benchmark_cli/compare/base.rb +45 -0
  18. data/bin/benchmark_cli/compare/command.rb +47 -0
  19. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  20. data/bin/benchmark_cli/examples.rb +23 -0
  21. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  22. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  30. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  32. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  37. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  38. data/bin/benchmark_cli/helpers.rb +12 -0
  39. data/bin/benchmark_cli/ruby.rb +15 -0
  40. data/bin/benchmark_cli/ruby/command.rb +38 -0
  41. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  42. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  43. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  44. data/bin/console +1 -0
  45. data/bin/docserver +7 -0
  46. data/bin/errors +130 -0
  47. data/bin/errors_cli/contract_error_demo.rb +49 -0
  48. data/bin/errors_cli/di_error_demo.rb +38 -0
  49. data/bin/errors_cli/flow_error_demo.rb +22 -0
  50. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  51. data/bin/errors_cli/oc_error_demo.rb +40 -0
  52. data/bin/errors_cli/railway_error_demo.rb +10 -0
  53. data/bin/errors_cli/result_error_demo.rb +13 -0
  54. data/bin/errors_cli/scp_error_demo.rb +17 -0
  55. data/docs/.nojekyll +0 -0
  56. data/docs/README.md +13 -0
  57. data/docs/_sidebar.md +2 -0
  58. data/docs/index.html +30 -0
  59. data/flows.gemspec +27 -2
  60. data/forspell.dict +17 -0
  61. data/lefthook.yml +21 -0
  62. data/lib/flows.rb +13 -5
  63. data/lib/flows/contract.rb +402 -0
  64. data/lib/flows/contract/array.rb +55 -0
  65. data/lib/flows/contract/case_eq.rb +43 -0
  66. data/lib/flows/contract/compose.rb +77 -0
  67. data/lib/flows/contract/either.rb +53 -0
  68. data/lib/flows/contract/error.rb +25 -0
  69. data/lib/flows/contract/hash.rb +75 -0
  70. data/lib/flows/contract/hash_of.rb +70 -0
  71. data/lib/flows/contract/helpers.rb +22 -0
  72. data/lib/flows/contract/predicate.rb +34 -0
  73. data/lib/flows/contract/transformer.rb +50 -0
  74. data/lib/flows/contract/tuple.rb +70 -0
  75. data/lib/flows/flow.rb +96 -7
  76. data/lib/flows/flow/errors.rb +29 -0
  77. data/lib/flows/flow/node.rb +132 -0
  78. data/lib/flows/flow/router.rb +29 -0
  79. data/lib/flows/flow/router/custom.rb +59 -0
  80. data/lib/flows/flow/router/errors.rb +11 -0
  81. data/lib/flows/flow/router/simple.rb +25 -0
  82. data/lib/flows/plugin.rb +14 -0
  83. data/lib/flows/plugin/dependency_injector.rb +159 -0
  84. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  85. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  86. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  87. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  88. data/lib/flows/plugin/implicit_init.rb +45 -0
  89. data/lib/flows/plugin/output_contract.rb +85 -0
  90. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  91. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  92. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  93. data/lib/flows/plugin/profiler.rb +114 -0
  94. data/lib/flows/plugin/profiler/injector.rb +35 -0
  95. data/lib/flows/plugin/profiler/report.rb +48 -0
  96. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  97. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  98. data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
  99. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  100. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  101. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  102. data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
  103. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  104. data/lib/flows/railway.rb +154 -0
  105. data/lib/flows/railway/dsl.rb +18 -0
  106. data/lib/flows/railway/errors.rb +17 -0
  107. data/lib/flows/railway/step.rb +24 -0
  108. data/lib/flows/railway/step_list.rb +38 -0
  109. data/lib/flows/result.rb +189 -2
  110. data/lib/flows/result/do.rb +172 -0
  111. data/lib/flows/result/err.rb +12 -6
  112. data/lib/flows/result/errors.rb +29 -17
  113. data/lib/flows/result/helpers.rb +25 -3
  114. data/lib/flows/result/ok.rb +12 -6
  115. data/lib/flows/shared_context_pipeline.rb +299 -0
  116. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  117. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
  118. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  119. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  120. data/lib/flows/shared_context_pipeline/mutation_step.rb +29 -0
  121. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  122. data/lib/flows/shared_context_pipeline/step.rb +44 -0
  123. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  124. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  125. data/lib/flows/shared_context_pipeline/wrap.rb +74 -0
  126. data/lib/flows/util.rb +17 -0
  127. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  128. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +98 -0
  129. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  130. data/lib/flows/util/prepend_to_class.rb +179 -0
  131. data/lib/flows/version.rb +1 -1
  132. metadata +288 -20
  133. data/.travis.yml +0 -8
  134. data/Gemfile.lock +0 -119
  135. data/bin/demo +0 -66
  136. data/bin/examples.rb +0 -159
  137. data/bin/profile_10steps +0 -64
  138. data/bin/ruby_benchmarks +0 -26
  139. data/lib/flows/node.rb +0 -27
  140. data/lib/flows/operation.rb +0 -54
  141. data/lib/flows/operation/builder.rb +0 -130
  142. data/lib/flows/operation/builder/build_router.rb +0 -37
  143. data/lib/flows/operation/dsl.rb +0 -72
  144. data/lib/flows/operation/errors.rb +0 -75
  145. data/lib/flows/operation/executor.rb +0 -78
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a69e7b183fcc1a9a0e0eabeaac0e66aff9fc55ea458fa34a5c37de86c7970ab
4
- data.tar.gz: 20dbb58536ba20c0f7c7032757679ea968088965890851e93af81e2b4cb148f4
3
+ metadata.gz: e6f029a3e03c0cd3ac484cb703f67a1c4910a10c8395e7b1024518dc84a14cb5
4
+ data.tar.gz: 23f44992d336cd637c02ff6d96c82a99f7e6af2a2affc82bcdc2c3f4ee1153a2
5
5
  SHA512:
6
- metadata.gz: b606d7df548e65d694f4700ecf6991667bca8f6626a41111bce6cbe9f8949ad0c5cfc58830fd1596b1fc3c170eaca7aed2b4f8f83092a5a7d174979978ece70b
7
- data.tar.gz: 4fbfd1e9422aca8b7800b4930d32a7a3a4f4eca67eceab08cf0b20fcde24ef628d677f5bc40ffa1f7a1c9c3af6f1f80de78a6e248adf145f23f63d3d4ae6c77c
6
+ metadata.gz: 17015a5eb9868ced9c18872706625df9aeb51aa90717be36929fe223006b5e28fa439fcc63db49b43a047c0375b3d6493f4640da6164be21815b3df95291ae96
7
+ data.tar.gz: b2170ec9d8a710b70fd676772ba7a5556ab23b9a50313bf34f321afb440f07d9fe66bc76634a9e53f02dd7063ebf92e27a94e162e6470e2b22a87703a6856f48
@@ -0,0 +1,38 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+ schedule:
11
+ - cron: 0 2 * * 1-5
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ ruby:
20
+ - 2.5.x
21
+ - 2.6.x
22
+ # - 2.7.x
23
+ steps:
24
+ - uses: actions/checkout@v1
25
+ - name: Set up Ruby
26
+ uses: actions/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby }}
29
+ - name: Install Bundler
30
+ run: gem install bundler
31
+ - name: Install Hunspell
32
+ run: sudo apt-get install hunspell
33
+ - name: Install Deps
34
+ run: bundle install --jobs 4 --retry 3
35
+ - name: Test
36
+ env:
37
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
38
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  /.bundle/
2
+ /.vendor/
2
3
  /.yardoc
3
4
  /_yardoc/
4
5
  /coverage/
@@ -12,4 +13,11 @@
12
13
 
13
14
  # profile results folder
14
15
  /profile/
15
- !/profile/.keep
16
+ !/profile/.keep
17
+
18
+ # OS specific stuff
19
+ .DS_Store
20
+
21
+ # In case of gem development we don't need to track Gemfile.lock
22
+ # All the constraints must be expressed in gemspec
23
+ /Gemfile.lock
data/.mdlrc ADDED
@@ -0,0 +1 @@
1
+ rules "~MD013", "~MD033", "~MD024"
@@ -0,0 +1,54 @@
1
+ # default config: https://github.com/troessner/reek/blob/master/docs/defaults.reek.yml
2
+ # detectors' docs: https://github.com/troessner/reek/tree/master/docs
3
+ ---
4
+ exclude_paths:
5
+ - 'bin/'
6
+ - 'spec/'
7
+
8
+ detectors:
9
+ LongParameterList:
10
+ enabled: true
11
+ exclude: []
12
+ max_params: 4 # +1 to default value
13
+ overrides:
14
+ initialize:
15
+ max_params: 6 # +1 to default value
16
+ ModuleInitialize:
17
+ enabled: false
18
+ TooManyInstanceVariables:
19
+ enabled: true
20
+ exclude: []
21
+ max_instance_variables: 5 # +1 to default value
22
+ DataClump:
23
+ exclude:
24
+ - Flows::Result::Helpers
25
+ - Flows::SharedContextPipeline::DSL::Tracks
26
+ IrresponsibleModule:
27
+ exclude:
28
+ - Flows::SharedContextPipeline
29
+ FeatureEnvy:
30
+ exclude:
31
+ - Flows::SharedContextPipeline#call
32
+ - Flows::Contract # too many false positives here
33
+ - Flows::Plugin::Profiler::Report#events
34
+ TooManyStatements:
35
+ exclude:
36
+ - Flows::SharedContextPipeline#call
37
+ - Flows::Plugin::Profiler::Wrapper#make_instance_wrapper
38
+ - Flows::Plugin::Profiler::Wrapper#make_singleton_wrapper
39
+ - initialize
40
+ DuplicateMethodCall:
41
+ exclude:
42
+ - Flows::SharedContextPipeline#call
43
+ - Flows::Plugin::Profiler::Wrapper#make_instance_wrapper
44
+ - Flows::Plugin::Profiler::Wrapper#make_singleton_wrapper
45
+ - 'length'
46
+ MissingSafeMethod:
47
+ exclude:
48
+ - Flows::Contract
49
+ BooleanParameter:
50
+ exclude:
51
+ - Flows::Plugin::OutputContract::DSL
52
+ TooManyMethods:
53
+ exclude:
54
+ - Flows::Plugin::Profiler::Report::Tree::Node
@@ -1,11 +1,22 @@
1
1
  require:
2
2
  - rubocop-rspec
3
3
  - rubocop-performance
4
+ - rubocop-md
4
5
 
5
6
  AllCops:
6
- TargetRubyVersion: 2.6
7
+ TargetRubyVersion: 2.7
8
+ NewCops: enable
7
9
 
8
- LineLength:
10
+ Style/HashEachMethods:
11
+ Enabled: true
12
+
13
+ Style/HashTransformKeys:
14
+ Enabled: true
15
+
16
+ Style/HashTransformValues:
17
+ Enabled: true
18
+
19
+ Layout/LineLength:
9
20
  Max: 120
10
21
 
11
22
  Metrics/BlockLength:
@@ -20,3 +31,34 @@ Metrics/ParameterLists:
20
31
 
21
32
  Style/WhileUntilModifier:
22
33
  Enabled: false
34
+
35
+ Naming/MethodParameterName:
36
+ Exclude:
37
+ - '**/*.md'
38
+ - 'spec/**/*'
39
+
40
+ Style/MixinUsage:
41
+ Exclude:
42
+ - '**/*.md'
43
+
44
+ Lint/UnusedMethodArgument:
45
+ Exclude:
46
+ - '**/*.md'
47
+
48
+ Lint/UnusedBlockArgument:
49
+ Exclude:
50
+ - '**/*.md'
51
+
52
+ Style/CaseEquality:
53
+ Exclude:
54
+ - 'lib/flows/contract/**/*'
55
+
56
+ Naming/RescuedExceptionsVariableName:
57
+ PreferredName: err
58
+
59
+ Style/Documentation:
60
+ Exclude:
61
+ - 'lib/flows/shared_context_pipeline/step.rb' # false positive here
62
+
63
+ Style/SlicingWithRange:
64
+ Enabled: false # to support Ruby 2.5
@@ -1 +1 @@
1
- 2.6.2
1
+ 2.6.5
@@ -0,0 +1 @@
1
+ --markup markdown
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Types of changes:
9
+
10
+ * _Added_ - for new features.
11
+ * _Changed_ - for changes in existing functionality.
12
+ * _Deprecated_ - for soon-to-be removed features.
13
+ * _Removed_ - for now removed features.
14
+ * _Fixed_ -- for any bug fixes.
15
+ * _Security_ - in case of vulnerabilities.
16
+
17
+ ## [Unreleased]
18
+
19
+ ## [0.5.1] - 2020-06-29
20
+
21
+ ### Fixed
22
+
23
+ * `Flows::SharedContextPipeline` wrap DSL failure in case of inheritance, [issue](https://github.com/ffloyd/flows/issues/18)
24
+
25
+ ## [0.5.0] - 2020-05-18
26
+
27
+ ### Added
28
+
29
+ * `Flows::SharedContextPipeline` wrap DSL, [issue](https://github.com/ffloyd/flows/issues/7)
30
+ * `Flows::Flow` routing integrity check on initialization
31
+ * `Flows::Plugin::OutputContract` skip contract DSL method
32
+ * `Flows::Plugin::Profiler` introduced. Report types: raw, tree and flat.
33
+
34
+ ### Changed
35
+
36
+ * `Flows::SharedContextPipeline` callback API, [issue](https://github.com/ffloyd/flows/issues/6)
37
+ * `Flows::Util` modules API, [issue](https://github.com/ffloyd/flows/issues/11)
38
+ * `Flows::Contract::CaseEq` default error expanded to present more context
39
+
40
+ ## [0.4.0] - 2020-04-21
41
+
42
+ ### Added
43
+
44
+ * `Flows::Contract` - type contracts with specific transformation feature.
45
+ * `Flows::Flow` - fast and lightweight logic execution engine, designed for
46
+ internal usage and library writers.
47
+ * `Flows::Plugin::DependencyInjector` - simple dependency injection plugin for your classes
48
+ * `Flows::Plugin::ImplicitInit` - allows to use `MyClass.call` instead of
49
+ `MyClass.new.call`, an class instance will be created once.
50
+ * `Flows::Plugin::OutputContract` - plugin for output type checks and
51
+ transformations for service objects which return `Flows::Result`.
52
+ * `Flows::Railway` - stupid simple implementation of the railway pattern.
53
+ * `Flows::Result` - general purpose Result Object designed after Rust Result type.
54
+ * `Flows::Result::Do` - do-notation for Result Objects.
55
+ * `Flows::SharedContextPipeline` - much more flexible adoption of the railway
56
+ pattern for Ruby.
57
+ * `Flows::Util::InheritableSingletonVars` - allows to define behavior for
58
+ singleton variables in the context of inheritance.
59
+ * `Flows::Util::PrependToClass` - allows to prepend some module to class even if
60
+ target module did not included directly into class.
61
+
62
+ [unreleased]: https://github.com/ffloyd/flows/compare/v0.5.1...HEAD
63
+ [0.5.1]: https://github.com/ffloyd/flows/compare/v0.5.0...v0.5.1
64
+ [0.5.0]: https://github.com/ffloyd/flows/compare/v0.4.0...v0.5.0
65
+ [0.4.0]: https://github.com/ffloyd/flows/compare/v0.3.0...v0.4.0
data/README.md CHANGED
@@ -1,339 +1,269 @@
1
1
  # Flows
2
2
 
3
- [![Build Status](https://travis-ci.com/ffloyd/flows.svg?branch=master)](https://travis-ci.com/ffloyd/flows)
3
+ [![Build Status](https://github.com/ffloyd/flows/workflows/Test/badge.svg)](https://github.com/ffloyd/flows/actions)
4
4
  [![codecov](https://codecov.io/gh/ffloyd/flows/branch/master/graph/badge.svg)](https://codecov.io/gh/ffloyd/flows)
5
5
  [![Gem Version](https://badge.fury.io/rb/flows.svg)](https://badge.fury.io/rb/flows)
6
6
 
7
7
  Small and fast ruby framework for implementing railway-like operations.
8
- By design it close to [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/) and [Dry::Transaction](https://dry-rb.org/gems/dry-transaction/),
9
- but has more simpler and flexible DSL for defining operations and matching results. Also `flows` is significantly faster.
8
+ By design it is close to
9
+ [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/),
10
+ [Dry::Transaction](https://dry-rb.org/gems/dry-transaction/) and Rust control
11
+ flow style.
12
+ Flows has simple and flexible DSL for defining operations and matching results.
13
+ Also `flows` is faster than Ruby's alternatives.
10
14
 
11
- `flows` has no production dependencies so you can use it with any framework.
15
+ `flows` has no production dependencies so it can be used with any framework.
12
16
 
13
17
  ## Installation
14
18
 
15
19
  Add this line to your application's Gemfile:
16
20
 
17
21
  ```ruby
18
- gem 'flows'
22
+ gem 'flows', '~> 0.5'
19
23
  ```
20
24
 
21
25
  And then execute:
22
26
 
23
- $ bundle
27
+ ```sh
28
+ bundle
29
+ ```
24
30
 
25
31
  Or install it yourself as:
26
32
 
27
- $ gem install flows
33
+ ```sh
34
+ gem install flows
35
+ ```
28
36
 
29
- ## Usage
37
+ ## Supported Ruby versions
30
38
 
31
- ### `Flows::Flow`
39
+ CI tests against last patch versions every day:
32
40
 
33
- Low-level instrument for defining execution flows. Used internally as execution engine for `Flows::Operation`.
34
- Check out source code and specs for details.
41
+ * `MRI 2.5.x`
42
+ * `MRI 2.6.x`
35
43
 
36
- ### `Flows::Result`
44
+ `MRI 2.7.x` will be added later, right now (`2.7.1`) this version of MRI Ruby is too
45
+ unstable and produce segmentation faults inside RSpec internals.
37
46
 
38
- Result Object implementation. Inspired by [Dry::Monads::Result](https://dry-rb.org/gems/dry-monads/1.0/result/) and
39
- [Rust Result Objects](https://doc.rust-lang.org/1.30.0/book/2018-edition/ch09-02-recoverable-errors-with-result.html).
47
+ ## Usage & Documentation
40
48
 
41
- Main concepts & conventions:
49
+ * [YARD documentation](https://rubydoc.info/github/ffloyd/flows/master) - this
50
+ link is for master branch. You can also find YARD documentation for any released
51
+ version after `v0.4.0`. This documentation has a lot of examples, describes
52
+ motivation behind each abstraction, but lacks some guides and defined conventions.
53
+ * [Guides](https://ffloyd.github.io/flows/#/) - guides, conventions, integration
54
+ and migration notes. Will be done before `v1.0.0` release. Right now is under development.
42
55
 
43
- * separate classes for successful (`Flows::Result::Ok`) and failure (`Flows::Result::Err`) results
44
- * both classes has same parent class `Flows::Result`
45
- * result data should be a `Hash` with symbol keys and any values
46
- * result has a status
47
- * default status for successful results is `:success`
48
- * default status for failure results is `:failure`
56
+ ## Development
49
57
 
50
- Basic usage:
58
+ `Flows` is designed to be framework for your business logic. It is a big
59
+ responsibility. That's why `flows` has near to be sadistic development
60
+ conventions and linter setup.
51
61
 
52
- ```ruby
53
- # create successful result with data {a: 1, b: 2}
54
- result_ok = Flows::Result::Ok.new(a:1, b: 2)
62
+ ### Anyone can make Flows even better
55
63
 
56
- # get `:a` from result
57
- result_ok.unwrap[:a] # 1
64
+ If you see some typos or unclear things in documentation or code - feel free to open
65
+ an issue. Even if you don't have plans to implement a solution - a problem reporting
66
+ will help development much. We cannot fix what we don't know.
58
67
 
59
- # get error data from result
60
- result_ok.error[:a] # raises exception
68
+ ### [Lefthook](https://github.com/Arkweid/lefthook) as a git hook manager
61
69
 
62
- # get status from result
63
- result_ok.status # :success
70
+ Installation on MacOS via Homebrew:
64
71
 
65
- # boolean flags
66
- result_ok.ok? # true
67
- result_ok.err? # false
72
+ ```sh
73
+ brew install Arkweid/lefthook/lefthook
74
+ ```
68
75
 
69
- # create successful result with data {a: 1, b: 2} and status `:custom`
70
- result_ok_custom = Flows::Result::Ok.new({ a: 1, b: 2 }, status: :custom)
76
+ Activation (in the root of the repo):
71
77
 
72
- # get status from result
73
- result_ok_custom.status # :custom
78
+ ```sh
79
+ lefthook install
80
+ ```
74
81
 
75
- # create failure result with data {a: 1, b: 2}
76
- result_err = Flows::Result::Err.new(a:1, b: 2)
82
+ Run hooks manually:
77
83
 
78
- # get `:a` from result
79
- result_err.unwrap[:a] # raises exception
84
+ ```sh
85
+ lefthook run pre-commit
86
+ lefthook run pre-push
87
+ ```
80
88
 
81
- # get error data from result
82
- result_err.error[:a] # 1
89
+ Please, never turn off the pre-commit and pre-push hooks.
83
90
 
84
- # get status from result
85
- result_err.status # :failure
91
+ ### Rubocop linter
86
92
 
87
- # boolean flags
88
- result_ok.ok? # false
89
- result_ok.err? # true
93
+ [Rubocop](https://docs.rubocop.org/en/stable/) in this setup is responsible for:
90
94
 
91
- # create failure result with data {a: 1, b: 2} and status `:custom`
92
- result_err_custom = Flows::Result::Err.new({ a: 1, b: 2 }, status: :custom)
95
+ * defining code style (indentation, etc.)
96
+ * suggest performance improvements ([rubocop-performance](https://docs.rubocop.org/projects/performance/en/stable/))
97
+ * forces all that stuff (with some exceptions) to snippets in Markdown files ([rubocop-md](https://github.com/rubocop-hq/rubocop-md))
98
+ * forces unit-testing best practices ([rubocop-rspec](https://docs.rubocop.org/projects/rspec/en/latest/))
93
99
 
94
- # get status from result
95
- result_err_custom.status # :custom
96
- ```
100
+ Rubocop config for library and RSpec files should be close to standard one only
101
+ with minor amount of exceptions.
97
102
 
98
- Mixin `Flows::Result::Helpers` contains tools for simpler generating and matching Result Objects:
103
+ Code in Markdown snippets and `/bin` folder can ignore more rules. `/bin` folder
104
+ contains only development-related scripts and tools so it's ok to ease linter requirements.
99
105
 
100
- ```ruby
101
- include Flows::Result::Helpers
106
+ Rubocop Metrics (ABC-size, method/class length, etc) must not be eased
107
+ globally. Never.
102
108
 
103
- # create successful result with data {a: 1, b: 2}
104
- result_ok = ok(a:1, b: 2)
109
+ ### Reek linter
105
110
 
106
- # create successful result with data {a: 1, b: 2} and status `:custom`
107
- result_ok_custom = ok(:custom, a: 1, b: 2)
111
+ [Ruby Reek](https://github.com/troessner/reek) is a very aggressive linter that
112
+ forces you to do a clean OOP design.
108
113
 
109
- # create failure result with data {a: 1, b: 2}
110
- result_err = err(a:1, b: 2)
114
+ You will be tempted to just shut up this linter many times. But believe me, in 9
115
+ of 10 cases it worth to refactor. And after each such refactoring you will
116
+ understand OOP design better and better.
111
117
 
112
- # create failure result with data {a: 1, b: 2} and status `:custom`
113
- result_err_custom = err(:custom, a: 1, b: 2)
118
+ ### Rest of the linters
114
119
 
115
- # matching helpers
116
- result = ...
120
+ * [MDL](https://github.com/markdownlint/markdownlint) - for consistent format of Markdown files
121
+ * [forspell](https://github.com/kkuprikov/forspell) - for spellchecking in comments and markdown files
122
+ * [inch](http://rrrene.org/inch/) - for documentation coverage suggestions (the
123
+ only optional linter)
117
124
 
118
- case result
119
- when match_ok(:custom)
120
- # matches only successful results with status :custom
121
- when match_ok
122
- # matches only successful results with any status
123
- when match_err(:custom)
124
- # matches only failure results with status :custom
125
- when match_err
126
- # matches only failure results with any status
127
- end
128
- ```
125
+ ### Default Rake task and CI
129
126
 
130
- ### `Flows::Operation`
127
+ Default rake task (`bundle exec rake`) executes the following checks:
131
128
 
132
- Let's solve simple task using operation:
129
+ * Rubocop
130
+ * Ruby Reek
131
+ * RSpec
132
+ * Spellcheck (forspell)
133
+ * MarkdownLint (mdl)
133
134
 
134
- * given numbers `a` and `b`
135
- * result should contain sum of this numbers
136
- * result should contain square of this sum
135
+ CI is also performing default Rake task. So, if you want to reproduce CI error
136
+ locally - just run `bundle exec rake`.
137
137
 
138
- ```ruby
139
- class Summator
140
- # Make this class an operation by including this module.
141
- # It adds DSL, initializer and call method.
142
- # Also it includes Flows::Result::Helper both on DSL and instance level.
143
- include Flows::Operation
144
-
145
- # This is step definitions.
146
- # In simplest form step defined by its name and
147
- # step implementation expected to be in a method
148
- # with same name.
149
- #
150
- # Steps will be executed in a definition order.
151
- step :validate
152
- step :calc_sum
153
- step :calc_square
154
-
155
- # Which keys of operation data we want to expose on success
156
- ok_shape :sum, :sum_square
157
-
158
- # Which keys of operation data we want to expose on failure
159
- err_shape :message
160
-
161
- # Step implementation receives execution context as keyword arguments.
162
- # For the first step context equals to operation arguments.
163
- #
164
- # Step implementation must return Result Object.
165
- # Result Objects's data will be merged into operation context.
166
- #
167
- # If result is successful - next step will be executed.
168
- # If not - operation terminates and returns failure.
169
- def validate(a:, b:, **)
170
- err(message: 'a is not a number') if !a.is_a?(Number)
171
- err(message: 'b is not a number') if !b.is_a?(Number)
172
-
173
- ok
174
- end
175
-
176
- def calc_sum(a:, b:, **)
177
- ok(sum: a + b)
178
- end
179
-
180
- # We may get data from previous steps because all results' data are merged to context.
181
- def calc_square(sum:, **)
182
- ok(sum_square: a * b)
183
- end
184
- end
185
-
186
-
187
- # prepare operation
188
- operation = Summator.new
189
-
190
- # execute operation
191
- result = operation.call(a: 1, b: 2)
192
-
193
- result.ok? # true
194
- result.unwrap # { sum: 3, sum_square: 9 } - only keys from success shape present
195
-
196
-
197
- result = operation.call(a: nil, b: nil)
198
-
199
- result.ok? # false
200
- result.error # { message: 'a is not a number' } - only keys from error shape present
201
- ```
138
+ Default Rake task is also executed as a pre-push git hook.
202
139
 
203
- #### Result Shapes
140
+ ### Error reporting
204
141
 
205
- You may limit list of exposed fields by defining success and failure shapes. _After_ step definitions use `ok_shape` to define shapes of success result,
206
- and `err_shape` to define shapes of failure result. Examples:
142
+ I hope no one will argue that clear errors makes development noticeably faster.
143
+ That's why _each_ exception in `flows` should be clear and easy to read.
207
144
 
208
- ```ruby
209
- # Set exposed keys for :success status of successful result.
210
- #
211
- # Success result will have shape like { key1: ..., key2: ... }
212
- #
213
- # If one of keys is missing in the final operation context an exception will be raised.
214
- ok_shape :key1, :key2
215
-
216
- # Set different exposed keys for different statuses.
217
- #
218
- # Operation result status is a status of last executed step result.
219
- ok_shape status1: [:key1, :key2],
220
- status2: [:key3]
221
-
222
- # Failure shapes defined in the same way:
223
- err_shape :key1, :key2
224
- err_shape status1: [:key1, :key2],
225
- status2: [:key3]
226
- ```
145
+ This cannot be tested automatically: you only can test correctness
146
+ automatically, convenience can only be tested manually. That's why when you
147
+ introduce any new `raise` you have to:
227
148
 
228
- Operation definition should have exact one `ok_shape` DSL-call and zero or one `err_shape` DSL-call. If you want to disable shaping
229
- you can write `no_shape` DSL-call instead of shape definitions.
149
+ * make an error message clear and descriptive
150
+ * add this error to _errors demo CLI_ (`bin/errors`)
151
+ * add this errors to _all the errors demo_ (`bin/all_the_errors`)
152
+ * make sure that error is displayed correctly and follows a style of the rest
153
+ of implemented errors
230
154
 
231
- #### Routing & Tracks
155
+ `bin/errors` is done using [GLI](https://davetron5000.github.io/gli/) library,
156
+ run `bin/errors -h` to explore possibilities.
232
157
 
233
- You define side tracks, even nested ones:
158
+ ### Performance
234
159
 
235
- ```ruby
236
- step :outer_1 # next step is outer_2
237
-
238
- track :some_track do
239
- step :inner_1 # next step is inner_2
240
- track :inner_track do
241
- step :deep_1 # next step is deep_2
242
- step :deep_2 # next step is inner_2
243
- end
244
- step :inner_2 # next step in outer_2
245
- end
246
-
247
- step :outer_2
248
- ```
160
+ Ruby is slow. Moreover, Ruby is very slow. Yes, again. In the past time we had
161
+ to compare Ruby with Python. Python was faster and that's why people started to
162
+ complain about Ruby performance. That was fixed. But is Ruby fast nowadays? No.
163
+ Because languages like Clojure, Go, Rust, Elixir appeared and in comparison
164
+ with any of these languages Ruby is very very slow.
249
165
 
250
- In definition above tracks will not be used because there is no routes to this tracks. You may define routing like this:
166
+ That's why you **must** be extra careful with performance. Some business
167
+ operations can be executed hundreds or even thousands times per request. Each
168
+ line of code in your abstraction will slow down such request a bit. That's why
169
+ you should think about each line performance.
251
170
 
252
- ```ruby
253
- # if result is successful and has status :to_some_track - next step will be inner_1
254
- # for any other successful results - outer_2
255
- step :outer_1,
256
- match_ok(:to_some_track) => :some_track
257
-
258
- track :some_track do
259
- step :inner_1, match_err => :inner_track # redirect to inner_track on failure result
260
- track :inner_track do
261
- step :deep_1, match_ok(:some_status) => :outer_2 # you may redirect to steps too
262
- step :deep_2
263
- end
264
- step :inner_2
265
- end
266
-
267
- step :outer_2
268
- ```
171
+ Also, it's nearly impossible to make zero-cost abstractions in Ruby. The best
172
+ thing you can do - to offload calculations to a class loading or initialization
173
+ step. Sacrifice some warm-up time to make runtime performance better.
269
174
 
270
- #### Lambda Steps
175
+ And to compare performance overhead between different `flows` abstractions
176
+ and another alternatives a benchmarking CLI was done: `bin/benchmark`.
271
177
 
272
- You can use lambda for in-place step implementation:
178
+ This CLI is done using GLI, run `bin/benchmark -h` to explore possibilities.
273
179
 
274
- ```ruby
275
- step :name, ->(a:, b:, **) { ok(sum: a + b) }
276
- ```
180
+ So far, `flows` offers the best performance among alternatives. And this CLI
181
+ is made to simplify comparison with alternatives and keep `flows` the fastest solution.
277
182
 
278
- #### Dependency Injection
183
+ ### Documentation
279
184
 
280
- You can override or inject step implementation on initialization:
185
+ Each public API method or module **must** be properly documented with examples
186
+ and motivation behind.
281
187
 
282
- ```ruby
283
- class Summator
284
- include Flows::Operation
285
-
286
- step :sum
287
-
288
- ok_shape :sum
289
- end
290
-
291
- summator = Summator.new(deps: {
292
- sum: ->(a:, b:, **) { ok(sum: a + b) }
293
- })
294
-
295
- summator.call(a: 1, b: 2).unwrap[:sum] # 3
296
- ```
188
+ To run documentation server locally run `bin/docserver`.
297
189
 
298
- #### Wrapping steps
190
+ Respect `@since` YARD documentation tag. When some module, class or method has any
191
+ API change - you have to provide correct `@since` tag value to the documentation.
299
192
 
300
- You can wrap several steps with some logic:
193
+ ### Documentation Driven Development
301
194
 
302
- ```ruby
303
- step :first
304
-
305
- wrap :wrapper do
306
- step :wrapped
307
- end
308
-
309
- def wrapper(**context)
310
- # do smth
311
- result = yield # execute wrapped steps
312
- # do smth or modify result
313
- result
314
- end
315
- ```
195
+ When you about to do some work, the following guideline can lead to the best
196
+ results:
316
197
 
317
- There is routing limitation when you use wrap:
198
+ * first, write needed class and method structure without implementation
199
+ * write YARD documentation with motivation and usage examples for each public
200
+ class, method, module.
201
+ * write unit tests, check that tests are failing
202
+ * write implementation until tests are green
318
203
 
319
- * outside `wrap` block you may route to wrapped block by wrapper name (`:wrapper` in the provided example)
320
- * you may route wrapped steps only to wrapped steps in the same wrap block
321
- * you cannot route to wrapped steps from outside
204
+ Yes, it's TDD approach with documentation step prepended.
322
205
 
323
- ## Development
206
+ ### Unit test
207
+
208
+ Each public API method or module **must** be properly tested. Internal modules
209
+ can be tested indirectly through public API.
210
+
211
+ Test coverage **must** be higher than 95%.
212
+
213
+ ### Commit naming
214
+
215
+ You **must** follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
216
+
217
+ Allowed prefixes since `v0.4.0`:
218
+
219
+ * `feat:` - for new features
220
+ * `fix:` - for bugfixes
221
+ * `perf:` - for performance improvements
222
+ * `refactor:` - for refactoring work
223
+ * `ci:` - updates for CI configuration
224
+ * `docs:` - for documentation update
225
+
226
+ Sometimes commit can have several responsibilities. As example: when you write
227
+ documentation, test and implementation for a feature in the one commit. You can do
228
+ extra effort to split and rearrange commits to make it atomic. But does it
229
+ really provide significant value if we already have a strong convention for
230
+ changelog (see the next section)?
231
+
232
+ So, when you in such situation use the first applicable prefix in the list:
233
+ between `docs` and `refactor` - pick `refactor`.
234
+
235
+ Also, there is one more special prefix for release commits. Release commit
236
+ messages **must** look like: `release: v0.4.0`.
237
+
238
+ ### Changelog
239
+
240
+ Starting from `v0.4.0` [keep a changelog](https://keepachangelog.com/en/1.0.0/)
241
+ guideline must be met.
242
+
243
+ If you adding something - provide some lines to the unreleased section of the `CHANGELOG.md`.
244
+
245
+ ### Versioning
324
246
 
325
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
247
+ The project strictly follows [SemVer](https://semver.org/spec/v2.0.0.html).
326
248
 
327
- To install this gem onto your local machine, run `bundle exec rake install`.
249
+ After `v1.0.0` even smallest backward incompatible change will bump major
250
+ version. _No exceptions._
328
251
 
329
- ## Contributing
252
+ Commit with a version bump should contain _only_ version bump and CHANGELOG.md update.
330
253
 
331
- Bug reports and pull requests are welcome on GitHub at https://github.com/ffloyd/flows. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
254
+ ### GitHub Flow
332
255
 
333
- ## License
256
+ Since `v0.4.0` this repo strictly follow [GitHub
257
+ Flow](https://guides.github.com/introduction/flow/) with some additions:
334
258
 
335
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
259
+ * branch naming using dash: `improved-contexts`
260
+ * use [references to
261
+ issues](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
262
+ in commit messages and make links to issues in CHANGELOG.md
336
263
 
337
- ## Code of Conduct
264
+ ### Planned features for v1.0.0
338
265
 
339
- Everyone interacting in the Flows project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ffloyd/flows/blob/master/CODE_OF_CONDUCT.md).
266
+ * validation framework
267
+ * error reporting improvements
268
+ * various plugins for SCP (tracing, benchmarking, logging, etc)
269
+ * site with guides and conventions