reform 2.3.0.rc1 → 2.5.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 +5 -5
- data/.gitignore +5 -1
- data/.travis.yml +7 -11
- data/CHANGES.md +43 -3
- data/Gemfile +2 -5
- data/ISSUE_TEMPLATE.md +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +7 -9
- data/Rakefile +6 -10
- data/lib/reform/contract.rb +7 -7
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +10 -6
- 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 +22 -60
- data/lib/reform/form/dry/input_hash.rb +37 -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 -4
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +9 -9
- data/test/benchmarking.rb +10 -11
- data/test/call_test.rb +8 -8
- data/test/changed_test.rb +13 -13
- data/test/coercion_test.rb +56 -24
- data/test/composition_test.rb +49 -51
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_test.rb +18 -18
- data/test/default_test.rb +3 -3
- data/test/deserialize_test.rb +14 -17
- data/test/docs/validation_test.rb +134 -0
- data/test/errors_test.rb +131 -86
- data/test/feature_test.rb +9 -11
- data/test/fixtures/dry_error_messages.yml +65 -52
- data/test/form_option_test.rb +3 -3
- data/test/form_test.rb +6 -6
- data/test/from_test.rb +17 -21
- data/test/inherit_test.rb +28 -35
- data/test/module_test.rb +23 -28
- data/test/parse_option_test.rb +12 -12
- data/test/parse_pipeline_test.rb +3 -3
- data/test/populate_test.rb +146 -93
- data/test/populator_skip_test.rb +3 -4
- data/test/prepopulator_test.rb +20 -21
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +7 -7
- data/test/reform_test.rb +38 -42
- data/test/save_test.rb +16 -19
- data/test/setup_test.rb +15 -15
- data/test/skip_if_test.rb +30 -19
- data/test/skip_setter_and_getter_test.rb +8 -9
- data/test/test_helper.rb +12 -5
- data/test/validate_test.rb +160 -140
- data/test/validation/dry_validation_test.rb +407 -236
- data/test/validation/result_test.rb +29 -31
- data/test/validation_library_provided_test.rb +3 -3
- data/test/virtual_test.rb +46 -6
- data/test/writeable_test.rb +13 -13
- metadata +32 -29
- data/test/readonly_test.rb +0 -14
data/test/feature_test.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
1
|
class FeatureInheritanceTest < BaseTest
|
4
2
|
Song = Struct.new(:title, :album, :composer)
|
5
3
|
Album = Struct.new(:name, :songs, :artist)
|
@@ -38,17 +36,17 @@ class FeatureInheritanceTest < BaseTest
|
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
let
|
42
|
-
let
|
43
|
-
let
|
44
|
-
let
|
45
|
-
let
|
39
|
+
let(:song) { Song.new("Broken") }
|
40
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
41
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
42
|
+
let(:artist) { Artist.new("Bad Religion") }
|
43
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
46
44
|
|
47
|
-
let
|
45
|
+
let(:form) { AlbumForm.new(album) }
|
48
46
|
|
49
47
|
it do
|
50
|
-
form.date
|
51
|
-
form.songs[0].date
|
48
|
+
assert_equal form.date, "May 16"
|
49
|
+
assert_equal form.songs[0].date, "May 16"
|
52
50
|
end
|
53
51
|
|
54
52
|
# it { subject.class.include?(Reform::Form::ActiveModel) }
|
@@ -62,4 +60,4 @@ class FeatureInheritanceTest < BaseTest
|
|
62
60
|
# it { subject.band.label.is_a?(Reform::Form::ActiveModel) }
|
63
61
|
# it { subject.band.label.is_a?(Reform::Form::Coercion) }
|
64
62
|
# it { subject.band.label.is_a?(Reform::Form::MultiParameterAttributes) }
|
65
|
-
end
|
63
|
+
end
|
@@ -1,91 +1,104 @@
|
|
1
1
|
en:
|
2
|
-
|
3
|
-
|
2
|
+
dry_validation:
|
3
|
+
errors:
|
4
|
+
array?: "must be an array"
|
5
|
+
|
6
|
+
empty?: "must be empty"
|
7
|
+
|
8
|
+
excludes?: "must not include %{value}"
|
9
|
+
|
10
|
+
excluded_from?:
|
11
|
+
arg:
|
12
|
+
default: "must not be one of: %{list}"
|
13
|
+
range: "must not be one of: %{list_left} - %{list_right}"
|
4
14
|
|
5
|
-
|
15
|
+
eql?: "must be equal to %{left}"
|
6
16
|
|
7
|
-
|
17
|
+
not_eql?: "must not be equal to %{left}"
|
8
18
|
|
9
|
-
|
10
|
-
arg:
|
11
|
-
default: "must not be one of: %{list}"
|
12
|
-
range: "must not be one of: %{list_left} - %{list_right}"
|
19
|
+
filled?: "must be filled"
|
13
20
|
|
14
|
-
|
21
|
+
format?: "is in invalid format"
|
15
22
|
|
16
|
-
|
23
|
+
number?: "must be a number"
|
17
24
|
|
18
|
-
|
25
|
+
odd?: "must be odd"
|
19
26
|
|
20
|
-
|
27
|
+
even?: "must be even"
|
21
28
|
|
22
|
-
|
29
|
+
gt?: "must be greater than %{num}"
|
23
30
|
|
24
|
-
|
31
|
+
gteq?: "must be greater than or equal to %{num}"
|
25
32
|
|
26
|
-
|
33
|
+
hash?: "must be a hash"
|
27
34
|
|
28
|
-
|
35
|
+
included_in?:
|
36
|
+
arg:
|
37
|
+
default: "must be one of: %{list}"
|
38
|
+
range: "must be one of: %{list_left} - %{list_right}"
|
29
39
|
|
30
|
-
|
40
|
+
includes?: "must include %{value}"
|
31
41
|
|
32
|
-
|
42
|
+
bool?: "must be boolean"
|
33
43
|
|
34
|
-
|
35
|
-
arg:
|
36
|
-
default: "must be one of: %{list}"
|
37
|
-
range: "must be one of: %{list_left} - %{list_right}"
|
44
|
+
true?: "must be true"
|
38
45
|
|
39
|
-
|
46
|
+
false?: "must be false"
|
40
47
|
|
41
|
-
|
48
|
+
int?: "must be an integer"
|
42
49
|
|
43
|
-
|
50
|
+
float?: "must be a float"
|
44
51
|
|
45
|
-
|
52
|
+
decimal?: "must be a decimal"
|
46
53
|
|
47
|
-
|
54
|
+
date?: "must be a date"
|
48
55
|
|
49
|
-
|
56
|
+
date_time?: "must be a date time"
|
50
57
|
|
51
|
-
|
58
|
+
time?: "must be a time"
|
52
59
|
|
53
|
-
|
60
|
+
key?: "is missing"
|
54
61
|
|
55
|
-
|
62
|
+
attr?: "is missing"
|
56
63
|
|
57
|
-
|
64
|
+
lt?: "must be less than %{num}"
|
58
65
|
|
59
|
-
|
66
|
+
lteq?: "must be less than or equal to %{num}"
|
60
67
|
|
61
|
-
|
68
|
+
max_size?: "size cannot be greater than %{num}"
|
62
69
|
|
63
|
-
|
70
|
+
min_size?: "size cannot be less than %{num}"
|
64
71
|
|
65
|
-
|
72
|
+
none?: "cannot be defined"
|
66
73
|
|
67
|
-
|
74
|
+
str?: "must be a string"
|
68
75
|
|
69
|
-
|
76
|
+
type?: "must be %{type}"
|
70
77
|
|
71
|
-
|
78
|
+
size?:
|
79
|
+
arg:
|
80
|
+
default: "size must be %{size}"
|
81
|
+
range: "size must be within %{size_left} - %{size_right}"
|
72
82
|
|
73
|
-
|
83
|
+
value:
|
84
|
+
string:
|
85
|
+
arg:
|
86
|
+
default: "length must be %{size}"
|
87
|
+
range: "length must be within %{size_left} - %{size_right}"
|
74
88
|
|
75
|
-
type?: "must be %{type}"
|
76
89
|
|
77
|
-
size?:
|
78
|
-
arg:
|
79
|
-
default: "size must be %{size}"
|
80
|
-
range: "size must be within %{size_left} - %{size_right}"
|
81
90
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
91
|
+
rules:
|
92
|
+
name:
|
93
|
+
good_musical_taste?: "you're a bad person"
|
94
|
+
title:
|
95
|
+
good_musical_taste?: "you're a bad person"
|
96
|
+
songs:
|
97
|
+
a_song?: "must have at least one enabled song"
|
98
|
+
artist:
|
99
|
+
with_last_name?: "must have last name"
|
87
100
|
|
88
|
-
good_musical_taste?: "you're a bad person"
|
89
101
|
de:
|
102
|
+
dry_validation:
|
90
103
|
errors:
|
91
|
-
|
104
|
+
filled?: "muss abgefüllt sein"
|
data/test/form_option_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class FormOptionTest < MiniTest::Spec
|
4
4
|
Song = Struct.new(:title)
|
@@ -7,7 +7,7 @@ class FormOptionTest < MiniTest::Spec
|
|
7
7
|
class SongForm < TestForm
|
8
8
|
property :title
|
9
9
|
validation do
|
10
|
-
required(:title).filled
|
10
|
+
params { required(:title).filled }
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -17,7 +17,7 @@ class FormOptionTest < MiniTest::Spec
|
|
17
17
|
|
18
18
|
it do
|
19
19
|
form = AlbumForm.new(Album.new(Song.new("When It Comes To You")))
|
20
|
-
|
20
|
+
assert_equal "When It Comes To You", form.song.title
|
21
21
|
|
22
22
|
form.validate(song: {title: "Run For Cover"})
|
23
23
|
end
|
data/test/form_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class FormTest < MiniTest::Spec
|
4
4
|
Artist = Struct.new(:name)
|
@@ -22,11 +22,11 @@ class FormTest < MiniTest::Spec
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe "::dup" do
|
25
|
-
let
|
25
|
+
let(:cloned) { AlbumForm.clone }
|
26
26
|
|
27
27
|
# #dup is called in Op.inheritable_attr(:contract_class), it must be subclass of the original one.
|
28
|
-
it { cloned
|
29
|
-
it { AlbumForm.definitions
|
28
|
+
it { refute_equal cloned, AlbumForm }
|
29
|
+
it { refute_equal AlbumForm.definitions, cloned.definitions }
|
30
30
|
|
31
31
|
it do
|
32
32
|
# currently, forms need a name for validation, even without AM.
|
@@ -37,7 +37,7 @@ class FormTest < MiniTest::Spec
|
|
37
37
|
end
|
38
38
|
|
39
39
|
cloned.validation do
|
40
|
-
required(:title).filled
|
40
|
+
params { required(:title).filled }
|
41
41
|
end
|
42
42
|
|
43
43
|
cloned.new(OpenStruct.new).validate({})
|
@@ -51,7 +51,7 @@ class FormTest < MiniTest::Spec
|
|
51
51
|
end
|
52
52
|
|
53
53
|
it "allows injecting :virtual options" do
|
54
|
-
ArtistForm.new(Artist.new, current_user: Object).current_user
|
54
|
+
assert_equal ArtistForm.new(Artist.new, current_user: Object).current_user, Object
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
data/test/from_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class AsTest < BaseTest
|
4
4
|
class AlbumForm < TestForm
|
@@ -19,46 +19,42 @@ class AsTest < BaseTest
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
let
|
22
|
+
let(:song2) { Song.new("Roxanne") }
|
23
23
|
|
24
|
-
let
|
24
|
+
let(:params) do
|
25
25
|
{
|
26
26
|
"name" => "Best Of The Police",
|
27
|
-
"single"
|
27
|
+
"single" => {"title" => "So Lonely"},
|
28
28
|
"tracks" => [{"name" => "Message In A Bottle"}, {"name" => "Roxanne"}]
|
29
29
|
}
|
30
|
-
|
30
|
+
end
|
31
31
|
|
32
32
|
subject { AlbumForm.new(Album.new("Best Of", hit, [Song.new("Fallout"), song2])) }
|
33
33
|
|
34
|
-
it { subject.name
|
35
|
-
it { subject.single.title
|
36
|
-
it { subject.tracks[0].name
|
37
|
-
it { subject.tracks[1].name
|
38
|
-
|
34
|
+
it { assert_equal subject.name, "Best Of" }
|
35
|
+
it { assert_equal subject.single.title, "Roxanne" }
|
36
|
+
it { assert_equal subject.tracks[0].name, "Fallout" }
|
37
|
+
it { assert_equal subject.tracks[1].name, "Roxanne" }
|
39
38
|
|
40
39
|
describe "#validate" do
|
41
40
|
|
42
|
-
|
43
41
|
before { subject.validate(params) }
|
44
42
|
|
45
|
-
it { subject.name
|
46
|
-
it { subject.single.title
|
47
|
-
it { subject.tracks[0].name
|
48
|
-
it { subject.tracks[1].name
|
43
|
+
it { assert_equal subject.name, "Best Of The Police" }
|
44
|
+
it { assert_equal subject.single.title, "So Lonely" }
|
45
|
+
it { assert_equal subject.tracks[0].name, "Message In A Bottle" }
|
46
|
+
it { assert_equal subject.tracks[1].name, "Roxanne" }
|
49
47
|
end
|
50
48
|
|
51
|
-
|
52
49
|
describe "#sync" do
|
53
|
-
before
|
50
|
+
before do
|
54
51
|
subject.tracks[1].name = "Livin' Ain't No Crime"
|
55
52
|
subject.sync
|
56
|
-
|
53
|
+
end
|
57
54
|
|
58
|
-
it { song2.title
|
55
|
+
it { assert_equal song2.title, "Livin' Ain't No Crime" }
|
59
56
|
end
|
60
57
|
|
61
|
-
|
62
58
|
describe "#save (nested hash)" do
|
63
59
|
before { subject.validate(params) }
|
64
60
|
|
@@ -69,7 +65,7 @@ class AsTest < BaseTest
|
|
69
65
|
hash = nested_hash
|
70
66
|
end
|
71
67
|
|
72
|
-
hash
|
68
|
+
assert_equal hash, "title" => "Best Of The Police", "hit" => {"title" => "So Lonely"}, "songs" => [{"title" => "Message In A Bottle"}, {"title" => "Roxanne"}], "band" => nil
|
73
69
|
end
|
74
70
|
end
|
75
71
|
end
|
data/test/inherit_test.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "test_helper"
|
2
|
+
require "representable/json"
|
3
3
|
|
4
4
|
class InheritTest < BaseTest
|
5
5
|
Populator = Reform::Form::Populator
|
6
6
|
|
7
7
|
class SkipParse
|
8
8
|
include Uber::Callable
|
9
|
-
def call(*
|
9
|
+
def call(*_args)
|
10
10
|
false
|
11
11
|
end
|
12
12
|
end
|
@@ -14,19 +14,18 @@ class InheritTest < BaseTest
|
|
14
14
|
class AlbumForm < TestForm
|
15
15
|
property :title, deserializer: {instance: "Instance"}, skip_if: "skip_if in AlbumForm" # allow direct configuration of :deserializer.
|
16
16
|
|
17
|
-
property :hit, populate_if_empty: ->
|
17
|
+
property :hit, populate_if_empty: ->(*) { Song.new } do
|
18
18
|
property :title
|
19
19
|
validation do
|
20
|
-
required(:title).filled
|
20
|
+
params { required(:title).filled }
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
collection :songs, populate_if_empty:
|
24
|
+
collection :songs, populate_if_empty: -> {}, skip_if: :all_blank do
|
25
25
|
property :title
|
26
26
|
end
|
27
27
|
|
28
|
-
property :band, populate_if_empty:
|
29
|
-
|
28
|
+
property :band, populate_if_empty: -> {} do
|
30
29
|
def band_id
|
31
30
|
1
|
32
31
|
end
|
@@ -35,10 +34,10 @@ class InheritTest < BaseTest
|
|
35
34
|
|
36
35
|
class CompilationForm < AlbumForm
|
37
36
|
property :title, inherit: true, skip_if: "skip_if from CompilationForm"
|
38
|
-
property :hit, :
|
37
|
+
property :hit, inherit: true, populate_if_empty: ->(*) { Song.new }, skip_if: SkipParse.new do
|
39
38
|
property :rating
|
40
39
|
validation do
|
41
|
-
required(:rating).filled
|
40
|
+
params { required(:rating).filled }
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
@@ -48,59 +47,53 @@ class InheritTest < BaseTest
|
|
48
47
|
end
|
49
48
|
end
|
50
49
|
|
51
|
-
let
|
50
|
+
let(:album) { Album.new(nil, Song.new, [], Band.new) }
|
52
51
|
subject { CompilationForm.new(album) }
|
53
52
|
|
54
53
|
it do
|
55
|
-
subject.validate(
|
56
|
-
subject.hit.title
|
57
|
-
subject.hit.rating
|
58
|
-
subject.errors.messages
|
54
|
+
subject.validate("hit" => {"title" => "LA Drone", "rating" => 10})
|
55
|
+
assert_equal subject.hit.title, "LA Drone"
|
56
|
+
assert_equal subject.hit.rating, 10
|
57
|
+
assert_equal subject.errors.messages, {}
|
59
58
|
end
|
60
59
|
|
61
60
|
it do
|
62
61
|
subject.validate({})
|
63
62
|
assert_nil subject.model.hit.title
|
64
63
|
assert_nil subject.model.hit.rating
|
65
|
-
subject.errors.messages
|
64
|
+
assert_equal subject.errors.messages, "hit.title": ["must be filled"], "hit.rating": ["must be filled"]
|
66
65
|
end
|
67
66
|
|
68
67
|
it "xxx" do
|
69
68
|
# sub hashes like :deserializer must be properly cloned when inheriting.
|
70
|
-
AlbumForm.options_for(:title)[:deserializer].object_id
|
69
|
+
refute_equal AlbumForm.options_for(:title)[:deserializer].object_id, CompilationForm.options_for(:title)[:deserializer].object_id
|
71
70
|
|
72
71
|
# don't overwrite direct deserializer: {} configuration.
|
73
|
-
AlbumForm.options_for(:title)[:internal_populator].
|
74
|
-
AlbumForm.options_for(:title)[:deserializer][:skip_parse]
|
72
|
+
assert AlbumForm.options_for(:title)[:internal_populator].is_a? Reform::Form::Populator::Sync
|
73
|
+
assert_equal AlbumForm.options_for(:title)[:deserializer][:skip_parse], "skip_if in AlbumForm"
|
75
74
|
|
76
75
|
# AlbumForm.options_for(:hit)[:internal_populator].inspect.must_match /Reform::Form::Populator:.+ @user_proc="Populator"/
|
77
76
|
# AlbumForm.options_for(:hit)[:deserializer][:instance].inspect.must_be_instance_with Reform::Form::Populator, user_proc: "Populator"
|
78
77
|
|
78
|
+
assert AlbumForm.options_for(:songs)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
|
79
|
+
assert AlbumForm.options_for(:songs)[:deserializer][:skip_parse].is_a? Reform::Form::Validate::Skip::AllBlank
|
79
80
|
|
80
|
-
AlbumForm.options_for(:
|
81
|
-
AlbumForm.options_for(:songs)[:deserializer][:skip_parse].must_be_instance_of Reform::Form::Validate::Skip::AllBlank
|
82
|
-
|
83
|
-
AlbumForm.options_for(:band)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
|
84
|
-
|
85
|
-
|
81
|
+
assert AlbumForm.options_for(:band)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
|
86
82
|
|
87
|
-
CompilationForm.options_for(:title)[:deserializer][:skip_parse]
|
83
|
+
assert_equal CompilationForm.options_for(:title)[:deserializer][:skip_parse], "skip_if from CompilationForm"
|
88
84
|
# pp CompilationForm.options_for(:songs)
|
89
|
-
CompilationForm.options_for(:songs)[:internal_populator].
|
85
|
+
assert CompilationForm.options_for(:songs)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
|
90
86
|
|
91
|
-
|
92
|
-
CompilationForm.options_for(:band)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
|
87
|
+
assert CompilationForm.options_for(:band)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
|
93
88
|
|
94
89
|
# completely overwrite inherited.
|
95
|
-
CompilationForm.options_for(:hit)[:deserializer][:skip_parse].
|
96
|
-
|
90
|
+
assert CompilationForm.options_for(:hit)[:deserializer][:skip_parse].is_a? SkipParse
|
97
91
|
|
98
92
|
# inherit: true with block will still inherit the original class.
|
99
|
-
AlbumForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id
|
100
|
-
CompilationForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id
|
93
|
+
assert_equal AlbumForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id, 1
|
94
|
+
assert_equal CompilationForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id, 1
|
101
95
|
end
|
102
96
|
|
103
|
-
|
104
97
|
class CDForm < AlbumForm
|
105
98
|
# override :band's original populate_if_empty but with :inherit.
|
106
99
|
property :band, inherit: true, populator: "CD Populator" do
|
@@ -108,5 +101,5 @@ class InheritTest < BaseTest
|
|
108
101
|
end
|
109
102
|
end
|
110
103
|
|
111
|
-
it { CDForm.options_for(:band)[:internal_populator].instance_variable_get(:@user_proc)
|
104
|
+
it { assert_equal CDForm.options_for(:band)[:internal_populator].instance_variable_get(:@user_proc), "CD Populator" }
|
112
105
|
end
|