reform 2.3.3 → 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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +0 -3
  4. data/CHANGES.md +9 -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/validation/groups.rb +0 -1
  12. data/lib/reform/version.rb +1 -1
  13. data/test/call_test.rb +23 -0
  14. data/test/changed_test.rb +6 -6
  15. data/test/coercion_test.rb +17 -17
  16. data/test/{composition_new_api.rb → composition_test.rb} +27 -28
  17. data/test/{contract_new_api.rb → contract_test.rb} +8 -8
  18. data/test/default_test.rb +2 -2
  19. data/test/deserialize_test.rb +8 -8
  20. data/test/docs/validation_test.rb +134 -0
  21. data/test/{errors_new_api.rb → errors_test.rb} +41 -41
  22. data/test/feature_test.rb +2 -2
  23. data/test/fixtures/dry_error_messages.yml +64 -54
  24. data/test/{form_option_new_api.rb → form_option_test.rb} +1 -1
  25. data/test/{form_new_api.rb → form_test.rb} +3 -3
  26. data/test/from_test.rb +10 -10
  27. data/test/{inherit_new_api.rb → inherit_test.rb} +17 -17
  28. data/test/{module_new_api.rb → module_test.rb} +10 -10
  29. data/test/parse_option_test.rb +7 -7
  30. data/test/parse_pipeline_test.rb +1 -1
  31. data/test/{populate_new_api.rb → populate_test.rb} +112 -53
  32. data/test/populator_skip_test.rb +2 -2
  33. data/test/prepopulator_test.rb +15 -15
  34. data/test/read_only_test.rb +2 -2
  35. data/test/readable_test.rb +3 -3
  36. data/test/{reform_new_api.rb → reform_test.rb} +19 -19
  37. data/test/{save_new_api.rb → save_test.rb} +4 -4
  38. data/test/setup_test.rb +9 -9
  39. data/test/{skip_if_new_api.rb → skip_if_test.rb} +12 -12
  40. data/test/skip_setter_and_getter_test.rb +6 -6
  41. data/test/test_helper.rb +5 -6
  42. data/test/{validate_new_api.rb → validate_test.rb} +65 -78
  43. data/test/validation/{dry_validation_new_api.rb → dry_validation_test.rb} +124 -123
  44. data/test/validation/result_test.rb +14 -14
  45. data/test/virtual_test.rb +7 -7
  46. data/test/writeable_test.rb +8 -8
  47. metadata +35 -68
  48. data/Appraisals +0 -8
  49. data/gemfiles/0.13.0.gemfile +0 -8
  50. data/gemfiles/1.5.0.gemfile +0 -9
  51. data/lib/reform/form/dry/new_api.rb +0 -45
  52. data/lib/reform/form/dry/old_api.rb +0 -61
  53. data/test/call_new_api.rb +0 -23
  54. data/test/call_old_api.rb +0 -23
  55. data/test/composition_old_api.rb +0 -184
  56. data/test/contract_old_api.rb +0 -77
  57. data/test/errors_old_api.rb +0 -230
  58. data/test/fixtures/dry_new_api_error_messages.yml +0 -104
  59. data/test/form_old_api.rb +0 -57
  60. data/test/form_option_old_api.rb +0 -24
  61. data/test/inherit_old_api.rb +0 -105
  62. data/test/module_old_api.rb +0 -146
  63. data/test/populate_old_api.rb +0 -304
  64. data/test/reform_old_api.rb +0 -202
  65. data/test/save_old_api.rb +0 -101
  66. data/test/skip_if_old_api.rb +0 -92
  67. data/test/validate_old_api.rb +0 -410
  68. data/test/validation/dry_validation_old_api.rb +0 -772
data/test/call_new_api.rb DELETED
@@ -1,23 +0,0 @@
1
- require "test_helper"
2
-
3
- class CallTest < Minitest::Spec
4
- Song = Struct.new(:title)
5
-
6
- class SongForm < TestForm
7
- property :title
8
-
9
- validation do
10
- params { required(:title).filled }
11
- end
12
- end
13
-
14
- let(:form) { SongForm.new(Song.new) }
15
-
16
- it { _(form.(title: "True North").success?).must_equal true }
17
- it { _(form.(title: "True North").failure?).must_equal false }
18
- it { _(form.(title: "").success?).must_equal false }
19
- it { _(form.(title: "").failure?).must_equal true }
20
-
21
- it { _(form.(title: "True North").errors.messages).must_equal({}) }
22
- it { _(form.(title: "").errors.messages).must_equal(title: ["must be filled"]) }
23
- end
data/test/call_old_api.rb DELETED
@@ -1,23 +0,0 @@
1
- require "test_helper"
2
-
3
- class CallTest < Minitest::Spec
4
- Song = Struct.new(:title)
5
-
6
- class SongForm < TestForm
7
- property :title
8
-
9
- validation do
10
- required(:title).filled
11
- end
12
- end
13
-
14
- let(:form) { SongForm.new(Song.new) }
15
-
16
- it { _(form.(title: "True North").success?).must_equal true }
17
- it { _(form.(title: "True North").failure?).must_equal false }
18
- it { _(form.(title: "").success?).must_equal false }
19
- it { _(form.(title: "").failure?).must_equal true }
20
-
21
- it { _(form.(title: "True North").errors.messages).must_equal({}) }
22
- it { _(form.(title: "").errors.messages).must_equal(title: ["must be filled"]) }
23
- end
@@ -1,184 +0,0 @@
1
- require "test_helper"
2
-
3
- class FormCompositionInheritanceTest < MiniTest::Spec
4
- module SizePrice
5
- include Reform::Form::Module
6
-
7
- property :price
8
- property :size
9
-
10
- module InstanceMethods
11
- def price(for_size: size)
12
- case for_size.to_sym
13
- when :s then super() * 1
14
- when :m then super() * 2
15
- when :l then super() * 3
16
- end
17
- end
18
- end
19
- end
20
-
21
- class OutfitForm < TestForm
22
- include Reform::Form::Composition
23
- include SizePrice
24
-
25
- property :price, inherit: true, on: :tshirt
26
- property :size, inherit: true, on: :measurement
27
- end
28
-
29
- let(:measurement) { Measurement.new(:l) }
30
- let(:tshirt) { Tshirt.new(2, :m) }
31
- let(:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
32
-
33
- Tshirt = Struct.new(:price, :size)
34
- Measurement = Struct.new(:size)
35
-
36
- it { _(form.price).must_equal 6 }
37
- it { _(form.price(for_size: :s)).must_equal 2 }
38
- end
39
-
40
- class FormCompositionTest < MiniTest::Spec
41
- Song = Struct.new(:id, :title, :band)
42
- Requester = Struct.new(:id, :name, :requester)
43
- Band = Struct.new(:title)
44
-
45
- class RequestForm < TestForm
46
- include Composition
47
-
48
- property :name, on: :requester
49
- property :requester_id, on: :requester, from: :id
50
- properties :title, :id, on: :song
51
- # property :channel # FIXME: what about the "main model"?
52
- property :channel, virtual: true, on: :song
53
- property :requester, on: :requester
54
- property :captcha, on: :song, virtual: true
55
-
56
- validation do
57
- required(:name).filled
58
- required(:title).filled
59
- end
60
-
61
- property :band, on: :song do
62
- property :title
63
- end
64
- end
65
-
66
- let(:form) { RequestForm.new(song: song, requester: requester) }
67
- let(:song) { Song.new(1, "Rio", band) }
68
- let(:requester) { Requester.new(2, "Duran Duran", "MCP") }
69
- let(:band) { Band.new("Duran^2") }
70
-
71
- # delegation form -> composition works
72
- it { _(form.id).must_equal 1 }
73
- it { _(form.title).must_equal "Rio" }
74
- it { _(form.name).must_equal "Duran Duran" }
75
- it { _(form.requester_id).must_equal 2 }
76
- it { assert_nil form.channel }
77
- it { _(form.requester).must_equal "MCP" } # same name as composed model.
78
- it { assert_nil form.captcha }
79
-
80
- # #model just returns <Composition>.
81
- it { _(form.mapper).must_be_kind_of Disposable::Composition }
82
-
83
- # #model[] -> composed models
84
- it { _(form.model[:requester]).must_equal requester }
85
- it { _(form.model[:song]).must_equal song }
86
-
87
- it "creates Composition for you" do
88
- _(form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb")).must_equal true
89
- _(form.validate("title" => "", "name" => "Frenzal Rhomb")).must_equal false
90
- end
91
-
92
- describe "#save" do
93
- # #save with {}
94
- it do
95
- hash = {}
96
-
97
- form.save do |map|
98
- hash[:name] = form.name
99
- hash[:title] = form.title
100
- end
101
-
102
- _(hash).must_equal({name: "Duran Duran", title: "Rio"})
103
- end
104
-
105
- it "provides nested symbolized hash as second block argument" do
106
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "channel" => "JJJ", "captcha" => "wonderful")
107
-
108
- hash = nil
109
-
110
- form.save do |map|
111
- hash = map
112
- end
113
-
114
- _(hash).must_equal({
115
- song: {"title" => "Greyhound", "id" => 1, "channel" => "JJJ", "captcha" => "wonderful", "band" => {"title" => "Duran^2"}},
116
- requester: {"name" => "Frenzal Rhomb", "id" => 2, "requester" => "MCP"}
117
- }
118
- )
119
- end
120
-
121
- it "xxx pushes data to models and calls #save when no block passed" do
122
- song.extend(Saveable)
123
- requester.extend(Saveable)
124
- band.extend(Saveable)
125
-
126
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "captcha" => "1337")
127
- _(form.captcha).must_equal "1337" # TODO: move to separate test.
128
-
129
- form.save
130
-
131
- _(requester.name).must_equal "Frenzal Rhomb"
132
- _(requester.saved?).must_equal true
133
- _(song.title).must_equal "Greyhound"
134
- _(song.saved?).must_equal true
135
- _(song.band.title).must_equal "Duran^2"
136
- _(song.band.saved?).must_equal true
137
- end
138
-
139
- it "returns true when models all save successfully" do
140
- song.extend(Saveable)
141
- requester.extend(Saveable)
142
- band.extend(Saveable)
143
-
144
- _(form.save).must_equal true
145
- end
146
-
147
- it "returns false when one or more models don't save successfully" do
148
- module Unsaveable
149
- def save
150
- false
151
- end
152
- end
153
-
154
- song.extend(Unsaveable)
155
- requester.extend(Saveable)
156
- band.extend(Saveable)
157
-
158
- _(form.save).must_equal false
159
- end
160
- end
161
- end
162
-
163
- class FormCompositionCollectionTest < MiniTest::Spec
164
- Book = Struct.new(:id, :name)
165
- Library = Struct.new(:id) do
166
- def books
167
- [Book.new(1, "My book")]
168
- end
169
- end
170
-
171
- class LibraryForm < TestForm
172
- include Reform::Form::Composition
173
-
174
- collection :books, on: :library do
175
- property :id
176
- property :name
177
- end
178
- end
179
-
180
- let(:form) { LibraryForm.new(library: library) }
181
- let(:library) { Library.new(2) }
182
-
183
- it { form.save { |hash| _(hash).must_equal({library: {"books" => [{"id" => 1, "name" => "My book"}]}}) } }
184
- end
@@ -1,77 +0,0 @@
1
- require "test_helper"
2
-
3
- class ContractTest < MiniTest::Spec
4
- Song = Struct.new(:title, :album, :composer)
5
- Album = Struct.new(:name, :duration, :songs, :artist)
6
- Artist = Struct.new(:name)
7
-
8
- class ArtistForm < TestForm
9
- property :name
10
- end
11
-
12
- class AlbumForm < TestContract
13
- property :name
14
-
15
- properties :duration
16
- properties :year, :style, readable: false
17
-
18
- validation do
19
- required(:name).filled
20
- end
21
-
22
- collection :songs do
23
- property :title
24
- validation do
25
- required(:title).filled
26
- end
27
-
28
- property :composer do
29
- property :name
30
- validation do
31
- required(:name).filled
32
- end
33
- end
34
- end
35
-
36
- property :artist, form: ArtistForm
37
- end
38
-
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", 123, [song, song_with_composer], artist) }
44
-
45
- let(:form) { AlbumForm.new(album) }
46
-
47
- # accept `property form: SongForm`.
48
- it do
49
- _(form.artist).must_be_instance_of ArtistForm
50
- end
51
-
52
- describe ".properties" do
53
- it "defines a property when called with one argument" do
54
- _(form).must_respond_to :duration
55
- end
56
-
57
- it "defines several properties when called with multiple arguments" do
58
- _(form).must_respond_to :year
59
- _(form).must_respond_to :style
60
- end
61
-
62
- it "passes options to each property when options are provided" do
63
- readable = AlbumForm.new(album).options_for(:style)[:readable]
64
- _(readable).must_equal false
65
- end
66
-
67
- it "returns the list of defined properties" do
68
- returned_value = AlbumForm.properties(:hello, :world, virtual: true)
69
- _(returned_value).must_equal %i[hello world]
70
- end
71
- end
72
-
73
- describe "#options_for" do
74
- it { _(AlbumForm.options_for(:name).extend(Declarative::Inspect).inspect).must_equal "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
75
- it { _(AlbumForm.new(album).options_for(:name).extend(Declarative::Inspect).inspect).must_equal "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
76
- end
77
- end
@@ -1,230 +0,0 @@
1
- require "test_helper"
2
-
3
- class ErrorsTest < MiniTest::Spec
4
- class AlbumForm < TestForm
5
- property :title
6
- validation do
7
- required(:title).filled
8
- end
9
-
10
- property :artists, default: []
11
- property :producer do
12
- property :name
13
- end
14
-
15
- property :hit do
16
- property :title
17
- validation do
18
- required(:title).filled
19
- end
20
- end
21
-
22
- collection :songs do
23
- property :title
24
- validation do
25
- required(:title).filled
26
- end
27
- end
28
-
29
- property :band do # yepp, people do crazy stuff like that.
30
- property :name
31
- property :label do
32
- property :name
33
- validation do
34
- required(:name).filled
35
- end
36
- end
37
- # TODO: make band a required object.
38
-
39
- validation do
40
- configure do
41
- config.messages_file = "test/fixtures/dry_error_messages.yml"
42
-
43
- def good_musical_taste?(value)
44
- value != "Nickelback"
45
- end
46
- end
47
-
48
- required(:name).filled(:good_musical_taste?)
49
- end
50
- end
51
-
52
- validation do
53
- required(:title).filled
54
- required(:artists).each(:str?)
55
- required(:producer).schema do
56
- required(:name).filled
57
- end
58
- end
59
- end
60
-
61
- let(:album_title) { "Blackhawks Over Los Angeles" }
62
- let(:album) do
63
- OpenStruct.new(
64
- title: album_title,
65
- hit: song,
66
- songs: songs, # TODO: document this requirement,
67
- band: Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
68
- producer: Struct.new(:name).new("Sun Records")
69
- )
70
- end
71
- let(:song) { OpenStruct.new(title: "Downtown") }
72
- let(:songs) { [song = OpenStruct.new(title: "Calling"), song] }
73
- let(:form) { AlbumForm.new(album) }
74
-
75
- describe "#validate with invalid array property" do
76
- it do
77
- _(form.validate(
78
- title: "Swimming Pool - EP",
79
- band: {
80
- name: "Marie Madeleine",
81
- label: {name: "Ekler'o'shocK"}
82
- },
83
- artists: [42, "Good Charlotte", 43]
84
- )).must_equal false
85
- _(form.errors.messages).must_equal(artists: {0 => ["must be a string"], 2 => ["must be a string"]})
86
- _(form.errors.size).must_equal(1)
87
- end
88
- end
89
-
90
- describe "#errors without #validate" do
91
- it do
92
- _(form.errors.size).must_equal 0
93
- end
94
- end
95
-
96
- describe "blank everywhere" do
97
- before do
98
- form.validate(
99
- "hit" => {"title" => ""},
100
- "title" => "",
101
- "songs" => [{"title" => ""}, {"title" => ""}],
102
- "producer" => {"name" => ""}
103
- )
104
- end
105
-
106
- it do
107
- _(form.errors.messages).must_equal(
108
- title: ["must be filled"],
109
- "hit.title": ["must be filled"],
110
- "songs.title": ["must be filled"],
111
- "band.label.name": ["must be filled"],
112
- "producer.name": ["must be filled"]
113
- )
114
- end
115
-
116
- # it do
117
- # form.errors.must_equal({:title => ["must be filled"]})
118
- # TODO: this should only contain local errors?
119
- # end
120
-
121
- # nested forms keep their own Errors:
122
- it { _(form.producer.errors.messages).must_equal(name: ["must be filled"]) }
123
- it { _(form.hit.errors.messages).must_equal(title: ["must be filled"]) }
124
- it { _(form.songs[0].errors.messages).must_equal(title: ["must be filled"]) }
125
-
126
- it do
127
- _(form.errors.messages).must_equal(
128
- title: ["must be filled"],
129
- "hit.title": ["must be filled"],
130
- "songs.title": ["must be filled"],
131
- "band.label.name": ["must be filled"],
132
- "producer.name": ["must be filled"]
133
- )
134
- _(form.errors.size).must_equal(5)
135
- end
136
- end
137
-
138
- describe "#validate with main form invalid" do
139
- it do
140
- _(form.validate("title" => "", "band" => {"label" => {name: "Fat Wreck"}}, "producer" => nil)).must_equal false
141
- _(form.errors.messages).must_equal(title: ["must be filled"], producer: ["must be a hash"])
142
- _(form.errors.size).must_equal(2)
143
- end
144
- end
145
-
146
- describe "#validate with middle nested form invalid" do
147
- before { @result = form.validate("hit" => {"title" => ""}, "band" => {"label" => {name: "Fat Wreck"}}) }
148
-
149
- it { _(@result).must_equal false }
150
- it { _(form.errors.messages).must_equal("hit.title": ["must be filled"]) }
151
- it { _(form.errors.size).must_equal(1) }
152
- end
153
-
154
- describe "#validate with collection form invalid" do
155
- before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {name: "Fat Wreck"}}) }
156
-
157
- it { _(@result).must_equal false }
158
- it { _(form.errors.messages).must_equal("songs.title": ["must be filled"]) }
159
- it { _(form.errors.size).must_equal(1) }
160
- end
161
-
162
- describe "#validate with collection and 2-level-nested invalid" do
163
- before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
164
-
165
- it { _(@result).must_equal false }
166
- it { _(form.errors.messages).must_equal("songs.title": ["must be filled"], "band.label.name": ["must be filled"]) }
167
- it { _(form.errors.size).must_equal(2) }
168
- end
169
-
170
- describe "#validate with nested form using :base invalid" do
171
- it do
172
- result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
173
- _(result).must_equal false
174
- _(form.errors.messages).must_equal("band.name": ["you're a bad person"])
175
- _(form.errors.size).must_equal(1)
176
- end
177
- end
178
-
179
- describe "#add" do
180
- let(:album_title) { nil }
181
- it do
182
- form.errors.add(:before, "validate")
183
- form.errors.add(:before, "validate 2")
184
- form.errors.add(:title, "before validate")
185
- result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
186
- _(result).must_equal false
187
- _(form.errors.messages).must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"])
188
- # add a new custom error
189
- form.errors.add(:policy, "error_text")
190
- _(form.errors.messages).must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"])
191
- # does not duplicate errors
192
- form.errors.add(:title, "must be filled")
193
- _(form.errors.messages).must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"])
194
- # merge existing errors
195
- form.errors.add(:policy, "another error")
196
- _(form.errors.messages).must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"], policy: ["error_text", "another error"])
197
- end
198
- end
199
-
200
- describe "correct #validate" do
201
- before do
202
- @result = form.validate(
203
- "hit" => {"title" => "Sacrifice"},
204
- "title" => "Second Heat",
205
- "songs" => [{"title" => "Heart Of A Lion"}],
206
- "band" => {"label" => {name: "Fat Wreck"}}
207
- )
208
- end
209
-
210
- it { _(@result).must_equal true }
211
- it { _(form.hit.title).must_equal "Sacrifice" }
212
- it { _(form.title).must_equal "Second Heat" }
213
- it { _(form.songs.first.title).must_equal "Heart Of A Lion" }
214
- it do
215
- skip "WE DON'T NEED COUNT AND EMPTY? ON THE CORE ERRORS OBJECT"
216
- _(form.errors.size).must_equal(0)
217
- _(form.errors.empty?).must_equal(true)
218
- end
219
- end
220
-
221
- describe "Errors#to_s" do
222
- before { form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
223
-
224
- # to_s is aliased to messages
225
- it {
226
- skip "why do we need Errors#to_s ?"
227
- _(form.errors.to_s).must_equal "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}"
228
- }
229
- end
230
- end