reform 2.3.2 → 2.6.1

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.
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