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/populator_skip_test.rb
CHANGED
@@ -4,7 +4,6 @@ class PopulatorSkipTest < MiniTest::Spec
|
|
4
4
|
Album = Struct.new(:songs)
|
5
5
|
Song = Struct.new(:title)
|
6
6
|
|
7
|
-
|
8
7
|
class AlbumForm < TestForm
|
9
8
|
collection :songs, populator: :my_populator do
|
10
9
|
property :title
|
@@ -22,8 +21,8 @@ class PopulatorSkipTest < MiniTest::Spec
|
|
22
21
|
|
23
22
|
form.validate(hash)
|
24
23
|
|
25
|
-
form.songs.size
|
24
|
+
assert_equal form.songs.size, 2
|
26
25
|
assert_nil form.songs[0].title
|
27
|
-
form.songs[1].title
|
26
|
+
assert_equal form.songs[1].title, "Bad"
|
28
27
|
end
|
29
|
-
end
|
28
|
+
end
|
data/test/prepopulator_test.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class PrepopulatorTest < MiniTest::Spec
|
4
4
|
Song = Struct.new(:title, :band, :length)
|
5
5
|
Band = Struct.new(:name)
|
6
6
|
|
7
7
|
class AlbumForm < TestForm
|
8
|
-
property :title, prepopulator: ->(*){ self.title = "Another Day At Work" } # normal assignment.
|
8
|
+
property :title, prepopulator: ->(*) { self.title = "Another Day At Work" } # normal assignment.
|
9
9
|
property :length
|
10
10
|
|
11
11
|
property :hit, prepopulator: ->(options) { self.hit = Song.new(options[:title]) } do # use user options.
|
12
12
|
property :title
|
13
13
|
|
14
|
-
property :band, prepopulator: ->(options){ self.band = my_band(options[:title]) } do # invoke your own code.
|
14
|
+
property :band, prepopulator: ->(options) { self.band = my_band(options[:title]) } do # invoke your own code.
|
15
15
|
property :name
|
16
16
|
end
|
17
17
|
|
@@ -24,7 +24,7 @@ class PrepopulatorTest < MiniTest::Spec
|
|
24
24
|
property :title
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
private
|
28
28
|
def prepopulate_songs!(options)
|
29
29
|
if songs == nil
|
30
30
|
self.songs = [Song.new, Song.new]
|
@@ -37,25 +37,25 @@ class PrepopulatorTest < MiniTest::Spec
|
|
37
37
|
it do
|
38
38
|
form = AlbumForm.new(OpenStruct.new(length: 1)).prepopulate!(title: "Potemkin City Limits")
|
39
39
|
|
40
|
-
form.length
|
41
|
-
form.title
|
42
|
-
form.hit.model
|
43
|
-
form.songs.size
|
44
|
-
form.songs[0].model
|
45
|
-
form.songs[1].model
|
46
|
-
form.songs[1].model
|
40
|
+
assert_equal form.length, 1
|
41
|
+
assert_equal form.title, "Another Day At Work"
|
42
|
+
assert_equal form.hit.model, Song.new("Potemkin City Limits")
|
43
|
+
assert_equal form.songs.size, 2
|
44
|
+
assert_equal form.songs[0].model, Song.new
|
45
|
+
assert_equal form.songs[1].model, Song.new
|
46
|
+
assert_equal form.songs[1].model, Song.new
|
47
47
|
# prepopulate works more than 1 level, recursive.
|
48
48
|
# it also passes options properly down there.
|
49
|
-
form.hit.band.model
|
49
|
+
assert_equal form.hit.band.model, Band.new("Potemkin City Limits")
|
50
50
|
end
|
51
51
|
|
52
52
|
# add to existing collection.
|
53
53
|
it do
|
54
54
|
form = AlbumForm.new(OpenStruct.new(songs: [Song.new])).prepopulate!
|
55
55
|
|
56
|
-
form.songs.size
|
57
|
-
form.songs[0].model
|
58
|
-
form.songs[1].model
|
56
|
+
assert_equal form.songs.size, 2
|
57
|
+
assert_equal form.songs[0].model, Song.new
|
58
|
+
assert_equal form.songs[1].model, Song.new
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -75,10 +75,9 @@ class PrepopulateWithoutConfiguration < MiniTest::Spec
|
|
75
75
|
|
76
76
|
subject { AlbumForm.new(OpenStruct.new(songs: [], hit: nil)).prepopulate! }
|
77
77
|
|
78
|
-
it { subject.songs.size
|
78
|
+
it { assert_equal subject.songs.size, 0 }
|
79
79
|
end
|
80
80
|
|
81
|
-
|
82
81
|
class ManualPrepopulatorOverridingTest < MiniTest::Spec
|
83
82
|
Song = Struct.new(:title, :band, :length)
|
84
83
|
Band = Struct.new(:name)
|
@@ -105,8 +104,8 @@ class ManualPrepopulatorOverridingTest < MiniTest::Spec
|
|
105
104
|
it do
|
106
105
|
form = AlbumForm.new(OpenStruct.new(length: 1)).prepopulate!(title: "Potemkin City Limits")
|
107
106
|
|
108
|
-
form.length
|
109
|
-
form.hit.model
|
110
|
-
form.hit.title
|
107
|
+
assert_equal form.length, 1
|
108
|
+
assert_equal form.hit.model, Song.new("Potemkin City Limits")
|
109
|
+
assert_equal form.hit.title, "Potemkin City Limits"
|
111
110
|
end
|
112
|
-
end
|
111
|
+
end
|
data/test/read_only_test.rb
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
|
+
class ReadonlyTest < MiniTest::Spec
|
4
|
+
class SongForm < TestForm
|
5
|
+
property :artist
|
6
|
+
property :title, writeable: false
|
7
|
+
# TODO: what to do with virtual values?
|
8
|
+
end
|
3
9
|
|
10
|
+
let(:form) { SongForm.new(OpenStruct.new) }
|
11
|
+
|
12
|
+
it { refute form.readonly?(:artist) }
|
13
|
+
it { assert form.readonly?(:title) }
|
14
|
+
end
|
data/test/readable_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class ReadableTest < MiniTest::Spec
|
4
4
|
Credentials = Struct.new(:password)
|
@@ -7,24 +7,24 @@ class ReadableTest < MiniTest::Spec
|
|
7
7
|
property :password, readable: false
|
8
8
|
end
|
9
9
|
|
10
|
-
let
|
11
|
-
let
|
10
|
+
let(:cred) { Credentials.new }
|
11
|
+
let(:form) { PasswordForm.new(cred) }
|
12
12
|
|
13
13
|
it {
|
14
14
|
assert_nil form.password # password not read.
|
15
15
|
|
16
16
|
form.validate("password" => "123")
|
17
17
|
|
18
|
-
form.password
|
18
|
+
assert_equal form.password, "123"
|
19
19
|
|
20
20
|
form.sync
|
21
|
-
cred.password
|
21
|
+
assert_equal cred.password, "123" # password written.
|
22
22
|
|
23
23
|
hash = {}
|
24
24
|
form.save do |nested|
|
25
25
|
hash = nested
|
26
26
|
end
|
27
27
|
|
28
|
-
hash
|
28
|
+
assert_equal hash, "password" => "123"
|
29
29
|
}
|
30
|
-
end
|
30
|
+
end
|
data/test/reform_test.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
|
-
# TODO: this test should be removed.
|
4
3
|
class ReformTest < Minitest::Spec
|
5
|
-
let
|
4
|
+
let(:comp) { OpenStruct.new(name: "Duran Duran", title: "Rio") }
|
6
5
|
|
7
|
-
let
|
6
|
+
let(:form) { SongForm.new(comp) }
|
8
7
|
|
9
8
|
class SongForm < TestForm
|
10
9
|
property :name
|
11
10
|
property :title
|
12
11
|
|
13
12
|
validation do
|
14
|
-
required(:name).filled
|
13
|
+
params { required(:name).filled }
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
17
|
describe "(new) form with empty models" do
|
19
|
-
let
|
18
|
+
let(:comp) { OpenStruct.new }
|
20
19
|
|
21
20
|
it "returns empty fields" do
|
22
21
|
assert_nil form.title
|
23
|
-
form.name
|
22
|
+
assert_nil form.name
|
24
23
|
end
|
25
24
|
|
26
25
|
describe "and submitted values" do
|
@@ -28,20 +27,20 @@ class ReformTest < Minitest::Spec
|
|
28
27
|
form.validate("name" => "Duran Duran")
|
29
28
|
|
30
29
|
assert_nil form.title
|
31
|
-
form.name
|
30
|
+
assert_equal form.name, "Duran Duran"
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
35
|
describe "(edit) form with existing models" do
|
37
36
|
it "returns filled-out fields" do
|
38
|
-
form.name
|
39
|
-
form.title
|
37
|
+
assert_equal form.name, "Duran Duran"
|
38
|
+
assert_equal form.title, "Rio"
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
42
|
describe "#validate" do
|
44
|
-
let
|
43
|
+
let(:comp) { OpenStruct.new }
|
45
44
|
|
46
45
|
it "ignores unmapped fields in input" do
|
47
46
|
form.validate("name" => "Duran Duran", :genre => "80s")
|
@@ -51,13 +50,13 @@ class ReformTest < Minitest::Spec
|
|
51
50
|
end
|
52
51
|
|
53
52
|
it "returns true when valid" do
|
54
|
-
form.validate("name" => "Duran Duran")
|
53
|
+
assert_equal form.validate("name" => "Duran Duran"), true
|
55
54
|
end
|
56
55
|
|
57
56
|
it "exposes input via property accessors" do
|
58
57
|
form.validate("name" => "Duran Duran")
|
59
58
|
|
60
|
-
form.name
|
59
|
+
assert_equal form.name, "Duran Duran"
|
61
60
|
end
|
62
61
|
|
63
62
|
it "doesn't change model properties" do
|
@@ -73,33 +72,35 @@ class ReformTest < Minitest::Spec
|
|
73
72
|
property :title
|
74
73
|
|
75
74
|
validation do
|
76
|
-
|
77
|
-
|
75
|
+
params do
|
76
|
+
required(:name).filled
|
77
|
+
required(:title).filled
|
78
|
+
end
|
78
79
|
end
|
79
80
|
end
|
80
|
-
let
|
81
|
+
let(:form) { ValidatingForm.new(comp) }
|
81
82
|
|
82
83
|
it "returns false when invalid" do
|
83
|
-
form.validate({})
|
84
|
+
assert_equal form.validate({}), false
|
84
85
|
end
|
85
86
|
|
86
87
|
it "populates errors" do
|
87
88
|
form.validate({})
|
88
|
-
form.errors.messages
|
89
|
+
assert_equal form.errors.messages, name: ["must be filled"], title: ["must be filled"]
|
89
90
|
end
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
93
94
|
describe "#save" do
|
94
|
-
let
|
95
|
-
let
|
95
|
+
let(:comp) { OpenStruct.new }
|
96
|
+
let(:form) { SongForm.new(comp) }
|
96
97
|
|
97
98
|
before { form.validate("name" => "Diesel Boy") }
|
98
99
|
|
99
100
|
it "xxpushes data to models" do
|
100
101
|
form.save
|
101
102
|
|
102
|
-
comp.name
|
103
|
+
assert_equal comp.name, "Diesel Boy"
|
103
104
|
assert_nil comp.title
|
104
105
|
end
|
105
106
|
|
@@ -111,42 +112,39 @@ class ReformTest < Minitest::Spec
|
|
111
112
|
hash = map
|
112
113
|
end
|
113
114
|
|
114
|
-
hash
|
115
|
+
assert_equal hash, "name" => "Diesel Boy", "title" => nil
|
115
116
|
end
|
116
117
|
end
|
117
118
|
end
|
118
119
|
|
119
|
-
|
120
120
|
describe "#model" do
|
121
|
-
it { form.model
|
121
|
+
it { assert_equal form.model, comp }
|
122
122
|
end
|
123
123
|
|
124
|
-
|
125
124
|
describe "inheritance" do
|
126
125
|
class HitForm < SongForm
|
127
126
|
property :position
|
128
127
|
validation do
|
129
|
-
required(:position).filled
|
128
|
+
params { required(:position).filled }
|
130
129
|
end
|
131
130
|
end
|
132
131
|
|
133
|
-
let
|
132
|
+
let(:form) { HitForm.new(OpenStruct.new()) }
|
134
133
|
it do
|
135
|
-
form.validate(
|
136
|
-
form.title
|
134
|
+
form.validate("title" => "The Body")
|
135
|
+
assert_equal form.title, "The Body"
|
137
136
|
assert_nil form.position
|
138
|
-
form.errors.messages
|
137
|
+
assert_equal form.errors.messages, name: ["must be filled"], position: ["must be filled"]
|
139
138
|
end
|
140
139
|
end
|
141
140
|
end
|
142
141
|
|
143
|
-
|
144
142
|
class OverridingAccessorsTest < BaseTest
|
145
143
|
class SongForm < TestForm
|
146
144
|
property :title
|
147
145
|
|
148
146
|
def title=(v) # used in #validate.
|
149
|
-
super v*2
|
147
|
+
super v * 2
|
150
148
|
end
|
151
149
|
|
152
150
|
def title # used in #sync.
|
@@ -154,23 +152,22 @@ class OverridingAccessorsTest < BaseTest
|
|
154
152
|
end
|
155
153
|
end
|
156
154
|
|
157
|
-
let
|
155
|
+
let(:song) { Song.new("Pray") }
|
158
156
|
subject { SongForm.new(song) }
|
159
157
|
|
160
158
|
# override reader for presentation.
|
161
|
-
it { subject.title
|
162
|
-
|
159
|
+
it { assert_equal subject.title, "pray" }
|
163
160
|
|
164
161
|
describe "#save" do
|
165
162
|
before { subject.validate("title" => "Hey Little World") }
|
166
163
|
|
167
164
|
# reader always used
|
168
|
-
it { subject.title
|
165
|
+
it { assert_equal subject.title, "hey little worldhey little world" }
|
169
166
|
|
170
167
|
# the reader is not used when saving/syncing.
|
171
168
|
it do
|
172
169
|
subject.save do |hash|
|
173
|
-
hash["title"]
|
170
|
+
assert_equal hash["title"], "Hey Little WorldHey Little World"
|
174
171
|
end
|
175
172
|
end
|
176
173
|
|
@@ -178,12 +175,11 @@ class OverridingAccessorsTest < BaseTest
|
|
178
175
|
it do
|
179
176
|
song.extend(Saveable)
|
180
177
|
subject.save
|
181
|
-
song.title
|
178
|
+
assert_equal song.title, "Hey Little WorldHey Little World"
|
182
179
|
end
|
183
180
|
end
|
184
181
|
end
|
185
182
|
|
186
|
-
|
187
183
|
class MethodInFormTest < MiniTest::Spec
|
188
184
|
class AlbumForm < TestForm
|
189
185
|
property :title
|
@@ -202,7 +198,7 @@ class MethodInFormTest < MiniTest::Spec
|
|
202
198
|
end
|
203
199
|
|
204
200
|
# methods can be used instead of created accessors.
|
205
|
-
subject { AlbumForm.new(OpenStruct.new(:
|
206
|
-
it { subject.title
|
207
|
-
it { subject.hit.title
|
201
|
+
subject { AlbumForm.new(OpenStruct.new(hit: OpenStruct.new)) }
|
202
|
+
it { assert_equal subject.title, "The Suffer And The Witness" }
|
203
|
+
it { assert_equal subject.hit.title, "Drones" }
|
208
204
|
end
|
data/test/save_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class SaveTest < BaseTest
|
4
4
|
Song = Struct.new(:title, :album, :composer)
|
@@ -8,19 +8,19 @@ class SaveTest < BaseTest
|
|
8
8
|
class AlbumForm < TestForm
|
9
9
|
property :name
|
10
10
|
validation do
|
11
|
-
required(:name).filled
|
11
|
+
params { required(:name).filled }
|
12
12
|
end
|
13
13
|
|
14
14
|
collection :songs do
|
15
15
|
property :title
|
16
16
|
validation do
|
17
|
-
required(:title).filled
|
17
|
+
params { required(:title).filled }
|
18
18
|
end
|
19
19
|
|
20
20
|
property :composer do
|
21
21
|
property :name
|
22
22
|
validation do
|
23
|
-
required(:name).filled
|
23
|
+
params { required(:name).filled }
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -40,24 +40,22 @@ class SaveTest < BaseTest
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
let(:song) { Song.new("Broken").extend(Saveable) }
|
44
|
+
# let(:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
|
45
|
+
let(:composer) { Artist.new("Greg Graffin").extend(Saveable) }
|
46
|
+
let(:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
|
47
|
+
let(:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
|
43
48
|
|
44
|
-
let
|
45
|
-
# let (:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
|
46
|
-
let (:composer) { Artist.new("Greg Graffin").extend(Saveable) }
|
47
|
-
let (:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
|
48
|
-
let (:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
|
49
|
-
|
50
|
-
let (:form) { AlbumForm.new(album) }
|
51
|
-
|
49
|
+
let(:form) { AlbumForm.new(album) }
|
52
50
|
|
53
51
|
it do
|
54
52
|
form.validate("songs" => [{"title" => "Fixed"}])
|
55
53
|
|
56
54
|
form.save
|
57
55
|
|
58
|
-
album.saved
|
59
|
-
album.songs[0].title
|
60
|
-
album.songs[0].saved
|
56
|
+
assert album.saved?
|
57
|
+
assert_equal album.songs[0].title, "Fixed"
|
58
|
+
assert album.songs[0].saved?
|
61
59
|
assert_nil album.artist.saved?
|
62
60
|
end
|
63
61
|
|
@@ -72,12 +70,11 @@ class SaveTest < BaseTest
|
|
72
70
|
nested_hash = hash
|
73
71
|
end
|
74
72
|
|
75
|
-
nested_hash
|
73
|
+
assert_equal nested_hash, "name" => nil, "artist" => nil
|
76
74
|
end
|
77
75
|
end
|
78
76
|
end
|
79
77
|
|
80
|
-
|
81
78
|
# class SaveWithDynamicOptionsTest < MiniTest::Spec
|
82
79
|
# Song = Struct.new(:id, :title, :length) do
|
83
80
|
# include Saveable
|
@@ -88,8 +85,8 @@ end
|
|
88
85
|
# property :length, virtual: true
|
89
86
|
# end
|
90
87
|
|
91
|
-
# let
|
92
|
-
# let
|
88
|
+
# let(:song) { Song.new }
|
89
|
+
# let(:form) { SongForm.new(song) }
|
93
90
|
|
94
91
|
# # we have access to original input value and outside parameters.
|
95
92
|
# it "xxx" do
|