pathway 0.9.1 → 0.11.3
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/.circleci/config.yml +38 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +0 -2
- data/README.md +91 -50
- data/Rakefile +2 -0
- data/lib/pathway.rb +3 -1
- data/lib/pathway/plugins/dry_validation.rb +17 -91
- data/lib/pathway/plugins/dry_validation/v0_11.rb +86 -0
- data/lib/pathway/plugins/dry_validation/v0_12.rb +86 -0
- data/lib/pathway/plugins/dry_validation/v1_0.rb +78 -0
- data/lib/pathway/plugins/responder.rb +2 -0
- data/lib/pathway/plugins/sequel_models.rb +31 -16
- data/lib/pathway/plugins/simple_auth.rb +2 -0
- data/lib/pathway/result.rb +2 -0
- data/lib/pathway/rspec.rb +2 -0
- data/lib/pathway/rspec/matchers.rb +2 -0
- data/lib/pathway/rspec/matchers/accept_optional_fields.rb +31 -6
- data/lib/pathway/rspec/matchers/fail_on.rb +5 -3
- data/lib/pathway/rspec/matchers/field_list_helpers.rb +4 -2
- data/lib/pathway/rspec/matchers/form_schema_helpers.rb +47 -3
- data/lib/pathway/rspec/matchers/list_helpers.rb +5 -3
- data/lib/pathway/rspec/matchers/require_fields.rb +30 -5
- data/lib/pathway/rspec/matchers/succeed_on.rb +3 -1
- data/lib/pathway/version.rb +3 -1
- data/pathway.gemspec +6 -5
- metadata +13 -10
@@ -0,0 +1,86 @@
|
|
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
|
@@ -0,0 +1,86 @@
|
|
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
|
@@ -0,0 +1,78 @@
|
|
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
|
+
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
|
@@ -1,25 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sequel/model'
|
2
4
|
|
3
5
|
module Pathway
|
4
6
|
module Plugins
|
5
7
|
module SequelModels
|
6
8
|
module DSLMethods
|
7
|
-
def transaction(&bl)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
def transaction(step_name = nil, &bl)
|
10
|
+
fail 'must provide a step or a block but not both' if !step_name.nil? == block_given?
|
11
|
+
|
12
|
+
if step_name
|
13
|
+
transaction { step step_name }
|
14
|
+
else
|
15
|
+
around(-> steps, _ {
|
16
|
+
db.transaction(savepoint: true) do
|
17
|
+
raise Sequel::Rollback if steps.call.failure?
|
18
|
+
end
|
19
|
+
}, &bl)
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
|
-
def after_commit(&bl)
|
16
|
-
|
17
|
-
dsl = self.class::DSL.new(State.new(self, state.to_h.dup), self)
|
23
|
+
def after_commit(step_name = nil, &bl)
|
24
|
+
fail 'must provide a step or a block but not both' if !step_name.nil? == block_given?
|
18
25
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
if step_name
|
27
|
+
after_commit { step step_name }
|
28
|
+
else
|
29
|
+
around(-> steps, state {
|
30
|
+
dsl = self.class::DSL.new(State.new(self, state.to_h.dup), self)
|
31
|
+
|
32
|
+
db.after_commit do
|
33
|
+
steps.call(dsl)
|
34
|
+
end
|
35
|
+
}, &bl)
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
25
39
|
|
@@ -49,10 +63,11 @@ module Pathway
|
|
49
63
|
delegate :db => :model_class
|
50
64
|
|
51
65
|
def fetch_model(state, from: model_class, search_by: search_field, using: search_by, to: result_key, overwrite: false, error_message: nil)
|
52
|
-
error_message ||= if from
|
53
|
-
Inflector.humanize(Inflector.underscore(Inflector.demodulize(from.name))) + ' not found'
|
54
|
-
else
|
66
|
+
error_message ||= if (from == model_class)
|
55
67
|
model_not_found
|
68
|
+
elsif from.respond_to?(:name) || from.respond_to?(:model)
|
69
|
+
from_name = (from.respond_to?(:name) ? from : from.model).name
|
70
|
+
Inflector.humanize(Inflector.underscore(Inflector.demodulize(from_name))) + ' not found'
|
56
71
|
end
|
57
72
|
|
58
73
|
if state[to].nil? || overwrite
|
data/lib/pathway/result.rb
CHANGED
data/lib/pathway/rspec.rb
CHANGED
@@ -1,30 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pathway/rspec/matchers/form_schema_helpers'
|
2
4
|
|
3
5
|
RSpec::Matchers.define :accept_optional_fields do |*fields|
|
4
6
|
match do |form|
|
5
7
|
@form, @fields = form, fields
|
6
8
|
|
7
|
-
not_defined.empty? &&
|
9
|
+
not_defined.empty? &&
|
10
|
+
not_optional.empty? &&
|
11
|
+
allowing_null_values_matches? &&
|
12
|
+
not_allowing_null_values_matches?
|
8
13
|
end
|
9
14
|
|
10
15
|
match_when_negated do |form|
|
16
|
+
raise NotImplementedError, 'expect().not_to accept_optional_fields.not_allowing_null_values is not supported.' if @allowing_null_values || @not_allowing_null_values
|
17
|
+
|
11
18
|
@form, @fields = form, fields
|
12
19
|
|
13
20
|
not_defined.empty? && optional.empty?
|
14
21
|
end
|
15
22
|
|
16
23
|
description do
|
17
|
-
|
24
|
+
null_value_allowed = @allowing_null_values ? ' allowing null values' : ''
|
25
|
+
null_value_disallowed = @not_allowing_null_values ? ' not allowing null values' : ''
|
26
|
+
"accept #{field_list} as optional #{pluralize_fields}#{null_value_allowed}#{null_value_disallowed}"
|
18
27
|
end
|
19
28
|
|
20
29
|
failure_message do
|
21
|
-
|
22
|
-
|
30
|
+
null_value_allowed = @allowing_null_values ? ' allowing null values' : ''
|
31
|
+
null_value_disallowed = @not_allowing_null_values ? ' not allowing null values' : ''
|
32
|
+
|
33
|
+
"Expected to accept #{field_list} as optional #{pluralize_fields}#{null_value_allowed}#{null_value_disallowed} but " +
|
34
|
+
as_sentence([not_optional_list, not_defined_list, accepting_null_list, not_accepting_null_list].compact,
|
35
|
+
connector: '; ', last_connector: '; and ')
|
23
36
|
end
|
24
37
|
|
25
38
|
failure_message_when_negated do
|
26
39
|
"Did not expect to accept #{field_list} as optional #{pluralize_fields} but " +
|
27
|
-
[optional_list, not_defined_list].compact.join(
|
40
|
+
[optional_list, not_defined_list].compact.join('; and ')
|
28
41
|
end
|
29
42
|
|
30
43
|
include Pathway::Rspec::FormSchemaHelpers
|
@@ -34,7 +47,19 @@ RSpec::Matchers.define :accept_optional_fields do |*fields|
|
|
34
47
|
end
|
35
48
|
|
36
49
|
def not_optional_list
|
37
|
-
"#{as_list(
|
50
|
+
"#{as_list(not_optional)} #{were_was(not_optional)} not optional" if not_optional.any?
|
51
|
+
end
|
52
|
+
|
53
|
+
chain :allowing_null_values do
|
54
|
+
fail 'cannot use allowing_null_values and not_allowing_null_values at the same time' if @not_allowing_null_values
|
55
|
+
|
56
|
+
@allowing_null_values = true
|
57
|
+
end
|
58
|
+
|
59
|
+
chain :not_allowing_null_values do
|
60
|
+
fail 'cannot use allowing_null_values and not_allowing_null_values at the same time' if @allowing_null_values
|
61
|
+
|
62
|
+
@not_allowing_null_values = true
|
38
63
|
end
|
39
64
|
end
|
40
65
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pathway/rspec/matchers/list_helpers'
|
2
4
|
|
3
5
|
RSpec::Matchers.define :fail_on do |input|
|
@@ -38,20 +40,20 @@ RSpec::Matchers.define :fail_on do |input|
|
|
38
40
|
alias :and_details :details
|
39
41
|
|
40
42
|
description do
|
41
|
-
|
43
|
+
'fail' + (@type ? " with :#@type error" : '')
|
42
44
|
end
|
43
45
|
|
44
46
|
failure_message do
|
45
47
|
if !failure?
|
46
48
|
"Expected operation to fail but it didn't"
|
47
49
|
else
|
48
|
-
|
50
|
+
'Expected failed operation to ' +
|
49
51
|
as_sentence(failure_descriptions, connector: '; ', last_connector: '; and ')
|
50
52
|
end
|
51
53
|
end
|
52
54
|
|
53
55
|
failure_message_when_negated do
|
54
|
-
|
56
|
+
'Did not expected operation to fail but it did'
|
55
57
|
end
|
56
58
|
|
57
59
|
def failure?
|