reform 2.3.0.rc1 → 2.3.0.rc2
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/.gitignore +2 -0
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +460 -0
- data/.travis.yml +26 -11
- data/CHANGES.md +25 -2
- data/Gemfile +6 -3
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +2 -4
- data/Rakefile +18 -9
- data/lib/reform/contract.rb +7 -7
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +9 -5
- data/lib/reform/errors.rb +27 -15
- data/lib/reform/form.rb +22 -11
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +10 -86
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/dry/new_api.rb +58 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +9 -11
- data/lib/reform/form/prepopulate.rb +3 -2
- data/lib/reform/form/validate.rb +19 -12
- data/lib/reform/result.rb +36 -9
- data/lib/reform/validation.rb +10 -8
- data/lib/reform/validation/groups.rb +2 -3
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +10 -9
- data/test/benchmarking.rb +10 -11
- data/test/call_new_api.rb +23 -0
- data/test/{call_test.rb → call_old_api.rb} +3 -3
- data/test/changed_test.rb +7 -7
- data/test/coercion_test.rb +50 -18
- data/test/composition_new_api.rb +186 -0
- data/test/{composition_test.rb → composition_old_api.rb} +23 -26
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_new_api.rb +77 -0
- data/test/{contract_test.rb → contract_old_api.rb} +8 -8
- data/test/default_test.rb +1 -1
- data/test/deserialize_test.rb +8 -11
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +7 -9
- data/test/fixtures/dry_error_messages.yml +5 -2
- data/test/fixtures/dry_new_api_error_messages.yml +104 -0
- data/test/form_new_api.rb +57 -0
- data/test/{form_test.rb → form_old_api.rb} +2 -2
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
- data/test/from_test.rb +8 -12
- data/test/inherit_new_api.rb +105 -0
- data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
- data/test/module_new_api.rb +137 -0
- data/test/{module_test.rb → module_old_api.rb} +19 -15
- data/test/parse_option_test.rb +5 -5
- data/test/parse_pipeline_test.rb +2 -2
- data/test/populate_new_api.rb +304 -0
- data/test/{populate_test.rb → populate_old_api.rb} +28 -34
- data/test/populator_skip_test.rb +1 -2
- data/test/prepopulator_test.rb +5 -6
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +5 -5
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +17 -23
- data/test/save_new_api.rb +101 -0
- data/test/{save_test.rb → save_old_api.rb} +10 -13
- data/test/setup_test.rb +6 -6
- data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +2 -3
- data/test/test_helper.rb +13 -5
- data/test/validate_new_api.rb +408 -0
- data/test/{validate_test.rb → validate_old_api.rb} +43 -53
- data/test/validation/dry_validation_new_api.rb +826 -0
- data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
- data/test/validation/result_test.rb +20 -22
- data/test/validation_library_provided_test.rb +3 -3
- data/test/virtual_test.rb +46 -6
- data/test/writeable_test.rb +7 -7
- metadata +101 -51
- data/test/errors_test.rb +0 -180
- data/test/readonly_test.rb +0 -14
|
@@ -16,13 +16,13 @@ module Reform::Form::Composition
|
|
|
16
16
|
#
|
|
17
17
|
# class CoverSongForm < Reform::Form
|
|
18
18
|
# model :song, on: :cover_song
|
|
19
|
-
def model(main_model, options={})
|
|
19
|
+
def model(main_model, options = {})
|
|
20
20
|
super
|
|
21
21
|
|
|
22
22
|
composition_model = options[:on] || main_model
|
|
23
23
|
|
|
24
24
|
# FIXME: this should just delegate to :model as in FB, and the comp would take care of it internally.
|
|
25
|
-
[
|
|
25
|
+
%i[persisted? to_key to_param].each do |method|
|
|
26
26
|
define_method method do
|
|
27
27
|
model[composition_model].send(method)
|
|
28
28
|
end
|
data/lib/reform/form/dry.rb
CHANGED
|
@@ -1,95 +1,19 @@
|
|
|
1
1
|
require "dry-validation"
|
|
2
|
-
require "dry/validation/
|
|
2
|
+
require "dry/validation/version"
|
|
3
3
|
require "reform/validation"
|
|
4
|
+
require "reform/form/dry/input_hash"
|
|
4
5
|
|
|
5
6
|
module Reform::Form::Dry
|
|
6
7
|
def self.included(includer)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def validation_group_class
|
|
14
|
-
Group
|
|
15
|
-
end
|
|
8
|
+
if Gem::Version.new(Dry::Validation::VERSION) > Gem::Version.new("0.13.3")
|
|
9
|
+
require "reform/form/dry/new_api"
|
|
10
|
+
validations = Reform::Form::Dry::NewApi::Validations
|
|
11
|
+
else
|
|
12
|
+
require "reform/form/dry/old_api"
|
|
13
|
+
validations = Reform::Form::Dry::OldApi::Validations
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class Group
|
|
24
|
-
def initialize(options = {})
|
|
25
|
-
options ||= {}
|
|
26
|
-
schema_class = options[:schema] || Dry::Validation::Schema
|
|
27
|
-
@validator = Dry::Validation.Schema(schema_class, build: false)
|
|
28
|
-
|
|
29
|
-
@schema_inject_params = options[:with] || {}
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def instance_exec(&block)
|
|
33
|
-
@validator = Dry::Validation.Schema(@validator, build: false, &block)
|
|
34
|
-
|
|
35
|
-
# inject the keys into the configure block automatically
|
|
36
|
-
keys = @schema_inject_params.keys
|
|
37
|
-
@validator.class_eval do
|
|
38
|
-
configure do
|
|
39
|
-
keys.each { |k| option k }
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def call(form)
|
|
45
|
-
dynamic_options = {}
|
|
46
|
-
dynamic_options[:form] = form if @schema_inject_params[:form]
|
|
47
|
-
inject_options = @schema_inject_params.merge(dynamic_options)
|
|
48
|
-
|
|
49
|
-
# TODO: only pass submitted values to Schema#call?
|
|
50
|
-
dry_result = call_schema(inject_options, input_hash(form))
|
|
51
|
-
# dry_messages = dry_result.messages
|
|
52
|
-
|
|
53
|
-
return dry_result
|
|
54
|
-
reform_errors = Reform::Contract::Errors.new(dry_result) # TODO: dry should be merged here.
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
def call_schema(inject_options, input)
|
|
59
|
-
@validator.new(@validator.rules, inject_options).(input)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# if dry_error is a hash rather than an array then it contains
|
|
63
|
-
# the messages for a nested property
|
|
64
|
-
# these messages need to be added to the correct collection
|
|
65
|
-
# objects.
|
|
66
|
-
|
|
67
|
-
# collections:
|
|
68
|
-
# {0=>{:name=>["must be filled"]}, 1=>{:name=>["must be filled"]}}
|
|
69
|
-
|
|
70
|
-
# Objects:
|
|
71
|
-
# {:name=>["must be filled"]}
|
|
72
|
-
# simply load up the object and attach the message to it
|
|
73
|
-
|
|
74
|
-
# we can't use to_nested_hash as it get's messed up by composition.
|
|
75
|
-
def input_hash(form)
|
|
76
|
-
hash = form.class.nested_hash_representer.new(form).to_hash
|
|
77
|
-
symbolize_hash(hash)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# dry-v needs symbolized keys
|
|
81
|
-
# TODO: Don't do this here... Representers??
|
|
82
|
-
def symbolize_hash(old_hash)
|
|
83
|
-
old_hash.each_with_object({}) { |(k, v), new_hash|
|
|
84
|
-
new_hash[k.to_sym] = if v.is_a?(Hash)
|
|
85
|
-
symbolize_hash(v)
|
|
86
|
-
elsif v.is_a?(Array)
|
|
87
|
-
v.map{ |h| h.is_a?(Hash) ? symbolize_hash(h) : h }
|
|
88
|
-
else
|
|
89
|
-
v
|
|
90
|
-
end
|
|
91
|
-
}
|
|
92
|
-
end
|
|
93
|
-
end
|
|
16
|
+
includer.send :include, validations
|
|
17
|
+
includer.extend validations::ClassMethods
|
|
94
18
|
end
|
|
95
19
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Reform::Form::Dry
|
|
2
|
+
module InputHash
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
# if dry_error is a hash rather than an array then it contains
|
|
6
|
+
# the messages for a nested property
|
|
7
|
+
# these messages need to be added to the correct collection
|
|
8
|
+
# objects.
|
|
9
|
+
|
|
10
|
+
# collections:
|
|
11
|
+
# {0=>{:name=>["must be filled"]}, 1=>{:name=>["must be filled"]}}
|
|
12
|
+
|
|
13
|
+
# Objects:
|
|
14
|
+
# {:name=>["must be filled"]}
|
|
15
|
+
# simply load up the object and attach the message to it
|
|
16
|
+
|
|
17
|
+
# we can't use to_nested_hash as it get's messed up by composition.
|
|
18
|
+
def input_hash(form)
|
|
19
|
+
hash = form.class.nested_hash_representer.new(form).to_hash
|
|
20
|
+
symbolize_hash(hash)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# dry-v needs symbolized keys
|
|
24
|
+
# TODO: Don't do this here... Representers??
|
|
25
|
+
def symbolize_hash(old_hash)
|
|
26
|
+
old_hash.each_with_object({}) do |(k, v), new_hash|
|
|
27
|
+
new_hash[k.to_sym] = if v.is_a?(Hash)
|
|
28
|
+
symbolize_hash(v)
|
|
29
|
+
elsif v.is_a?(Array)
|
|
30
|
+
v.map { |h| h.is_a?(Hash) ? symbolize_hash(h) : h }
|
|
31
|
+
else
|
|
32
|
+
v
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Reform::Form::Dry
|
|
2
|
+
module NewApi
|
|
3
|
+
class Contract < Dry::Validation::Contract
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module Validations
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def validation_group_class
|
|
9
|
+
Group
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.included(includer)
|
|
14
|
+
includer.extend(ClassMethods)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Group
|
|
18
|
+
include InputHash
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
21
|
+
options ||= {}
|
|
22
|
+
@validator = options[:schema] || Reform::Form::Dry::NewApi::Contract
|
|
23
|
+
|
|
24
|
+
@schema_inject_params = options[:with] || {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def instance_exec(&block)
|
|
28
|
+
Dry::Validation.load_extensions(:hints)
|
|
29
|
+
@block = block
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call(form)
|
|
33
|
+
dynamic_options = {}
|
|
34
|
+
dynamic_options[:form] = form if @schema_inject_params[:form]
|
|
35
|
+
inject_options = @schema_inject_params.merge(dynamic_options)
|
|
36
|
+
|
|
37
|
+
Dry::Schema::DSL.class_eval do
|
|
38
|
+
inject_options.each do |key, value|
|
|
39
|
+
define_method(key) { value }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# when passing options[:schema] the class instance is already created so we just need to call
|
|
44
|
+
# "call"
|
|
45
|
+
@validator = @validator.build(&@block) if @validator == Reform::Form::Dry::NewApi::Contract
|
|
46
|
+
|
|
47
|
+
# TODO: only pass submitted values to Schema#call?
|
|
48
|
+
dry_result = @validator.call(input_hash(form))
|
|
49
|
+
# dry_messages = dry_result.messages
|
|
50
|
+
|
|
51
|
+
return dry_result
|
|
52
|
+
|
|
53
|
+
_reform_errors = Reform::Contract::Errors.new(dry_result) # TODO: dry should be merged here.
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Reform::Form::Dry
|
|
2
|
+
module OldApi
|
|
3
|
+
class Schema < Dry::Validation::Schema
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module Validations
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def validation_group_class
|
|
9
|
+
Group
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.included(includer)
|
|
14
|
+
includer.extend(ClassMethods)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Group
|
|
18
|
+
include InputHash
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
21
|
+
options ||= {}
|
|
22
|
+
schema_class = options[:schema] || Reform::Form::Dry::OldApi::Schema
|
|
23
|
+
@validator = Dry::Validation.Schema(schema_class, build: false)
|
|
24
|
+
|
|
25
|
+
@schema_inject_params = options[:with] || {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def instance_exec(&block)
|
|
29
|
+
@validator = Dry::Validation.Schema(@validator, build: false, &block)
|
|
30
|
+
# inject the keys into the configure block automatically
|
|
31
|
+
keys = @schema_inject_params.keys
|
|
32
|
+
@validator.class_eval do
|
|
33
|
+
configure do
|
|
34
|
+
keys.each { |k| option k }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def call(form)
|
|
40
|
+
dynamic_options = {}
|
|
41
|
+
dynamic_options[:form] = form if @schema_inject_params[:form]
|
|
42
|
+
inject_options = @schema_inject_params.merge(dynamic_options)
|
|
43
|
+
|
|
44
|
+
# TODO: only pass submitted values to Schema#call?
|
|
45
|
+
dry_result = call_schema(inject_options, input_hash(form))
|
|
46
|
+
# dry_messages = dry_result.messages
|
|
47
|
+
|
|
48
|
+
return dry_result
|
|
49
|
+
|
|
50
|
+
_reform_errors = Reform::Contract::Errors.new(dry_result) # TODO: dry should be merged here.
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def call_schema(inject_options, input)
|
|
56
|
+
@validator.new(@validator.rules, inject_options).(input)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -26,7 +26,8 @@ class Reform::Form::Populator
|
|
|
26
26
|
twin
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
private
|
|
29
|
+
private
|
|
30
|
+
|
|
30
31
|
def call!(options)
|
|
31
32
|
form = options[:represented]
|
|
32
33
|
@value.(form, options) # Declarative::Option call.
|
|
@@ -37,7 +38,7 @@ private
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def get(options)
|
|
40
|
-
|
|
41
|
+
Representable::GetValue.(nil, options)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
class IfEmpty < self # Populator
|
|
@@ -59,7 +60,8 @@ private
|
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
private
|
|
64
|
+
|
|
63
65
|
def run!(form, fragment, options)
|
|
64
66
|
return @user_proc.new if @user_proc.is_a?(Class) # handle populate_if_empty: Class. this excludes using Callables, though.
|
|
65
67
|
|
|
@@ -75,18 +77,14 @@ private
|
|
|
75
77
|
|
|
76
78
|
@value.(form, options[:fragment], options[:user_options])
|
|
77
79
|
end
|
|
78
|
-
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
# Sync (default) blindly grabs the corresponding form twin and returns it. This might imply that nil is returned,
|
|
82
83
|
# and in turn #validate! is called on nil.
|
|
83
84
|
class Sync < self
|
|
84
85
|
def call!(options)
|
|
85
|
-
if options[:binding].array?
|
|
86
|
-
|
|
87
|
-
else
|
|
88
|
-
options[:model]
|
|
89
|
-
end
|
|
86
|
+
return options[:model][options[:index]] if options[:binding].array?
|
|
87
|
+
options[:model]
|
|
90
88
|
end
|
|
91
89
|
end
|
|
92
90
|
|
|
@@ -102,8 +100,8 @@ private
|
|
|
102
100
|
# (which population is) to the form.
|
|
103
101
|
class External
|
|
104
102
|
def call(input, options)
|
|
105
|
-
options[:represented].class.definitions
|
|
106
|
-
|
|
103
|
+
options[:represented].class.definitions
|
|
104
|
+
.get(options[:binding][:name])[:internal_populator].(input, options)
|
|
107
105
|
end
|
|
108
106
|
end
|
|
109
107
|
end
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# prepopulate!(options)
|
|
2
2
|
# prepopulator: ->(model, user_options)
|
|
3
3
|
module Reform::Form::Prepopulate
|
|
4
|
-
def prepopulate!(options={})
|
|
4
|
+
def prepopulate!(options = {})
|
|
5
5
|
prepopulate_local!(options) # call #prepopulate! on local properties.
|
|
6
6
|
prepopulate_nested!(options) # THEN call #prepopulate! on nested forms.
|
|
7
7
|
|
|
8
8
|
self
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
private
|
|
11
|
+
private
|
|
12
|
+
|
|
12
13
|
def prepopulate_local!(options)
|
|
13
14
|
schema.each do |dfn|
|
|
14
15
|
next unless block = dfn[:prepopulator]
|
data/lib/reform/form/validate.rb
CHANGED
|
@@ -5,17 +5,23 @@ module Reform::Form::Validate
|
|
|
5
5
|
include Uber::Callable
|
|
6
6
|
|
|
7
7
|
def call(form, options)
|
|
8
|
-
params = options[:input]
|
|
9
8
|
# TODO: Schema should provide property names as plain list.
|
|
10
|
-
|
|
9
|
+
# ensure param keys are strings.
|
|
10
|
+
params = options[:input].each_with_object({}) { |(k, v), hash|
|
|
11
|
+
hash[k.to_s] = v
|
|
12
|
+
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
# return false if any property inputs are populated.
|
|
15
|
+
options[:binding][:nested].definitions.each do |definition|
|
|
16
|
+
value = params[definition.name.to_s]
|
|
17
|
+
return false if (!value.nil? && value != '')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
true # skip this property
|
|
14
21
|
end
|
|
15
22
|
end
|
|
16
23
|
end
|
|
17
24
|
|
|
18
|
-
|
|
19
25
|
def validate(params)
|
|
20
26
|
# allow an external deserializer.
|
|
21
27
|
@input_params = params # we want to store these for access via dry later
|
|
@@ -30,27 +36,28 @@ module Reform::Form::Validate
|
|
|
30
36
|
deserializer.new(self).from_hash(params)
|
|
31
37
|
end
|
|
32
38
|
|
|
39
|
+
private
|
|
33
40
|
|
|
34
|
-
private
|
|
35
41
|
# Meant to return params processable by the representer. This is the hook for munching date fields, etc.
|
|
36
42
|
def deserialize!(params)
|
|
37
43
|
# NOTE: it is completely up to the form user how they want to deserialize (e.g. using an external JSON-API representer).
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
# use the deserializer as an external instance to operate on the Twin API,
|
|
45
|
+
# e.g. adding new items in collections using #<< etc.
|
|
40
46
|
# DISCUSS: using self here will call the form's setters like title= which might be overridden.
|
|
41
47
|
params
|
|
42
48
|
end
|
|
43
49
|
|
|
44
50
|
# Default deserializer for hash.
|
|
45
51
|
# This is input-specific, e.g. Hash, JSON, or XML.
|
|
46
|
-
def deserializer!(source=self.class, options={}) # called on top-level, only, for now.
|
|
47
|
-
deserializer = Disposable::Rescheme.from(
|
|
52
|
+
def deserializer!(source = self.class, options = {}) # called on top-level, only, for now.
|
|
53
|
+
deserializer = Disposable::Rescheme.from(
|
|
54
|
+
source,
|
|
48
55
|
{
|
|
49
56
|
include: [Representable::Hash::AllowSymbols, Representable::Hash],
|
|
50
57
|
superclass: Representable::Decorator,
|
|
51
|
-
definitions_from:
|
|
58
|
+
definitions_from: ->(inline) { inline.definitions },
|
|
52
59
|
options_from: :deserializer,
|
|
53
|
-
exclude_options: [
|
|
60
|
+
exclude_options: %i[default populator] # Reform must not copy Disposable/Reform-only options that might confuse representable.
|
|
54
61
|
}.merge(options)
|
|
55
62
|
)
|
|
56
63
|
|
data/lib/reform/result.rb
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
1
|
module Reform
|
|
2
2
|
class Contract < Disposable::Twin
|
|
3
|
-
|
|
4
3
|
# Collects all native results of a form of all groups and provides
|
|
5
4
|
# a unified API: #success?, #errors, #messages, #hints.
|
|
6
5
|
# #success? returns validity of the branch.
|
|
7
6
|
class Result
|
|
8
|
-
def initialize(results, nested_results=[]) # DISCUSS: do we like this?
|
|
7
|
+
def initialize(results, nested_results = []) # DISCUSS: do we like this?
|
|
9
8
|
@results = results # native Result objects, e.g. `#<Dry::Validation::Result output={:title=>"Fallout", :composer=>nil} errors={}>`
|
|
10
9
|
@failure = (results + nested_results).find(&:failure?) # TODO: test nested.
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def failure?; @failure end
|
|
13
|
+
|
|
14
14
|
def success?; !failure? end
|
|
15
15
|
|
|
16
16
|
def errors(*args); filter_for(:errors, *args) end
|
|
17
|
+
|
|
17
18
|
def messages(*args); filter_for(:messages, *args) end
|
|
19
|
+
|
|
18
20
|
def hints(*args); filter_for(:hints, *args) end
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
def add_error(key, error_text)
|
|
23
|
+
CustomError.new(key, error_text, @results)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_results
|
|
27
|
+
@results
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# this doesn't do nested errors (e.g. )
|
|
21
33
|
def filter_for(method, *args)
|
|
22
|
-
@results.collect { |r| r.public_send(method, *args) }
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
@results.collect { |r| r.public_send(method, *args).to_h }
|
|
35
|
+
.inject({}) { |hah, err| hah.merge(err) { |key, old_v, new_v| (new_v.is_a?(Array) ? (old_v |= new_v) : old_v.merge(new_v)) } }
|
|
36
|
+
.find_all { |k, v| # filter :nested=>{:something=>["too nested!"]} #DISCUSS: do we want that here?
|
|
37
|
+
if v.is_a?(Hash)
|
|
38
|
+
nested_errors = v.select { |attr_key, val| attr_key.is_a?(Integer) && val.is_a?(Array) && val.any? }
|
|
39
|
+
v = nested_errors.to_a if nested_errors.any?
|
|
40
|
+
end
|
|
41
|
+
v.is_a?(Array)
|
|
42
|
+
}.to_h
|
|
26
43
|
end
|
|
27
44
|
|
|
28
45
|
# Note: this class will be redundant in Reform 3, where the public API
|
|
@@ -39,17 +56,27 @@ module Reform
|
|
|
39
56
|
def_delegators :@result, :success?, :failure?
|
|
40
57
|
|
|
41
58
|
def errors(*args); traverse_for(:errors, *args) end
|
|
59
|
+
|
|
42
60
|
def messages(*args); traverse_for(:messages, *args) end
|
|
61
|
+
|
|
43
62
|
def hints(*args); traverse_for(:hints, *args) end
|
|
44
63
|
|
|
45
64
|
def advance(*path)
|
|
46
65
|
path = @path + path.compact # remove index if nil.
|
|
47
|
-
|
|
66
|
+
traverse = traverse(@result.errors, path)
|
|
67
|
+
# when returns {} is because no errors are found
|
|
68
|
+
# when returns a String is because an error has been found on the main key not in the nested one.
|
|
69
|
+
# Collection with custom rule will return a String here and does not need to be considered
|
|
70
|
+
# as a nested error.
|
|
71
|
+
# when return an Array without an index is same as String but it's a property with a custom rule.
|
|
72
|
+
# Check test/validation/dry_validation_test.rb:685
|
|
73
|
+
return if traverse == {} || traverse.is_a?(String) || (traverse.is_a?(Array) && path.compact.size == 1)
|
|
48
74
|
|
|
49
75
|
Pointer.new(@result, path)
|
|
50
76
|
end
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
private
|
|
79
|
+
|
|
53
80
|
def traverse(hash, path)
|
|
54
81
|
path.inject(hash) { |errs, segment| errs[segment] || {} } # FIXME. test if all segments present.
|
|
55
82
|
end
|