pathway 0.12.2 → 1.0.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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +2 -2
- data/CHANGELOG.md +13 -0
- data/README.md +3 -7
- data/lib/pathway/plugins/auto_deconstruct_state.rb +14 -4
- data/lib/pathway/plugins/dry_validation.rb +73 -12
- data/lib/pathway/plugins/responder.rb +3 -5
- data/lib/pathway/plugins/sequel_models.rb +15 -15
- data/lib/pathway/plugins/simple_auth.rb +1 -3
- data/lib/pathway/result.rb +16 -42
- data/lib/pathway/rspec/matchers/fail_on.rb +6 -6
- data/lib/pathway/version.rb +1 -1
- data/lib/pathway.rb +46 -61
- data/pathway.gemspec +4 -4
- metadata +22 -29
- data/lib/pathway/plugins/auto_deconstruct_state/ruby3.rb +0 -22
- data/lib/pathway/plugins/dry_validation/v0_11.rb +0 -86
- data/lib/pathway/plugins/dry_validation/v0_12.rb +0 -86
- data/lib/pathway/plugins/dry_validation/v1_0.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc09be062809a2afe5a1874e1da97c15be0353059116e2932d8224d0857b3899
|
4
|
+
data.tar.gz: dbc788f70ba9f95bfa700c790aff1a1c2313b2802b58417e4598314263c2af6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e39b0e7702a87427d242f722deb03ccf18186bb47e271a1fbb2c218bd1bde948db7fece51805a2b8de0c3f232273f624dd4c28cf743a50aa83bf00262c161cf2
|
7
|
+
data.tar.gz: 2b528a4f6c8f8172294265fafe0e3751c62a3412bc67a2deeef09f124dd38a419302502e7c7be77ead661088b3ccb54c2eb0f35c65e848a0e5947117470d294f
|
data/.github/workflows/tests.yml
CHANGED
@@ -11,7 +11,7 @@ jobs:
|
|
11
11
|
runs-on: ubuntu-latest
|
12
12
|
strategy:
|
13
13
|
matrix:
|
14
|
-
ruby-version: [
|
14
|
+
ruby-version: [3.1, 3.2, 3.3]
|
15
15
|
steps:
|
16
16
|
- uses: actions/checkout@v3
|
17
17
|
- name: Set up Ruby
|
@@ -23,7 +23,7 @@ jobs:
|
|
23
23
|
- name: Run tests
|
24
24
|
run: bundle exec rake
|
25
25
|
- name: Coveralls GitHub Action
|
26
|
-
if: matrix.ruby-version == '3.
|
26
|
+
if: matrix.ruby-version == '3.3'
|
27
27
|
uses: coverallsapp/github-action@v2
|
28
28
|
with:
|
29
29
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## [1.0.0] - 2025-05-19
|
2
|
+
### Changed
|
3
|
+
- Removed support for `Ruby` versions older than 3.0
|
4
|
+
- Removed support for `dry-validation` versions older than 1.0
|
5
|
+
|
6
|
+
## [0.12.3] - 2024-08-13
|
7
|
+
### Changed
|
8
|
+
- Renamed config option `:auto_wire_options` to `:auto_wire` at `:dry_validation` plugin
|
9
|
+
- Updated `Pathway::State#use` to accept block with postional parameters
|
10
|
+
- Updated `Pathway::State#use` to raise an `ArgumentError` exception on invalid arguments
|
11
|
+
### Added
|
12
|
+
- Provide alias `Pathway::State#use` to `Pathway::State#unwrap`
|
13
|
+
|
1
14
|
## [0.12.2] - 2024-08-06
|
2
15
|
### Added
|
3
16
|
- Add `Pathway::State#unwrap` and `Pathway::State#u` to access internal state
|
data/README.md
CHANGED
@@ -407,12 +407,12 @@ end
|
|
407
407
|
|
408
408
|
If you are familiar with `dry-validation` you probably know it provides a way to [inject options](https://dry-rb.org/gems/dry-validation/1.4/external-dependencies/) before calling the contract.
|
409
409
|
|
410
|
-
In those scenarios, you must either set the `
|
410
|
+
In those scenarios, you must either set the `auto_wire: true` plugin argument or specify how to map options from the execution state to the contract when calling `step :validate`.
|
411
411
|
Lets see and example for the first case:
|
412
412
|
|
413
413
|
```ruby
|
414
414
|
class CreateNugget < Pathway::Operation
|
415
|
-
plugin :dry_validation,
|
415
|
+
plugin :dry_validation, auto_wire: true
|
416
416
|
|
417
417
|
context :user_name
|
418
418
|
|
@@ -438,7 +438,7 @@ class CreateNugget < Pathway::Operation
|
|
438
438
|
end
|
439
439
|
```
|
440
440
|
|
441
|
-
Here the defined contract needs a `:user_name` option, so we tell the operation to grab the attribute with the same name from the state by activating `:
|
441
|
+
Here the defined contract needs a `:user_name` option, so we tell the operation to grab the attribute with the same name from the state by activating `:auto_wire`, afterwards, when the validation runs, the contract will already have the user name available.
|
442
442
|
|
443
443
|
Mind you, this option is `false` by default, so be sure to set it to `true` at `Pathway::Operation` if you'd rather have it enabled for all your operations.
|
444
444
|
|
@@ -474,10 +474,6 @@ end
|
|
474
474
|
|
475
475
|
The `with:` parameter can always be specified for `step :validate`, and allows you to override the default mapping regardless if auto-wiring is active or not.
|
476
476
|
|
477
|
-
##### Older versions of `dry-validation`
|
478
|
-
|
479
|
-
Pathway supports the `dry-validation` gem down to version `0.11` (inclusive) in case you still have unmigrated code. When using versions below `1.0` the concept of contract is not present and instead of calling the `contract` method to set up your validation logic, you must use the `form` method. Everything else remains the same except, obviously, that you would have to use `dry-definition`'s [old API](https://dry-rb.org/gems/dry-validation/0.13/) which is a bit different from the current one.
|
480
|
-
|
481
477
|
#### `SimpleAuth` plugin
|
482
478
|
|
483
479
|
This very simple plugin adds a custom step called `:authorize`, that can be used to check for permissions and halt the operation with a `:forbidden` error when they aren't fulfilled.
|
@@ -1,12 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
if RUBY_VERSION =~ /^3\./
|
4
|
-
require 'pathway/plugins/auto_deconstruct_state/ruby3'
|
5
|
-
end
|
6
|
-
|
7
3
|
module Pathway
|
8
4
|
module Plugins
|
9
5
|
module AutoDeconstructState
|
6
|
+
module DSLMethods
|
7
|
+
private
|
8
|
+
|
9
|
+
def _callable(callable)
|
10
|
+
if callable.is_a?(Symbol) && @operation.respond_to?(callable, true) &&
|
11
|
+
@operation.method(callable).arity != 0 &&
|
12
|
+
@operation.method(callable).parameters.all? { _1 in [:key|:keyreq|:keyrest|:block,*] }
|
13
|
+
|
14
|
+
-> state { @operation.send(callable, **state) }
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
10
20
|
end
|
11
21
|
end
|
12
22
|
end
|
@@ -5,21 +5,82 @@ require 'dry/validation'
|
|
5
5
|
module Pathway
|
6
6
|
module Plugins
|
7
7
|
module DryValidation
|
8
|
-
|
8
|
+
module ClassMethods
|
9
|
+
attr_reader :contract_class, :contract_options
|
10
|
+
attr_accessor :auto_wire
|
11
|
+
|
12
|
+
alias_method :auto_wire_options, :auto_wire
|
13
|
+
alias_method :auto_wire_options=, :auto_wire=
|
14
|
+
|
15
|
+
def contract(base = nil, &block)
|
16
|
+
if block_given?
|
17
|
+
base ||= _base_contract
|
18
|
+
self.contract_class = Class.new(base, &block)
|
19
|
+
elsif base
|
20
|
+
self.contract_class = base
|
21
|
+
else
|
22
|
+
raise ArgumentError, 'Either a contract class or a block must be provided'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def params(*args, **kwargs, &block)
|
27
|
+
contract { params(*args, **kwargs, &block) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def contract_class= klass
|
31
|
+
@contract_class = klass
|
32
|
+
@contract_options = (klass.dry_initializer.options - Dry::Validation::Contract.dry_initializer.options).map(&:target)
|
33
|
+
@builded_contract = @contract_options.empty? && klass.schema ? klass.new : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_contract(**opts)
|
37
|
+
@builded_contract || contract_class.new(**opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def inherited(subclass)
|
41
|
+
super
|
42
|
+
subclass.auto_wire = auto_wire
|
43
|
+
subclass.contract_class = contract_class
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def _base_contract
|
49
|
+
superclass.respond_to?(:contract_class) ? superclass.contract_class : Dry::Validation::Contract
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module InstanceMethods
|
54
|
+
extend Forwardable
|
55
|
+
|
56
|
+
delegate %i[build_contract contract_options auto_wire_options auto_wire] => 'self.class'
|
57
|
+
alias_method :contract, :build_contract
|
58
|
+
|
59
|
+
def validate(state, with: nil)
|
60
|
+
if auto_wire && contract_options.any?
|
61
|
+
with ||= contract_options.zip(contract_options).to_h
|
62
|
+
end
|
63
|
+
opts = Hash(with).map { |to, from| [to, state[from]] }.to_h
|
64
|
+
validate_with(state[:input], **opts)
|
65
|
+
.then { |params| state.update(params:) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_with(input, **opts)
|
69
|
+
result = contract(**opts).call(input)
|
70
|
+
|
71
|
+
result.success? ? wrap(result.values.to_h) : error(:validation, details: result.errors.to_h)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.apply(operation, auto_wire_options: (auto_wire_options_was_not_used=true; false), auto_wire: auto_wire_options)
|
9
76
|
#:nocov:
|
10
|
-
|
11
|
-
|
12
|
-
elsif Gem.loaded_specs['dry-validation'].version < Gem::Version.new('0.12')
|
13
|
-
require 'pathway/plugins/dry_validation/v0_11'
|
14
|
-
operation.plugin(Plugins::DryValidation::V0_11, **kwargs)
|
15
|
-
elsif Gem.loaded_specs['dry-validation'].version < Gem::Version.new('1.0')
|
16
|
-
require 'pathway/plugins/dry_validation/v0_12'
|
17
|
-
operation.plugin(Plugins::DryValidation::V0_12, **kwargs)
|
18
|
-
else
|
19
|
-
require 'pathway/plugins/dry_validation/v1_0'
|
20
|
-
operation.plugin(Plugins::DryValidation::V1_0, **kwargs)
|
77
|
+
unless auto_wire_options_was_not_used
|
78
|
+
warn "[DEPRECATION] `auto_wire_options` is deprecated. Please use `auto_wire` instead"
|
21
79
|
end
|
22
80
|
#:nocov:
|
81
|
+
|
82
|
+
operation.auto_wire = auto_wire
|
83
|
+
operation.contract_class = Dry::Validation::Contract
|
23
84
|
end
|
24
85
|
end
|
25
86
|
end
|
@@ -4,8 +4,8 @@ module Pathway
|
|
4
4
|
module Plugins
|
5
5
|
module Responder
|
6
6
|
module ClassMethods
|
7
|
-
|
8
|
-
result = super(*args)
|
7
|
+
def call(*args, **kwargs, &bl)
|
8
|
+
result = super(*args, **kwargs)
|
9
9
|
block_given? ? Responder.respond(result, &bl) : result
|
10
10
|
end
|
11
11
|
end
|
@@ -21,9 +21,7 @@ module Pathway
|
|
21
21
|
instance_eval(&bl)
|
22
22
|
end
|
23
23
|
|
24
|
-
def success(&bl)
|
25
|
-
@ok = bl
|
26
|
-
end
|
24
|
+
def success(&bl)= @ok = bl
|
27
25
|
|
28
26
|
def failure(type = nil, &bl)
|
29
27
|
if type.nil?
|
@@ -6,33 +6,33 @@ module Pathway
|
|
6
6
|
module Plugins
|
7
7
|
module SequelModels
|
8
8
|
module DSLMethods
|
9
|
-
def transaction(step_name = nil, &
|
10
|
-
|
9
|
+
def transaction(step_name = nil, &dsl_bl)
|
10
|
+
raise 'must provide a step or a block but not both' if !step_name.nil? == block_given?
|
11
11
|
|
12
12
|
if step_name
|
13
13
|
transaction { step step_name }
|
14
14
|
else
|
15
|
-
around(->
|
15
|
+
around(->(runner, _) {
|
16
16
|
db.transaction(savepoint: true) do
|
17
|
-
raise Sequel::Rollback if
|
17
|
+
raise Sequel::Rollback if runner.call.failure?
|
18
18
|
end
|
19
|
-
}, &
|
19
|
+
}, &dsl_bl)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def after_commit(step_name = nil, &
|
24
|
-
|
23
|
+
def after_commit(step_name = nil, &dsl_bl)
|
24
|
+
raise 'must provide a step or a block but not both' if !step_name.nil? == block_given?
|
25
25
|
|
26
26
|
if step_name
|
27
27
|
after_commit { step step_name }
|
28
28
|
else
|
29
|
-
around(->
|
30
|
-
|
29
|
+
around(->(runner, state) {
|
30
|
+
dsl_copy = self.class::DSL.new(State.new(self, state.to_h.dup), self)
|
31
31
|
|
32
32
|
db.after_commit do
|
33
|
-
|
33
|
+
runner.call(dsl_copy)
|
34
34
|
end
|
35
|
-
}, &
|
35
|
+
}, &dsl_bl)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -41,10 +41,10 @@ module Pathway
|
|
41
41
|
attr_accessor :model_class, :search_field, :model_not_found
|
42
42
|
|
43
43
|
def model(model_class, search_by: model_class.primary_key, set_result_key: true, set_context_param: true, error_message: nil)
|
44
|
-
self.model_class
|
45
|
-
self.search_field
|
46
|
-
self.result_key
|
47
|
-
self.model_not_found
|
44
|
+
self.model_class = model_class
|
45
|
+
self.search_field = search_by
|
46
|
+
self.result_key = Inflector.underscore(Inflector.demodulize(model_class.name)).to_sym if set_result_key
|
47
|
+
self.model_not_found = error_message || "#{Inflector.humanize(Inflector.underscore(Inflector.demodulize(model_class.name)))} not found".freeze
|
48
48
|
|
49
49
|
self.context(result_key => Contextualizer::OPTIONAL) if set_result_key && set_context_param
|
50
50
|
end
|
data/lib/pathway/result.rb
CHANGED
@@ -6,13 +6,8 @@ module Pathway
|
|
6
6
|
attr_reader :value, :error
|
7
7
|
|
8
8
|
class Success < Result
|
9
|
-
def initialize(value)
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def success?
|
14
|
-
true
|
15
|
-
end
|
9
|
+
def initialize(value) = @value = value
|
10
|
+
def success? = true
|
16
11
|
|
17
12
|
def then(bl=nil)
|
18
13
|
result(block_given? ? yield(value): bl.call(value))
|
@@ -25,29 +20,18 @@ module Pathway
|
|
25
20
|
|
26
21
|
private
|
27
22
|
|
28
|
-
|
23
|
+
alias_method :value_for_deconstruct, :value
|
29
24
|
end
|
30
25
|
|
31
26
|
class Failure < Result
|
32
|
-
def initialize(error)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def success?
|
37
|
-
false
|
38
|
-
end
|
39
|
-
|
40
|
-
def then(_=nil)
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
44
|
-
def tee(_=nil)
|
45
|
-
self
|
46
|
-
end
|
27
|
+
def initialize(error) = @error = error
|
28
|
+
def success? = false
|
29
|
+
def then(_=nil) = self
|
30
|
+
def tee(_=nil) = self
|
47
31
|
|
48
32
|
private
|
49
33
|
|
50
|
-
|
34
|
+
alias_method :value_for_deconstruct, :error
|
51
35
|
end
|
52
36
|
|
53
37
|
module Mixin
|
@@ -55,10 +39,16 @@ module Pathway
|
|
55
39
|
Failure = Result::Failure
|
56
40
|
end
|
57
41
|
|
58
|
-
def
|
59
|
-
|
42
|
+
def self.success(value) = Success.new(value)
|
43
|
+
def self.failure(error) = Failure.new(error)
|
44
|
+
|
45
|
+
def self.result(object)
|
46
|
+
object.is_a?(Result) ? object : success(object)
|
60
47
|
end
|
61
48
|
|
49
|
+
def failure? = !success?
|
50
|
+
def deconstruct = [value_for_deconstruct]
|
51
|
+
|
62
52
|
def deconstruct_keys(keys)
|
63
53
|
if value_for_deconstruct.respond_to?(:deconstruct_keys)
|
64
54
|
value_for_deconstruct.deconstruct_keys(keys)
|
@@ -67,22 +57,6 @@ module Pathway
|
|
67
57
|
end
|
68
58
|
end
|
69
59
|
|
70
|
-
def failure?
|
71
|
-
!success?
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.success(value)
|
75
|
-
Success.new(value)
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.failure(error)
|
79
|
-
Failure.new(error)
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.result(object)
|
83
|
-
object.is_a?(Result) ? object : success(object)
|
84
|
-
end
|
85
|
-
|
86
60
|
delegate :result => 'self.class'
|
87
61
|
end
|
88
62
|
end
|
@@ -22,22 +22,22 @@ RSpec::Matchers.define :fail_on do |input|
|
|
22
22
|
@type = type
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
alias_method :with_type, :type
|
26
|
+
alias_method :and_type, :type
|
27
27
|
|
28
28
|
chain :message do |message|
|
29
29
|
@message = message
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
32
|
+
alias_method :with_message, :message
|
33
|
+
alias_method :and_message, :message
|
34
34
|
|
35
35
|
chain :details do |details|
|
36
36
|
@details = details
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
alias_method :with_details, :details
|
40
|
+
alias_method :and_details, :details
|
41
41
|
|
42
42
|
description do
|
43
43
|
'fail' + (@type ? " with :#@type error" : '')
|
data/lib/pathway/version.rb
CHANGED
data/lib/pathway.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'ruby2_keywords'
|
4
3
|
require 'forwardable'
|
5
4
|
require 'dry/inflector'
|
6
5
|
require 'contextualizer'
|
@@ -11,7 +10,7 @@ module Pathway
|
|
11
10
|
Inflector = Dry::Inflector.new
|
12
11
|
class Operation
|
13
12
|
class << self
|
14
|
-
|
13
|
+
def plugin(name,...)
|
15
14
|
require "pathway/plugins/#{Inflector.underscore(name)}" if name.is_a?(Symbol)
|
16
15
|
|
17
16
|
plugin = name.is_a?(Module) ? name : Plugins.const_get(Inflector.camelize(name))
|
@@ -20,7 +19,7 @@ module Pathway
|
|
20
19
|
self.include plugin::InstanceMethods if plugin.const_defined? :InstanceMethods
|
21
20
|
self::DSL.include plugin::DSLMethods if plugin.const_defined? :DSLMethods
|
22
21
|
|
23
|
-
plugin.apply(self
|
22
|
+
plugin.apply(self,...) if plugin.respond_to?(:apply)
|
24
23
|
end
|
25
24
|
|
26
25
|
def inherited(subclass)
|
@@ -45,13 +44,8 @@ module Pathway
|
|
45
44
|
@details = details || {}
|
46
45
|
end
|
47
46
|
|
48
|
-
def deconstruct
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def deconstruct_keys(_)
|
53
|
-
{ type: type, message: message, details: details }
|
54
|
-
end
|
47
|
+
def deconstruct = [type, message, details]
|
48
|
+
def deconstruct_keys(_) = { type:, message:, details: }
|
55
49
|
|
56
50
|
private
|
57
51
|
|
@@ -62,64 +56,61 @@ module Pathway
|
|
62
56
|
|
63
57
|
class State
|
64
58
|
extend Forwardable
|
59
|
+
delegate %i([] []= fetch store include? values_at deconstruct_keys) => :@hash
|
65
60
|
|
66
61
|
def initialize(operation, values = {})
|
67
62
|
@hash = operation.context.merge(values)
|
68
63
|
@result_key = operation.result_key
|
69
64
|
end
|
70
65
|
|
71
|
-
delegate %i([] []= fetch store include? values_at deconstruct_keys) => :@hash
|
72
|
-
|
73
66
|
def update(kargs)
|
74
67
|
@hash.update(kargs)
|
75
68
|
self
|
76
69
|
end
|
77
70
|
|
78
|
-
def result
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
def to_hash
|
83
|
-
@hash
|
84
|
-
end
|
71
|
+
def result = @hash[@result_key]
|
72
|
+
def to_hash = @hash
|
85
73
|
|
86
|
-
def
|
87
|
-
raise 'a block must be provided' if !block_given?
|
88
|
-
|
89
|
-
|
90
|
-
raise 'only keyword arguments are supported for unwraping'
|
74
|
+
def use(&bl)
|
75
|
+
raise ArgumentError, 'a block must be provided' if !block_given?
|
76
|
+
if bl.parameters in [*, [:rest|:keyrest,], *]
|
77
|
+
raise ArgumentError, 'rest arguments are not supported'
|
91
78
|
end
|
92
79
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
80
|
+
keys = bl.parameters.select { _1 in :key|:keyreq, }.map(&:last)
|
81
|
+
names = bl.parameters.select { _1 in :req|:opt, }.map(&:last)
|
82
|
+
|
83
|
+
if keys.any? && names.any?
|
84
|
+
raise ArgumentError, 'cannot mix positional and keyword arguments'
|
85
|
+
elsif keys.any?
|
97
86
|
bl.call(**to_hash.slice(*keys))
|
87
|
+
else
|
88
|
+
bl.call(*to_hash.values_at(*names))
|
98
89
|
end
|
99
90
|
end
|
100
91
|
|
101
92
|
alias_method :to_h, :to_hash
|
102
|
-
alias_method :u, :
|
93
|
+
alias_method :u, :use
|
94
|
+
alias_method :unwrap, :use
|
103
95
|
end
|
104
96
|
|
105
97
|
module Plugins
|
106
98
|
module Base
|
107
99
|
module ClassMethods
|
108
100
|
attr_accessor :result_key
|
109
|
-
|
101
|
+
|
102
|
+
alias_method :result_at, :result_key=
|
110
103
|
|
111
104
|
def process(&bl)
|
112
105
|
dsl = self::DSL
|
113
106
|
define_method(:call) do |input|
|
114
|
-
dsl.new(State.new(self, input:
|
107
|
+
dsl.new(State.new(self, input:), self)
|
115
108
|
.run(&bl)
|
116
109
|
.then(&:result)
|
117
110
|
end
|
118
111
|
end
|
119
112
|
|
120
|
-
|
121
|
-
new(ctx).call(*params)
|
122
|
-
end
|
113
|
+
def call(ctx,...) = new(ctx).call(...)
|
123
114
|
|
124
115
|
def inherited(subclass)
|
125
116
|
super
|
@@ -133,18 +124,16 @@ module Pathway
|
|
133
124
|
delegate :result_key => 'self.class'
|
134
125
|
delegate %i[result success failure] => Result
|
135
126
|
|
136
|
-
|
127
|
+
alias_method :wrap, :result
|
137
128
|
|
138
|
-
def call(*)
|
139
|
-
fail 'must implement at subclass'
|
140
|
-
end
|
129
|
+
def call(*) = raise 'must implement at subclass'
|
141
130
|
|
142
131
|
def error(type, message: nil, details: nil)
|
143
|
-
failure(Error.new(type
|
132
|
+
failure(Error.new(type:, message:, details:))
|
144
133
|
end
|
145
134
|
|
146
135
|
def wrap_if_present(value, type: :not_found, message: nil, details: {})
|
147
|
-
value.nil? ? error(type, message
|
136
|
+
value.nil? ? error(type, message:, details:) : success(value)
|
148
137
|
end
|
149
138
|
end
|
150
139
|
|
@@ -164,45 +153,43 @@ module Pathway
|
|
164
153
|
end
|
165
154
|
|
166
155
|
# Execute step and preserve the former state
|
167
|
-
|
156
|
+
def step(callable,...)
|
168
157
|
bl = _callable(callable)
|
169
|
-
|
170
|
-
@result = @result.tee { |state| bl.call(state, *args) }
|
158
|
+
@result = @result.tee { |state| bl.call(state,...) }
|
171
159
|
end
|
172
160
|
|
173
161
|
# Execute step and modify the former state setting the key
|
174
|
-
def set(callable, *args, to: @operation.result_key)
|
162
|
+
def set(callable, *args, to: @operation.result_key, **kwargs, &bl)
|
175
163
|
bl = _callable(callable)
|
176
164
|
|
177
165
|
@result = @result.then do |state|
|
178
|
-
wrap(bl.call(state, *args))
|
166
|
+
wrap(bl.call(state, *args, **kwargs, &bl))
|
179
167
|
.then { |value| state.update(to => value) }
|
180
168
|
end
|
181
169
|
end
|
182
170
|
|
183
171
|
# Execute step and replace the current state completely
|
184
|
-
def map(callable)
|
172
|
+
def map(callable,...)
|
185
173
|
bl = _callable(callable)
|
186
|
-
@result = @result.then(
|
174
|
+
@result = @result.then { |state| bl.call(state,...) }
|
187
175
|
end
|
188
176
|
|
189
|
-
def around(
|
177
|
+
def around(execution_strategy, &dsl_block)
|
190
178
|
@result.then do |state|
|
191
|
-
|
192
|
-
|
179
|
+
dsl_runner = ->(dsl = self) { @result = dsl.run(&dsl_block) }
|
180
|
+
|
181
|
+
_callable(execution_strategy).call(dsl_runner, state)
|
193
182
|
end
|
194
183
|
end
|
195
184
|
|
196
|
-
def if_true(cond, &
|
185
|
+
def if_true(cond, &dsl_block)
|
197
186
|
cond = _callable(cond)
|
198
|
-
around(->
|
199
|
-
seq.call if cond.call(state)
|
200
|
-
}, &steps)
|
187
|
+
around(->(dsl_runner, state) { dsl_runner.call if cond.call(state) }, &dsl_block)
|
201
188
|
end
|
202
189
|
|
203
|
-
def if_false(cond, &
|
190
|
+
def if_false(cond, &dsl_block)
|
204
191
|
cond = _callable(cond)
|
205
|
-
if_true(->
|
192
|
+
if_true(->(state) { !cond.call(state) }, &dsl_block)
|
206
193
|
end
|
207
194
|
|
208
195
|
alias_method :sequence, :around
|
@@ -210,16 +197,14 @@ module Pathway
|
|
210
197
|
|
211
198
|
private
|
212
199
|
|
213
|
-
def wrap(obj)
|
214
|
-
Result.result(obj)
|
215
|
-
end
|
200
|
+
def wrap(obj) = Result.result(obj)
|
216
201
|
|
217
202
|
def _callable(callable)
|
218
203
|
case callable
|
219
204
|
when Proc
|
220
|
-
->
|
205
|
+
->(*args, **kwargs) { @operation.instance_exec(*args, **kwargs, &callable) }
|
221
206
|
when Symbol
|
222
|
-
->
|
207
|
+
->(*args, **kwargs) { @operation.send(callable, *args, **kwargs) }
|
223
208
|
else
|
224
209
|
callable
|
225
210
|
end
|
data/pathway.gemspec
CHANGED
@@ -27,13 +27,12 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.required_ruby_version = ">=
|
30
|
+
spec.required_ruby_version = ">= 3.1.0"
|
31
31
|
|
32
32
|
spec.add_dependency "dry-inflector", ">= 0.1.0"
|
33
|
-
spec.add_dependency "contextualizer", "~> 0.0
|
34
|
-
spec.add_dependency "ruby2_keywords"
|
33
|
+
spec.add_dependency "contextualizer", "~> 0.1.0"
|
35
34
|
|
36
|
-
spec.add_development_dependency "dry-validation", ">= 0
|
35
|
+
spec.add_development_dependency "dry-validation", ">= 1.0"
|
37
36
|
spec.add_development_dependency "bundler", ">= 2.4.10"
|
38
37
|
spec.add_development_dependency "sequel", "~> 5.0"
|
39
38
|
spec.add_development_dependency "rake", "~> 13.0"
|
@@ -41,6 +40,7 @@ Gem::Specification.new do |spec|
|
|
41
40
|
spec.add_development_dependency "simplecov-lcov", '~> 0.8.0'
|
42
41
|
spec.add_development_dependency "simplecov"
|
43
42
|
spec.add_development_dependency "pry"
|
43
|
+
spec.add_development_dependency "byebug"
|
44
44
|
spec.add_development_dependency "pry-byebug"
|
45
45
|
spec.add_development_dependency "pry-doc"
|
46
46
|
spec.add_development_dependency "pry-stack"
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pathway
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pablo Herrero
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: dry-inflector
|
@@ -30,42 +29,28 @@ dependencies:
|
|
30
29
|
requirements:
|
31
30
|
- - "~>"
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.0
|
32
|
+
version: 0.1.0
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - "~>"
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.0
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: ruby2_keywords
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
39
|
+
version: 0.1.0
|
55
40
|
- !ruby/object:Gem::Dependency
|
56
41
|
name: dry-validation
|
57
42
|
requirement: !ruby/object:Gem::Requirement
|
58
43
|
requirements:
|
59
44
|
- - ">="
|
60
45
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0
|
46
|
+
version: '1.0'
|
62
47
|
type: :development
|
63
48
|
prerelease: false
|
64
49
|
version_requirements: !ruby/object:Gem::Requirement
|
65
50
|
requirements:
|
66
51
|
- - ">="
|
67
52
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0
|
53
|
+
version: '1.0'
|
69
54
|
- !ruby/object:Gem::Dependency
|
70
55
|
name: bundler
|
71
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +149,20 @@ dependencies:
|
|
164
149
|
- - ">="
|
165
150
|
- !ruby/object:Gem::Version
|
166
151
|
version: '0'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: byebug
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
type: :development
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
167
166
|
- !ruby/object:Gem::Dependency
|
168
167
|
name: pry-byebug
|
169
168
|
requirement: !ruby/object:Gem::Requirement
|
@@ -228,11 +227,7 @@ files:
|
|
228
227
|
- bin/setup
|
229
228
|
- lib/pathway.rb
|
230
229
|
- lib/pathway/plugins/auto_deconstruct_state.rb
|
231
|
-
- lib/pathway/plugins/auto_deconstruct_state/ruby3.rb
|
232
230
|
- lib/pathway/plugins/dry_validation.rb
|
233
|
-
- lib/pathway/plugins/dry_validation/v0_11.rb
|
234
|
-
- lib/pathway/plugins/dry_validation/v0_12.rb
|
235
|
-
- lib/pathway/plugins/dry_validation/v1_0.rb
|
236
231
|
- lib/pathway/plugins/responder.rb
|
237
232
|
- lib/pathway/plugins/sequel_models.rb
|
238
233
|
- lib/pathway/plugins/simple_auth.rb
|
@@ -254,7 +249,6 @@ licenses:
|
|
254
249
|
metadata:
|
255
250
|
bug_tracker_uri: https://github.com/pabloh/pathway/issues
|
256
251
|
source_code_uri: https://github.com/pabloh/pathway
|
257
|
-
post_install_message:
|
258
252
|
rdoc_options: []
|
259
253
|
require_paths:
|
260
254
|
- lib
|
@@ -262,15 +256,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
262
256
|
requirements:
|
263
257
|
- - ">="
|
264
258
|
- !ruby/object:Gem::Version
|
265
|
-
version:
|
259
|
+
version: 3.1.0
|
266
260
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
267
261
|
requirements:
|
268
262
|
- - ">="
|
269
263
|
- !ruby/object:Gem::Version
|
270
264
|
version: '0'
|
271
265
|
requirements: []
|
272
|
-
rubygems_version: 3.
|
273
|
-
signing_key:
|
266
|
+
rubygems_version: 3.6.9
|
274
267
|
specification_version: 4
|
275
268
|
summary: Define your business logic in simple steps.
|
276
269
|
test_files: []
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pathway
|
4
|
-
module Plugins
|
5
|
-
module AutoDeconstructState
|
6
|
-
module DSLMethods
|
7
|
-
private
|
8
|
-
|
9
|
-
def _callable(callable)
|
10
|
-
if callable.is_a?(Symbol) && @operation.respond_to?(callable, true) &&
|
11
|
-
@operation.method(callable).arity != 0 &&
|
12
|
-
@operation.method(callable).parameters.all? { _1 in [:key|:keyreq|:keyrest|:block,*] }
|
13
|
-
|
14
|
-
-> state { @operation.send(callable, **state) }
|
15
|
-
else
|
16
|
-
super
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pathway
|
4
|
-
module Plugins
|
5
|
-
module DryValidation
|
6
|
-
module V0_11
|
7
|
-
module ClassMethods
|
8
|
-
attr_reader :form_class, :form_options
|
9
|
-
attr_accessor :auto_wire_options
|
10
|
-
|
11
|
-
def form(base = nil, **opts, &block)
|
12
|
-
if block_given?
|
13
|
-
base ||= _base_form
|
14
|
-
self.form_class = _block_definition(base, opts, &block)
|
15
|
-
elsif base
|
16
|
-
self.form_class = _form_class(base)
|
17
|
-
else
|
18
|
-
raise ArgumentError, 'Either a form class or a block must be provided'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def form_class= klass
|
23
|
-
@builded_form = klass.options.empty? ? klass.new : nil
|
24
|
-
@form_class = klass
|
25
|
-
@form_options = klass.options.keys
|
26
|
-
end
|
27
|
-
|
28
|
-
def build_form(opts = {})
|
29
|
-
@builded_form || form_class.new(opts)
|
30
|
-
end
|
31
|
-
|
32
|
-
def inherited(subclass)
|
33
|
-
super
|
34
|
-
subclass.form_class = form_class
|
35
|
-
subclass.auto_wire_options = auto_wire_options
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def _base_form
|
41
|
-
superclass.respond_to?(:form_class) ? superclass.form_class : Dry::Validation::Schema::Form
|
42
|
-
end
|
43
|
-
|
44
|
-
def _form_class(form)
|
45
|
-
form.is_a?(Class) ? form : form.class
|
46
|
-
end
|
47
|
-
|
48
|
-
def _form_opts(opts = {})
|
49
|
-
opts.merge(build: false)
|
50
|
-
end
|
51
|
-
|
52
|
-
def _block_definition(base, opts, &block)
|
53
|
-
Dry::Validation.Form(_form_class(base), _form_opts(opts), &block)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
module InstanceMethods
|
58
|
-
extend Forwardable
|
59
|
-
|
60
|
-
delegate %i[build_form form_options auto_wire_options] => 'self.class'
|
61
|
-
alias :form :build_form
|
62
|
-
|
63
|
-
def validate(state, with: nil)
|
64
|
-
if auto_wire_options && form_options.any?
|
65
|
-
with ||= form_options.zip(form_options).to_h
|
66
|
-
end
|
67
|
-
opts = Hash(with).map { |opt, key| [opt, state[key]] }.to_h
|
68
|
-
validate_with(state[:input], opts)
|
69
|
-
.then { |params| state.update(params: params) }
|
70
|
-
end
|
71
|
-
|
72
|
-
def validate_with(params, opts = {})
|
73
|
-
val = form(opts).call(params)
|
74
|
-
|
75
|
-
val.success? ? wrap(val.output) : error(:validation, details: val.messages)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.apply(operation, auto_wire_options: false)
|
80
|
-
operation.form_class = Dry::Validation::Schema::Form
|
81
|
-
operation.auto_wire_options = auto_wire_options
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pathway
|
4
|
-
module Plugins
|
5
|
-
module DryValidation
|
6
|
-
module V0_12
|
7
|
-
module ClassMethods
|
8
|
-
attr_reader :form_class, :form_options
|
9
|
-
attr_accessor :auto_wire_options
|
10
|
-
|
11
|
-
def form(base = nil, **opts, &block)
|
12
|
-
if block_given?
|
13
|
-
base ||= _base_form
|
14
|
-
self.form_class = _block_definition(base, opts, &block)
|
15
|
-
elsif base
|
16
|
-
self.form_class = _form_class(base)
|
17
|
-
else
|
18
|
-
raise ArgumentError, 'Either a form class or a block must be provided'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def form_class= klass
|
23
|
-
@builded_form = klass.options.empty? ? klass.new : nil
|
24
|
-
@form_class = klass
|
25
|
-
@form_options = klass.options.keys
|
26
|
-
end
|
27
|
-
|
28
|
-
def build_form(opts = {})
|
29
|
-
@builded_form || form_class.new(opts)
|
30
|
-
end
|
31
|
-
|
32
|
-
def inherited(subclass)
|
33
|
-
super
|
34
|
-
subclass.form_class = form_class
|
35
|
-
subclass.auto_wire_options = auto_wire_options
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def _base_form
|
41
|
-
superclass.respond_to?(:form_class) ? superclass.form_class : Dry::Validation::Schema::Params
|
42
|
-
end
|
43
|
-
|
44
|
-
def _form_class(form)
|
45
|
-
form.is_a?(Class) ? form : form.class
|
46
|
-
end
|
47
|
-
|
48
|
-
def _form_opts(opts = {})
|
49
|
-
opts.merge(build: false)
|
50
|
-
end
|
51
|
-
|
52
|
-
def _block_definition(base, opts, &block)
|
53
|
-
Dry::Validation.Params(_form_class(base), _form_opts(opts), &block)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
module InstanceMethods
|
58
|
-
extend Forwardable
|
59
|
-
|
60
|
-
delegate %i[build_form form_options auto_wire_options] => 'self.class'
|
61
|
-
alias :form :build_form
|
62
|
-
|
63
|
-
def validate(state, with: nil)
|
64
|
-
if auto_wire_options && form_options.any?
|
65
|
-
with ||= form_options.zip(form_options).to_h
|
66
|
-
end
|
67
|
-
opts = Hash(with).map { |opt, key| [opt, state[key]] }.to_h
|
68
|
-
validate_with(state[:input], opts)
|
69
|
-
.then { |params| state.update(params: params) }
|
70
|
-
end
|
71
|
-
|
72
|
-
def validate_with(params, opts = {})
|
73
|
-
val = form(opts).call(params)
|
74
|
-
|
75
|
-
val.success? ? wrap(val.output) : error(:validation, details: val.messages)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.apply(operation, auto_wire_options: false)
|
80
|
-
operation.form_class = Dry::Validation::Schema::Params
|
81
|
-
operation.auto_wire_options = auto_wire_options
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pathway
|
4
|
-
module Plugins
|
5
|
-
module DryValidation
|
6
|
-
module V1_0
|
7
|
-
module ClassMethods
|
8
|
-
attr_reader :contract_class, :contract_options
|
9
|
-
attr_accessor :auto_wire_options
|
10
|
-
|
11
|
-
def contract(base = nil, &block)
|
12
|
-
if block_given?
|
13
|
-
base ||= _base_contract
|
14
|
-
self.contract_class = Class.new(base, &block)
|
15
|
-
elsif base
|
16
|
-
self.contract_class = base
|
17
|
-
else
|
18
|
-
raise ArgumentError, 'Either a contract class or a block must be provided'
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
ruby2_keywords def params(*args, &block)
|
23
|
-
contract { params(*args, &block) }
|
24
|
-
end
|
25
|
-
|
26
|
-
def contract_class= klass
|
27
|
-
@contract_class = klass
|
28
|
-
@contract_options = (klass.dry_initializer.options - Dry::Validation::Contract.dry_initializer.options).map(&:target)
|
29
|
-
@builded_contract = @contract_options.empty? && klass.schema ? klass.new : nil
|
30
|
-
end
|
31
|
-
|
32
|
-
def build_contract(**opts)
|
33
|
-
@builded_contract || contract_class.new(**opts)
|
34
|
-
end
|
35
|
-
|
36
|
-
def inherited(subclass)
|
37
|
-
super
|
38
|
-
subclass.contract_class = contract_class
|
39
|
-
subclass.auto_wire_options = auto_wire_options
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def _base_contract
|
45
|
-
superclass.respond_to?(:contract_class) ? superclass.contract_class : Dry::Validation::Contract
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
module InstanceMethods
|
50
|
-
extend Forwardable
|
51
|
-
|
52
|
-
delegate %i[build_contract contract_options auto_wire_options] => 'self.class'
|
53
|
-
alias :contract :build_contract
|
54
|
-
|
55
|
-
def validate(state, with: nil)
|
56
|
-
if auto_wire_options && contract_options.any?
|
57
|
-
with ||= contract_options.zip(contract_options).to_h
|
58
|
-
end
|
59
|
-
opts = Hash(with).map { |to, from| [to, state[from]] }.to_h
|
60
|
-
validate_with(state[:input], **opts)
|
61
|
-
.then { |params| state.update(params: params) }
|
62
|
-
end
|
63
|
-
|
64
|
-
def validate_with(input, **opts)
|
65
|
-
result = contract(**opts).call(input)
|
66
|
-
|
67
|
-
result.success? ? wrap(result.values.to_h) : error(:validation, details: result.errors.to_h)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def self.apply(operation, auto_wire_options: false)
|
72
|
-
operation.contract_class = Dry::Validation::Contract
|
73
|
-
operation.auto_wire_options = auto_wire_options
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|