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