reform 2.3.2 → 2.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +17 -0
  3. data/.gitignore +1 -1
  4. data/CHANGES.md +23 -0
  5. data/Gemfile +1 -1
  6. data/LICENSE.txt +1 -1
  7. data/README.md +5 -5
  8. data/Rakefile +1 -12
  9. data/lib/reform/contract/validate.rb +1 -1
  10. data/lib/reform/form/dry.rb +47 -9
  11. data/lib/reform/form/populator.rb +13 -3
  12. data/lib/reform/form/prepopulate.rb +1 -1
  13. data/lib/reform/form/validate.rb +3 -3
  14. data/lib/reform/validation/groups.rb +0 -1
  15. data/lib/reform/version.rb +1 -1
  16. data/reform.gemspec +2 -2
  17. data/test/call_test.rb +23 -0
  18. data/test/changed_test.rb +6 -6
  19. data/test/coercion_test.rb +17 -17
  20. data/test/{composition_new_api.rb → composition_test.rb} +27 -28
  21. data/test/{contract_new_api.rb → contract_test.rb} +8 -8
  22. data/test/default_test.rb +2 -2
  23. data/test/deserialize_test.rb +8 -8
  24. data/test/docs/validation_test.rb +134 -0
  25. data/test/{errors_new_api.rb → errors_test.rb} +41 -41
  26. data/test/feature_test.rb +2 -2
  27. data/test/fixtures/dry_error_messages.yml +64 -54
  28. data/test/{form_option_new_api.rb → form_option_test.rb} +1 -1
  29. data/test/{form_new_api.rb → form_test.rb} +3 -3
  30. data/test/from_test.rb +10 -10
  31. data/test/{inherit_new_api.rb → inherit_test.rb} +17 -17
  32. data/test/{module_new_api.rb → module_test.rb} +10 -10
  33. data/test/parse_option_test.rb +7 -7
  34. data/test/parse_pipeline_test.rb +1 -1
  35. data/test/{populate_new_api.rb → populate_test.rb} +136 -53
  36. data/test/populator_skip_test.rb +2 -2
  37. data/test/prepopulator_test.rb +16 -16
  38. data/test/read_only_test.rb +2 -2
  39. data/test/readable_test.rb +3 -3
  40. data/test/{reform_new_api.rb → reform_test.rb} +19 -19
  41. data/test/{save_new_api.rb → save_test.rb} +4 -4
  42. data/test/setup_test.rb +9 -9
  43. data/test/{skip_if_new_api.rb → skip_if_test.rb} +12 -12
  44. data/test/skip_setter_and_getter_test.rb +6 -6
  45. data/test/test_helper.rb +5 -6
  46. data/test/{validate_new_api.rb → validate_test.rb} +65 -78
  47. data/test/validation/{dry_validation_new_api.rb → dry_validation_test.rb} +128 -128
  48. data/test/validation/result_test.rb +14 -14
  49. data/test/virtual_test.rb +7 -7
  50. data/test/writeable_test.rb +8 -8
  51. metadata +42 -75
  52. data/.travis.yml +0 -16
  53. data/Appraisals +0 -8
  54. data/gemfiles/0.13.0.gemfile +0 -8
  55. data/gemfiles/1.5.0.gemfile +0 -9
  56. data/lib/reform/form/dry/new_api.rb +0 -47
  57. data/lib/reform/form/dry/old_api.rb +0 -61
  58. data/test/call_new_api.rb +0 -23
  59. data/test/call_old_api.rb +0 -23
  60. data/test/composition_old_api.rb +0 -184
  61. data/test/contract_old_api.rb +0 -77
  62. data/test/errors_old_api.rb +0 -230
  63. data/test/fixtures/dry_new_api_error_messages.yml +0 -104
  64. data/test/form_old_api.rb +0 -57
  65. data/test/form_option_old_api.rb +0 -24
  66. data/test/inherit_old_api.rb +0 -105
  67. data/test/module_old_api.rb +0 -146
  68. data/test/populate_old_api.rb +0 -304
  69. data/test/reform_old_api.rb +0 -202
  70. data/test/save_old_api.rb +0 -101
  71. data/test/skip_if_old_api.rb +0 -92
  72. data/test/validate_old_api.rb +0 -410
  73. data/test/validation/dry_validation_old_api.rb +0 -772
@@ -1,146 +0,0 @@
1
- require "test_helper"
2
- require "reform/form/coercion"
3
-
4
- class ModuleInclusionTest < MiniTest::Spec
5
- module BandPropertyForm
6
- include Reform::Form::Module
7
-
8
- property :artist
9
- property :band do
10
- property :title
11
-
12
- validation do
13
- required(:title).filled
14
- end
15
-
16
- def id # gets mixed into Form, too.
17
- 2
18
- end
19
- end
20
-
21
- def id # gets mixed into Form, too.
22
- 1
23
- end
24
-
25
- validation do
26
- required(:band).filled
27
- end
28
-
29
- include Dry::Types.module # allows using Types::* in module.
30
- property :cool, type: DRY_TYPES_CONSTANT::Bool # test coercion.
31
-
32
- module InstanceMethods
33
- def artist=(new_value)
34
- errors.add(:artist, "this needs to be filled") if new_value.nil?
35
- super(new_value)
36
- end
37
- end
38
- end
39
-
40
- # TODO: test if works, move stuff into inherit_schema!
41
- module AirplaysPropertyForm
42
- include Reform::Form::Module
43
-
44
- collection :airplays do
45
- property :station
46
- validation do
47
- required(:station).filled
48
- end
49
- end
50
- validation do
51
- required(:airplays).filled
52
- end
53
- end
54
-
55
- # test:
56
- # by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
57
- class HitForm < TestForm
58
- include BandPropertyForm
59
- end
60
-
61
- class SongForm < TestForm
62
- include Coercion
63
- property :title
64
-
65
- include BandPropertyForm
66
- end
67
-
68
- let(:song) { OpenStruct.new(band: OpenStruct.new(title: "Time Again"), artist: "Ketama") }
69
-
70
- # nested form from module is present and creates accessor.
71
- it { SongForm.new(song).band.title.must_equal "Time Again" }
72
- it { SongForm.new(song).artist.must_equal "Ketama" }
73
-
74
- # methods from module get included.
75
- it { SongForm.new(song).id.must_equal 1 }
76
- it { SongForm.new(song).band.id.must_equal 2 }
77
-
78
- # validators get inherited.
79
- it do
80
- form = SongForm.new(OpenStruct.new)
81
- form.validate(artist: nil)
82
- form.errors.messages.must_equal(artist: ["this needs to be filled"], band: ["must be filled"])
83
- end
84
-
85
- # coercion works
86
- it do
87
- form = SongForm.new(OpenStruct.new)
88
- form.validate({cool: "1"})
89
- form.cool.must_equal true
90
- end
91
-
92
- # include a module into a module into a class :)
93
- module AlbumFormModule
94
- include Reform::Form::Module
95
- include BandPropertyForm
96
-
97
- property :name
98
- validation do
99
- required(:name).filled
100
- end
101
- end
102
-
103
- class AlbumForm < TestForm
104
- include AlbumFormModule
105
-
106
- # pp heritage
107
- property :band, inherit: true do
108
- property :label
109
- validation do
110
- required(:label).filled
111
- end
112
- end
113
- end
114
-
115
- it do
116
- form = AlbumForm.new(OpenStruct.new(band: OpenStruct.new))
117
- form.validate("band" => {})
118
- form.errors.messages.must_equal("band.title": ["must be filled"], "band.label": ["must be filled"], name: ["must be filled"])
119
- end
120
-
121
- describe "module with custom accessors" do
122
- module SongModule
123
- include Reform::Form::Module
124
-
125
- property :id # no custom accessor for id.
126
- property :title # has custom accessor.
127
-
128
- module InstanceMethods
129
- def title
130
- super.upcase
131
- end
132
- end
133
- end
134
-
135
- class IncludingSongForm < TestForm
136
- include SongModule
137
- end
138
-
139
- let(:song) { OpenStruct.new(id: 1, title: "Instant Mash") }
140
-
141
- it do
142
- IncludingSongForm.new(song).id.must_equal 1
143
- IncludingSongForm.new(song).title.must_equal "INSTANT MASH"
144
- end
145
- end
146
- end
@@ -1,304 +0,0 @@
1
- require "test_helper"
2
-
3
- class PopulatorTest < MiniTest::Spec
4
- Song = Struct.new(:title, :album, :composer)
5
- Album = Struct.new(:name, :songs, :artist)
6
- Artist = Struct.new(:name)
7
-
8
- class AlbumForm < TestForm
9
- property :name, populator: ->(options) { self.name = options[:fragment].reverse }
10
- validation do
11
- required(:name).filled
12
- end
13
-
14
- collection :songs,
15
- populator: ->(fragment:, model:, index:, **) {
16
- (item = model[index]) ? item : model.insert(index, Song.new)
17
- } do
18
- property :title
19
- validation do
20
- required(:title).filled
21
- end
22
-
23
- property :composer, populator: ->(options) { options[:model] || self.composer = Artist.new } do
24
- property :name
25
- validation do
26
- required(:name).filled
27
- end
28
- end
29
- end
30
-
31
- # property :artist, populator: lambda { |fragment, options| (item = options.binding.get) ? item : Artist.new } do
32
- # NOTE: we have to document that model here is the twin!
33
- property :artist, populator: ->(options) { options[:model] || self.artist = Artist.new } do
34
- property :name
35
- end
36
- end
37
-
38
- let(:song) { Song.new("Broken") }
39
- let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
40
- let(:composer) { Artist.new("Greg Graffin") }
41
- let(:artist) { Artist.new("Bad Religion") }
42
- let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
43
-
44
- let(:form) { AlbumForm.new(album) }
45
-
46
- it "runs populator on scalar" do
47
- form.validate(
48
- "name" => "override me!"
49
- )
50
-
51
- form.name.must_equal "!em edirrevo"
52
- end
53
-
54
- # changing existing property :artist.
55
- # TODO: check with artist==nil
56
- it do
57
- old_id = artist.object_id
58
-
59
- form.validate(
60
- "artist" => {"name" => "Marcus Miller"}
61
- )
62
-
63
- form.artist.model.object_id.must_equal old_id
64
- end
65
-
66
- # use populator for default value on scalars?
67
-
68
- # adding to collection via :populator.
69
- # valid.
70
- it "yyy" do
71
- form.validate(
72
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
73
- {"title" => "Rime Of The Ancient Mariner"}, # new song.
74
- {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
75
- ).must_equal true
76
-
77
- form.errors.messages.inspect.must_equal "{}"
78
-
79
- # form has updated.
80
- form.name.must_equal "The Dissent Of Man"
81
- form.songs[0].title.must_equal "Fallout"
82
- form.songs[1].title.must_equal "Roxanne"
83
- form.songs[1].composer.name.must_equal "Greg Graffin"
84
-
85
- form.songs[1].composer.model.must_be_instance_of Artist
86
-
87
- form.songs[1].title.must_equal "Roxanne"
88
- form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
89
- form.songs[3].title.must_equal "Re-Education"
90
- form.songs[3].composer.name.must_equal "Rise Against"
91
- form.songs.size.must_equal 4
92
- form.artist.name.must_equal "Bad Religion"
93
-
94
- # model has not changed, yet.
95
- album.name.must_equal "The Dissent Of Man"
96
- album.songs[0].title.must_equal "Broken"
97
- album.songs[1].title.must_equal "Resist Stance"
98
- album.songs[1].composer.name.must_equal "Greg Graffin"
99
- album.songs.size.must_equal 2
100
- album.artist.name.must_equal "Bad Religion"
101
- end
102
- end
103
-
104
- class PopulateWithMethodTest < Minitest::Spec
105
- Album = Struct.new(:title)
106
-
107
- class AlbumForm < TestForm
108
- property :title, populator: :title!
109
-
110
- def title!(options)
111
- self.title = options[:fragment].reverse
112
- end
113
- end
114
-
115
- let(:form) { AlbumForm.new(Album.new) }
116
-
117
- it "runs populator method" do
118
- form.validate("title" => "override me!")
119
-
120
- form.title.must_equal "!em edirrevo"
121
- end
122
- end
123
-
124
- class PopulateWithCallableTest < Minitest::Spec
125
- Album = Struct.new(:title)
126
-
127
- class TitlePopulator
128
- include Uber::Callable
129
-
130
- def call(form, options)
131
- form.title = options[:fragment].reverse
132
- end
133
- end
134
-
135
- class AlbumForm < TestForm
136
- property :title, populator: TitlePopulator.new
137
- end
138
-
139
- let(:form) { AlbumForm.new(Album.new) }
140
-
141
- it "runs populator method" do
142
- form.validate("title" => "override me!")
143
-
144
- form.title.must_equal "!em edirrevo"
145
- end
146
- end
147
-
148
- class PopulateWithProcTest < Minitest::Spec
149
- Album = Struct.new(:title)
150
-
151
- TitlePopulator = ->(options) do
152
- options[:represented].title = options[:fragment].reverse
153
- end
154
-
155
- class AlbumForm < TestForm
156
- property :title, populator: TitlePopulator
157
- end
158
-
159
- let(:form) { AlbumForm.new(Album.new) }
160
-
161
- it "runs populator method" do
162
- form.validate("title" => "override me!")
163
-
164
- form.title.must_equal "!em edirrevo"
165
- end
166
- end
167
-
168
- class PopulateIfEmptyTest < MiniTest::Spec
169
- Song = Struct.new(:title, :album, :composer)
170
- Album = Struct.new(:name, :songs, :artist)
171
- Artist = Struct.new(:name)
172
-
173
- let(:song) { Song.new("Broken") }
174
- let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
175
- let(:composer) { Artist.new("Greg Graffin") }
176
- let(:artist) { Artist.new("Bad Religion") }
177
- let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
178
-
179
- class AlbumForm < TestForm
180
- property :name
181
-
182
- collection :songs,
183
- populate_if_empty: Song do # class name works.
184
-
185
- property :title
186
- validation do
187
- required(:title).filled
188
- end
189
-
190
- property :composer, populate_if_empty: :populate_composer! do # lambda works, too. in form context.
191
- property :name
192
- validation do
193
- required(:name).filled
194
- end
195
- end
196
-
197
- private
198
- def populate_composer!(options)
199
- Artist.new
200
- end
201
- end
202
-
203
- property :artist, populate_if_empty: ->(args) { create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
204
- property :name
205
- end
206
-
207
- private
208
- class Sting < Artist
209
- attr_accessor :args
210
- end
211
- def create_artist(input, user_options)
212
- Sting.new.tap { |artist| artist.args = ([input, user_options].to_s) }
213
- end
214
- end
215
-
216
- let(:form) { AlbumForm.new(album) }
217
-
218
- it do
219
- form.songs.size.must_equal 2
220
-
221
- form.validate(
222
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
223
- {"title" => "Rime Of The Ancient Mariner"}, # new song.
224
- {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
225
- ).must_equal true
226
-
227
- form.errors.messages.inspect.must_equal "{}"
228
-
229
- # form has updated.
230
- form.name.must_equal "The Dissent Of Man"
231
- form.songs[0].title.must_equal "Fallout"
232
- form.songs[1].title.must_equal "Roxanne"
233
- form.songs[1].composer.name.must_equal "Greg Graffin"
234
- form.songs[1].title.must_equal "Roxanne"
235
- form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
236
- form.songs[3].title.must_equal "Re-Education"
237
- form.songs[3].composer.name.must_equal "Rise Against"
238
- form.songs.size.must_equal 4
239
- form.artist.name.must_equal "Bad Religion"
240
-
241
- # model has not changed, yet.
242
- album.name.must_equal "The Dissent Of Man"
243
- album.songs[0].title.must_equal "Broken"
244
- album.songs[1].title.must_equal "Resist Stance"
245
- album.songs[1].composer.name.must_equal "Greg Graffin"
246
- album.songs.size.must_equal 2
247
- album.artist.name.must_equal "Bad Religion"
248
- end
249
-
250
- # trigger artist populator. lambda calling form instance method.
251
- it "xxxx" do
252
- form = AlbumForm.new(album = Album.new)
253
- form.validate("artist" => {"name" => "From Autumn To Ashes"})
254
-
255
- form.artist.name.must_equal "From Autumn To Ashes"
256
- # test lambda was executed in form context.
257
- form.artist.model.must_be_instance_of AlbumForm::Sting
258
- # test lambda block arguments.
259
- form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
260
-
261
- assert_nil album.artist
262
- end
263
- end
264
-
265
- # delete songs while deserializing.
266
- class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
267
- Song = Struct.new(:title, :album, :composer)
268
- Album = Struct.new(:name, :songs, :artist)
269
-
270
- let(:song) { Song.new("Broken") }
271
- let(:song2) { Song.new("Resist Stance") }
272
- let(:album) { Album.new("The Dissent Of Man", [song, song2]) }
273
-
274
- class AlbumForm < TestForm
275
- property :name
276
-
277
- collection :songs,
278
- populate_if_empty: Song, skip_if: :delete_song! do
279
-
280
- property :title
281
- validation do
282
- required(:title).filled
283
- end
284
- end
285
-
286
- def delete_song!(options)
287
- songs.delete(songs[0]) and return true if options[:fragment]["title"] == "Broken, delete me!"
288
- false
289
- end
290
- end
291
-
292
- let(:form) { AlbumForm.new(album) }
293
-
294
- it do
295
- form.validate(
296
- "songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
297
- ).must_equal true
298
-
299
- form.errors.messages.inspect.must_equal "{}"
300
-
301
- form.songs.size.must_equal 1
302
- form.songs[0].title.must_equal "Roxanne"
303
- end
304
- end
@@ -1,202 +0,0 @@
1
- require "test_helper"
2
-
3
- class ReformTest < Minitest::Spec
4
- let(:comp) { OpenStruct.new(name: "Duran Duran", title: "Rio") }
5
-
6
- let(:form) { SongForm.new(comp) }
7
-
8
- class SongForm < TestForm
9
- property :name
10
- property :title
11
-
12
- validation do
13
- required(:name).filled
14
- end
15
- end
16
-
17
- describe "(new) form with empty models" do
18
- let(:comp) { OpenStruct.new }
19
-
20
- it "returns empty fields" do
21
- assert_nil form.title
22
- form.name.must_be_nil
23
- end
24
-
25
- describe "and submitted values" do
26
- it "returns filled-out fields" do
27
- form.validate("name" => "Duran Duran")
28
-
29
- assert_nil form.title
30
- form.name.must_equal "Duran Duran"
31
- end
32
- end
33
- end
34
-
35
- describe "(edit) form with existing models" do
36
- it "returns filled-out fields" do
37
- form.name.must_equal "Duran Duran"
38
- form.title.must_equal "Rio"
39
- end
40
- end
41
-
42
- describe "#validate" do
43
- let(:comp) { OpenStruct.new }
44
-
45
- it "ignores unmapped fields in input" do
46
- form.validate("name" => "Duran Duran", :genre => "80s")
47
- assert_raises NoMethodError do
48
- form.genre
49
- end
50
- end
51
-
52
- it "returns true when valid" do
53
- form.validate("name" => "Duran Duran").must_equal true
54
- end
55
-
56
- it "exposes input via property accessors" do
57
- form.validate("name" => "Duran Duran")
58
-
59
- form.name.must_equal "Duran Duran"
60
- end
61
-
62
- it "doesn't change model properties" do
63
- form.validate("name" => "Duran Duran")
64
-
65
- assert_nil comp.name # don't touch model, yet.
66
- end
67
-
68
- # TODO: test errors. test valid.
69
- describe "invalid input" do
70
- class ValidatingForm < TestForm
71
- property :name
72
- property :title
73
-
74
- validation do
75
- required(:name).filled
76
- required(:title).filled
77
- end
78
- end
79
- let(:form) { ValidatingForm.new(comp) }
80
-
81
- it "returns false when invalid" do
82
- form.validate({}).must_equal false
83
- end
84
-
85
- it "populates errors" do
86
- form.validate({})
87
- form.errors.messages.must_equal({name: ["must be filled"], title: ["must be filled"]})
88
- end
89
- end
90
- end
91
-
92
- describe "#save" do
93
- let(:comp) { OpenStruct.new }
94
- let(:form) { SongForm.new(comp) }
95
-
96
- before { form.validate("name" => "Diesel Boy") }
97
-
98
- it "xxpushes data to models" do
99
- form.save
100
-
101
- comp.name.must_equal "Diesel Boy"
102
- assert_nil comp.title
103
- end
104
-
105
- describe "#save with block" do
106
- it do
107
- hash = {}
108
-
109
- form.save do |map|
110
- hash = map
111
- end
112
-
113
- hash.must_equal({"name" => "Diesel Boy", "title" => nil})
114
- end
115
- end
116
- end
117
-
118
- describe "#model" do
119
- it { form.model.must_equal comp }
120
- end
121
-
122
- describe "inheritance" do
123
- class HitForm < SongForm
124
- property :position
125
- validation do
126
- required(:position).filled
127
- end
128
- end
129
-
130
- let(:form) { HitForm.new(OpenStruct.new()) }
131
- it do
132
- form.validate({"title" => "The Body"})
133
- form.title.must_equal "The Body"
134
- assert_nil form.position
135
- form.errors.messages.must_equal({name: ["must be filled"], position: ["must be filled"]})
136
- end
137
- end
138
- end
139
-
140
- class OverridingAccessorsTest < BaseTest
141
- class SongForm < TestForm
142
- property :title
143
-
144
- def title=(v) # used in #validate.
145
- super v * 2
146
- end
147
-
148
- def title # used in #sync.
149
- super.downcase
150
- end
151
- end
152
-
153
- let(:song) { Song.new("Pray") }
154
- subject { SongForm.new(song) }
155
-
156
- # override reader for presentation.
157
- it { subject.title.must_equal "pray" }
158
-
159
- describe "#save" do
160
- before { subject.validate("title" => "Hey Little World") }
161
-
162
- # reader always used
163
- it { subject.title.must_equal "hey little worldhey little world" }
164
-
165
- # the reader is not used when saving/syncing.
166
- it do
167
- subject.save do |hash|
168
- hash["title"].must_equal "Hey Little WorldHey Little World"
169
- end
170
- end
171
-
172
- # no reader or writer used when saving/syncing.
173
- it do
174
- song.extend(Saveable)
175
- subject.save
176
- song.title.must_equal "Hey Little WorldHey Little World"
177
- end
178
- end
179
- end
180
-
181
- class MethodInFormTest < MiniTest::Spec
182
- class AlbumForm < TestForm
183
- property :title
184
-
185
- def title
186
- "The Suffer And The Witness"
187
- end
188
-
189
- property :hit do
190
- property :title
191
-
192
- def title
193
- "Drones"
194
- end
195
- end
196
- end
197
-
198
- # methods can be used instead of created accessors.
199
- subject { AlbumForm.new(OpenStruct.new(hit: OpenStruct.new)) }
200
- it { subject.title.must_equal "The Suffer And The Witness" }
201
- it { subject.hit.title.must_equal "Drones" }
202
- end