reform 2.2.4
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 +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +11 -0
- data/CHANGES.md +415 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +339 -0
- data/Rakefile +15 -0
- data/TODO.md +45 -0
- data/gemfiles/Gemfile.disposable-0.3 +6 -0
- data/lib/reform.rb +8 -0
- data/lib/reform/contract.rb +77 -0
- data/lib/reform/contract/errors.rb +43 -0
- data/lib/reform/contract/validate.rb +33 -0
- data/lib/reform/form.rb +94 -0
- data/lib/reform/form/call.rb +23 -0
- data/lib/reform/form/coercion.rb +3 -0
- data/lib/reform/form/composition.rb +34 -0
- data/lib/reform/form/dry.rb +67 -0
- data/lib/reform/form/module.rb +27 -0
- data/lib/reform/form/mongoid.rb +37 -0
- data/lib/reform/form/orm.rb +26 -0
- data/lib/reform/form/populator.rb +123 -0
- data/lib/reform/form/prepopulate.rb +24 -0
- data/lib/reform/form/validate.rb +60 -0
- data/lib/reform/mongoid.rb +4 -0
- data/lib/reform/validation.rb +40 -0
- data/lib/reform/validation/groups.rb +73 -0
- data/lib/reform/version.rb +3 -0
- data/reform.gemspec +29 -0
- data/test/benchmarking.rb +26 -0
- data/test/call_test.rb +23 -0
- data/test/changed_test.rb +41 -0
- data/test/coercion_test.rb +66 -0
- data/test/composition_test.rb +149 -0
- data/test/contract_test.rb +77 -0
- data/test/default_test.rb +22 -0
- data/test/deprecation_test.rb +27 -0
- data/test/deserialize_test.rb +104 -0
- data/test/errors_test.rb +165 -0
- data/test/feature_test.rb +65 -0
- data/test/fixtures/dry_error_messages.yml +44 -0
- data/test/form_option_test.rb +24 -0
- data/test/form_test.rb +57 -0
- data/test/from_test.rb +75 -0
- data/test/inherit_test.rb +119 -0
- data/test/module_test.rb +142 -0
- data/test/parse_pipeline_test.rb +15 -0
- data/test/populate_test.rb +270 -0
- data/test/populator_skip_test.rb +28 -0
- data/test/prepopulator_test.rb +112 -0
- data/test/read_only_test.rb +3 -0
- data/test/readable_test.rb +30 -0
- data/test/readonly_test.rb +14 -0
- data/test/reform_test.rb +223 -0
- data/test/save_test.rb +89 -0
- data/test/setup_test.rb +48 -0
- data/test/skip_if_test.rb +74 -0
- data/test/skip_setter_and_getter_test.rb +54 -0
- data/test/test_helper.rb +49 -0
- data/test/validate_test.rb +420 -0
- data/test/validation/dry_test.rb +60 -0
- data/test/validation/dry_validation_test.rb +352 -0
- data/test/validation/errors.yml +4 -0
- data/test/virtual_test.rb +24 -0
- data/test/writeable_test.rb +29 -0
- metadata +265 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PrepopulatorTest < MiniTest::Spec
|
4
|
+
Song = Struct.new(:title, :band, :length)
|
5
|
+
Band = Struct.new(:name)
|
6
|
+
|
7
|
+
class AlbumForm < Reform::Form
|
8
|
+
property :title, prepopulator: ->(*){ self.title = "Another Day At Work" } # normal assignment.
|
9
|
+
property :length
|
10
|
+
|
11
|
+
property :hit, prepopulator: ->(options) { self.hit = Song.new(options[:title]) } do # use user options.
|
12
|
+
property :title
|
13
|
+
|
14
|
+
property :band, prepopulator: ->(options){ self.band = my_band(options[:title]) } do # invoke your own code.
|
15
|
+
property :name
|
16
|
+
end
|
17
|
+
|
18
|
+
def my_band(name)
|
19
|
+
Band.new(title)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
collection :songs, prepopulator: :prepopulate_songs! do
|
24
|
+
property :title
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def prepopulate_songs!(options)
|
29
|
+
if songs == nil
|
30
|
+
self.songs = [Song.new, Song.new]
|
31
|
+
else
|
32
|
+
songs << Song.new # full Twin::Collection API available.
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it do
|
38
|
+
form = AlbumForm.new(OpenStruct.new(length: 1)).prepopulate!(title: "Potemkin City Limits")
|
39
|
+
|
40
|
+
form.length.must_equal 1
|
41
|
+
form.title.must_equal "Another Day At Work"
|
42
|
+
form.hit.model.must_equal Song.new("Potemkin City Limits")
|
43
|
+
form.songs.size.must_equal 2
|
44
|
+
form.songs[0].model.must_equal Song.new
|
45
|
+
form.songs[1].model.must_equal Song.new
|
46
|
+
form.songs[1].model.must_equal Song.new
|
47
|
+
# prepopulate works more than 1 level, recursive.
|
48
|
+
# it also passes options properly down there.
|
49
|
+
form.hit.band.model.must_equal Band.new("Potemkin City Limits")
|
50
|
+
end
|
51
|
+
|
52
|
+
# add to existing collection.
|
53
|
+
it do
|
54
|
+
form = AlbumForm.new(OpenStruct.new(songs: [Song.new])).prepopulate!
|
55
|
+
|
56
|
+
form.songs.size.must_equal 2
|
57
|
+
form.songs[0].model.must_equal Song.new
|
58
|
+
form.songs[1].model.must_equal Song.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# calling form.prepopulate! shouldn't crash.
|
63
|
+
class PrepopulateWithoutConfiguration < MiniTest::Spec
|
64
|
+
Song = Struct.new(:title)
|
65
|
+
|
66
|
+
class AlbumForm < Reform::Form
|
67
|
+
collection :songs do
|
68
|
+
property :title
|
69
|
+
end
|
70
|
+
|
71
|
+
property :hit do
|
72
|
+
property :title
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
subject { AlbumForm.new(OpenStruct.new(songs: [], hit: nil)).prepopulate! }
|
77
|
+
|
78
|
+
it { subject.songs.size.must_equal 0 }
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
class ManualPrepopulatorOverridingTest < MiniTest::Spec
|
83
|
+
Song = Struct.new(:title, :band, :length)
|
84
|
+
Band = Struct.new(:name)
|
85
|
+
|
86
|
+
class AlbumForm < Reform::Form
|
87
|
+
property :title
|
88
|
+
property :length
|
89
|
+
|
90
|
+
property :hit do
|
91
|
+
property :title
|
92
|
+
|
93
|
+
property :band do
|
94
|
+
property :name
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def prepopulate!(options)
|
99
|
+
self.hit = Song.new(options[:title])
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# you can simply override Form#prepopulate!
|
105
|
+
it do
|
106
|
+
form = AlbumForm.new(OpenStruct.new(length: 1)).prepopulate!(title: "Potemkin City Limits")
|
107
|
+
|
108
|
+
form.length.must_equal 1
|
109
|
+
form.hit.model.must_equal Song.new("Potemkin City Limits")
|
110
|
+
form.hit.title.must_equal "Potemkin City Limits"
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ReadableTest < MiniTest::Spec
|
4
|
+
Credentials = Struct.new(:password)
|
5
|
+
|
6
|
+
class PasswordForm < Reform::Form
|
7
|
+
property :password, readable: false
|
8
|
+
end
|
9
|
+
|
10
|
+
let (:cred) { Credentials.new }
|
11
|
+
let (:form) { PasswordForm.new(cred) }
|
12
|
+
|
13
|
+
it {
|
14
|
+
form.password.must_equal nil # password not read.
|
15
|
+
|
16
|
+
form.validate("password" => "123")
|
17
|
+
|
18
|
+
form.password.must_equal "123"
|
19
|
+
|
20
|
+
form.sync
|
21
|
+
cred.password.must_equal "123" # password written.
|
22
|
+
|
23
|
+
hash = {}
|
24
|
+
form.save do |nested|
|
25
|
+
hash = nested
|
26
|
+
end
|
27
|
+
|
28
|
+
hash.must_equal("password"=> "123")
|
29
|
+
}
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ReadonlyTest < MiniTest::Spec
|
4
|
+
class SongForm < Reform::Form
|
5
|
+
property :artist
|
6
|
+
property :title, writeable: false
|
7
|
+
# TODO: what to do with virtual values?
|
8
|
+
end
|
9
|
+
|
10
|
+
let (:form) { SongForm.new(OpenStruct.new) }
|
11
|
+
|
12
|
+
it { form.readonly?(:artist).must_equal false }
|
13
|
+
it { form.readonly?(:title).must_equal true }
|
14
|
+
end
|
data/test/reform_test.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# TODO: this test should be removed.
|
4
|
+
class ReformTest < Minitest::Spec
|
5
|
+
let (:comp) { OpenStruct.new(:name => "Duran Duran", :title => "Rio") }
|
6
|
+
|
7
|
+
let (:form) { SongForm.new(comp) }
|
8
|
+
|
9
|
+
class SongForm < Reform::Form
|
10
|
+
property :name
|
11
|
+
property :title
|
12
|
+
|
13
|
+
validation do
|
14
|
+
key(:name).required
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "(new) form with empty models" do
|
19
|
+
let (:comp) { OpenStruct.new }
|
20
|
+
|
21
|
+
it "returns empty fields" do
|
22
|
+
form.title.must_equal nil
|
23
|
+
form.name.must_equal nil
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "and submitted values" do
|
27
|
+
it "returns filled-out fields" do
|
28
|
+
form.validate("name" => "Duran Duran")
|
29
|
+
|
30
|
+
form.title.must_equal nil
|
31
|
+
form.name.must_equal "Duran Duran"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "(edit) form with existing models" do
|
37
|
+
it "returns filled-out fields" do
|
38
|
+
form.name.must_equal "Duran Duran"
|
39
|
+
form.title.must_equal "Rio"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#validate" do
|
44
|
+
let (:comp) { OpenStruct.new }
|
45
|
+
|
46
|
+
it "ignores unmapped fields in input" do
|
47
|
+
form.validate("name" => "Duran Duran", :genre => "80s")
|
48
|
+
assert_raises NoMethodError do
|
49
|
+
form.genre
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns true when valid" do
|
54
|
+
form.validate("name" => "Duran Duran").must_equal true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "exposes input via property accessors" do
|
58
|
+
form.validate("name" => "Duran Duran")
|
59
|
+
|
60
|
+
form.name.must_equal "Duran Duran"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "doesn't change model properties" do
|
64
|
+
form.validate("name" => "Duran Duran")
|
65
|
+
|
66
|
+
comp.name.must_equal nil # don't touch model, yet.
|
67
|
+
end
|
68
|
+
|
69
|
+
# TODO: test errors. test valid.
|
70
|
+
describe "invalid input" do
|
71
|
+
class ValidatingForm < Reform::Form
|
72
|
+
property :name
|
73
|
+
property :title
|
74
|
+
|
75
|
+
validation do
|
76
|
+
key(:name).required
|
77
|
+
key(:title).required
|
78
|
+
end
|
79
|
+
end
|
80
|
+
let (:form) { ValidatingForm.new(comp) }
|
81
|
+
|
82
|
+
it "returns false when invalid" do
|
83
|
+
form.validate({}).must_equal false
|
84
|
+
end
|
85
|
+
|
86
|
+
it "populates errors" do
|
87
|
+
form.validate({})
|
88
|
+
form.errors.messages.must_equal({:name=>["is missing"], :title=>["is missing"]})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# FIXME: add this test to reform-rails.
|
94
|
+
describe "#errors" do
|
95
|
+
before { form.validate({})}
|
96
|
+
|
97
|
+
it { form.errors.must_be_kind_of Reform::Contract::Errors }
|
98
|
+
|
99
|
+
it { form.errors.messages.must_equal({}) }
|
100
|
+
|
101
|
+
it do
|
102
|
+
form.validate({"name"=>""})
|
103
|
+
form.errors.messages.must_equal({:name=>["must be filled"]})
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
describe "#save" do
|
109
|
+
let (:comp) { OpenStruct.new }
|
110
|
+
let (:form) { SongForm.new(comp) }
|
111
|
+
|
112
|
+
before { form.validate("name" => "Diesel Boy") }
|
113
|
+
|
114
|
+
it "xxpushes data to models" do
|
115
|
+
form.save
|
116
|
+
|
117
|
+
comp.name.must_equal "Diesel Boy"
|
118
|
+
comp.title.must_equal nil
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#save with block" do
|
122
|
+
it do
|
123
|
+
hash = {}
|
124
|
+
|
125
|
+
form.save do |map|
|
126
|
+
hash = map
|
127
|
+
end
|
128
|
+
|
129
|
+
hash.must_equal({"name"=>"Diesel Boy"})
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
describe "#model" do
|
136
|
+
it { form.model.must_equal comp }
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
describe "inheritance" do
|
141
|
+
class HitForm < SongForm
|
142
|
+
property :position
|
143
|
+
validation do
|
144
|
+
key(:position).required
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
let (:form) { HitForm.new(OpenStruct.new()) }
|
149
|
+
it do
|
150
|
+
form.validate({"title" => "The Body"})
|
151
|
+
form.title.must_equal "The Body"
|
152
|
+
form.position.must_equal nil
|
153
|
+
form.errors.messages.must_equal({:name=>["is missing"], :position=>["is missing"]})
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
class OverridingAccessorsTest < BaseTest
|
160
|
+
class SongForm < Reform::Form
|
161
|
+
property :title
|
162
|
+
|
163
|
+
def title=(v) # used in #validate.
|
164
|
+
super v*2
|
165
|
+
end
|
166
|
+
|
167
|
+
def title # used in #sync.
|
168
|
+
super.downcase
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
let (:song) { Song.new("Pray") }
|
173
|
+
subject { SongForm.new(song) }
|
174
|
+
|
175
|
+
# override reader for presentation.
|
176
|
+
it { subject.title.must_equal "pray" }
|
177
|
+
|
178
|
+
|
179
|
+
describe "#save" do
|
180
|
+
before { subject.validate("title" => "Hey Little World") }
|
181
|
+
|
182
|
+
# reader always used
|
183
|
+
it { subject.title.must_equal "hey little worldhey little world" }
|
184
|
+
|
185
|
+
# the reader is not used when saving/syncing.
|
186
|
+
it do
|
187
|
+
subject.save do |hash|
|
188
|
+
hash["title"].must_equal "Hey Little WorldHey Little World"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# no reader or writer used when saving/syncing.
|
193
|
+
it do
|
194
|
+
song.extend(Saveable)
|
195
|
+
subject.save
|
196
|
+
song.title.must_equal "Hey Little WorldHey Little World"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
class MethodInFormTest < MiniTest::Spec
|
203
|
+
class AlbumForm < Reform::Form
|
204
|
+
property :title
|
205
|
+
|
206
|
+
def title
|
207
|
+
"The Suffer And The Witness"
|
208
|
+
end
|
209
|
+
|
210
|
+
property :hit do
|
211
|
+
property :title
|
212
|
+
|
213
|
+
def title
|
214
|
+
"Drones"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# methods can be used instead of created accessors.
|
220
|
+
subject { AlbumForm.new(OpenStruct.new(:hit => OpenStruct.new)) }
|
221
|
+
it { subject.title.must_equal "The Suffer And The Witness" }
|
222
|
+
it { subject.hit.title.must_equal "Drones" }
|
223
|
+
end
|
data/test/save_test.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SaveTest < BaseTest
|
4
|
+
Song = Struct.new(:title, :album, :composer)
|
5
|
+
Album = Struct.new(:name, :songs, :artist)
|
6
|
+
Artist = Struct.new(:name)
|
7
|
+
|
8
|
+
class AlbumForm < Reform::Form
|
9
|
+
property :name
|
10
|
+
validation do
|
11
|
+
key(:name).required
|
12
|
+
end
|
13
|
+
|
14
|
+
collection :songs do
|
15
|
+
property :title
|
16
|
+
validation do
|
17
|
+
key(:title).required
|
18
|
+
end
|
19
|
+
|
20
|
+
property :composer do
|
21
|
+
property :name
|
22
|
+
validation do
|
23
|
+
key(:name).required
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
property :artist, save: false do
|
29
|
+
property :name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Saveable
|
34
|
+
def save
|
35
|
+
@saved = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def saved?
|
39
|
+
@saved
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
let (:song) { Song.new("Broken").extend(Saveable) }
|
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
|
+
|
52
|
+
|
53
|
+
it do
|
54
|
+
form.validate("songs" => [{"title" => "Fixed"}])
|
55
|
+
|
56
|
+
form.save
|
57
|
+
|
58
|
+
album.saved?.must_equal true
|
59
|
+
album.songs[0].title.must_equal "Fixed"
|
60
|
+
album.songs[0].saved?.must_equal true
|
61
|
+
album.artist.saved?.must_equal nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# class SaveWithDynamicOptionsTest < MiniTest::Spec
|
67
|
+
# Song = Struct.new(:id, :title, :length) do
|
68
|
+
# include Saveable
|
69
|
+
# end
|
70
|
+
|
71
|
+
# class SongForm < Reform::Form
|
72
|
+
# property :title#, save: false
|
73
|
+
# property :length, virtual: true
|
74
|
+
# end
|
75
|
+
|
76
|
+
# let (:song) { Song.new }
|
77
|
+
# let (:form) { SongForm.new(song) }
|
78
|
+
|
79
|
+
# # we have access to original input value and outside parameters.
|
80
|
+
# it "xxx" do
|
81
|
+
# form.validate("title" => "A Poor Man's Memory", "length" => 10)
|
82
|
+
# length_seconds = 120
|
83
|
+
# form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
|
84
|
+
|
85
|
+
# song.title.must_equal "A Poor Man's Memory"
|
86
|
+
# song.length.must_equal nil
|
87
|
+
# song.id.must_equal "10: 120"
|
88
|
+
# end
|
89
|
+
# end
|