reform 2.2.4 → 2.3.0.rc1

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -7
  3. data/CHANGES.md +26 -4
  4. data/CONTRIBUTING.md +31 -0
  5. data/Gemfile +1 -12
  6. data/ISSUE_TEMPLATE.md +25 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +3 -3
  9. data/lib/reform.rb +1 -0
  10. data/lib/reform/contract.rb +1 -11
  11. data/lib/reform/contract/validate.rb +49 -23
  12. data/lib/reform/errors.rb +49 -0
  13. data/lib/reform/form.rb +20 -5
  14. data/lib/reform/form/dry.rb +57 -29
  15. data/lib/reform/form/populator.rb +2 -16
  16. data/lib/reform/form/prepopulate.rb +1 -1
  17. data/lib/reform/form/validate.rb +10 -2
  18. data/lib/reform/result.rb +63 -0
  19. data/lib/reform/validation.rb +19 -13
  20. data/lib/reform/validation/groups.rb +11 -25
  21. data/lib/reform/version.rb +1 -1
  22. data/reform.gemspec +7 -6
  23. data/test/benchmarking.rb +39 -5
  24. data/test/call_test.rb +1 -1
  25. data/test/changed_test.rb +1 -1
  26. data/test/coercion_test.rb +2 -2
  27. data/test/composition_test.rb +47 -9
  28. data/test/contract_test.rb +5 -5
  29. data/test/default_test.rb +1 -1
  30. data/test/deserialize_test.rb +3 -3
  31. data/test/errors_test.rb +36 -21
  32. data/test/feature_test.rb +1 -1
  33. data/test/fixtures/dry_error_messages.yml +70 -23
  34. data/test/form_option_test.rb +3 -3
  35. data/test/form_test.rb +3 -3
  36. data/test/from_test.rb +2 -2
  37. data/test/inherit_test.rb +44 -51
  38. data/test/module_test.rb +12 -12
  39. data/test/parse_option_test.rb +40 -0
  40. data/test/parse_pipeline_test.rb +2 -2
  41. data/test/populate_test.rb +59 -19
  42. data/test/populator_skip_test.rb +9 -8
  43. data/test/prepopulator_test.rb +3 -3
  44. data/test/readable_test.rb +2 -2
  45. data/test/readonly_test.rb +1 -1
  46. data/test/reform_test.rb +16 -31
  47. data/test/save_test.rb +23 -8
  48. data/test/setup_test.rb +2 -2
  49. data/test/skip_if_test.rb +4 -4
  50. data/test/skip_setter_and_getter_test.rb +1 -1
  51. data/test/test_helper.rb +13 -10
  52. data/test/validate_test.rb +18 -18
  53. data/test/validation/dry_validation_test.rb +430 -117
  54. data/test/validation/result_test.rb +79 -0
  55. data/test/validation_library_provided_test.rb +16 -0
  56. data/test/virtual_test.rb +1 -1
  57. data/test/writeable_test.rb +31 -2
  58. metadata +42 -23
  59. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  60. data/lib/reform/contract/errors.rb +0 -43
  61. data/lib/reform/form/mongoid.rb +0 -37
  62. data/lib/reform/form/orm.rb +0 -26
  63. data/lib/reform/mongoid.rb +0 -4
  64. data/test/deprecation_test.rb +0 -27
  65. data/test/validation/dry_test.rb +0 -60
  66. data/test/validation/errors.yml +0 -4
@@ -5,22 +5,22 @@ class SaveTest < BaseTest
5
5
  Album = Struct.new(:name, :songs, :artist)
6
6
  Artist = Struct.new(:name)
7
7
 
8
- class AlbumForm < Reform::Form
8
+ class AlbumForm < TestForm
9
9
  property :name
10
10
  validation do
11
- key(:name).required
11
+ required(:name).filled
12
12
  end
13
13
 
14
14
  collection :songs do
15
15
  property :title
16
16
  validation do
17
- key(:title).required
17
+ required(:title).filled
18
18
  end
19
19
 
20
20
  property :composer do
21
21
  property :name
22
22
  validation do
23
- key(:name).required
23
+ required(:name).filled
24
24
  end
25
25
  end
26
26
  end
@@ -36,7 +36,7 @@ class SaveTest < BaseTest
36
36
  end
37
37
 
38
38
  def saved?
39
- @saved
39
+ defined?(@saved) && @saved
40
40
  end
41
41
  end
42
42
 
@@ -58,7 +58,22 @@ class SaveTest < BaseTest
58
58
  album.saved?.must_equal true
59
59
  album.songs[0].title.must_equal "Fixed"
60
60
  album.songs[0].saved?.must_equal true
61
- album.artist.saved?.must_equal nil
61
+ assert_nil album.artist.saved?
62
+ end
63
+
64
+ describe "#sync with block" do
65
+ it do
66
+ form = AlbumForm.new(Album.new("Greatest Hits"))
67
+
68
+ form.validate(name: nil) # nil-out the title.
69
+
70
+ nested_hash = nil
71
+ form.sync do |hash|
72
+ nested_hash = hash
73
+ end
74
+
75
+ nested_hash.must_equal({"name"=>nil, "artist"=>nil})
76
+ end
62
77
  end
63
78
  end
64
79
 
@@ -68,7 +83,7 @@ end
68
83
  # include Saveable
69
84
  # end
70
85
 
71
- # class SongForm < Reform::Form
86
+ # class SongForm < TestForm
72
87
  # property :title#, save: false
73
88
  # property :length, virtual: true
74
89
  # end
@@ -83,7 +98,7 @@ end
83
98
  # form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
84
99
 
85
100
  # song.title.must_equal "A Poor Man's Memory"
86
- # song.length.must_equal nil
101
+ # assert_nil song.length
87
102
  # song.id.must_equal "10: 120"
88
103
  # end
89
104
  # end
@@ -5,7 +5,7 @@ class SetupTest < MiniTest::Spec
5
5
  Album = Struct.new(:name, :songs, :artist)
6
6
  Artist = Struct.new(:name)
7
7
 
8
- class AlbumForm < Reform::Form
8
+ class AlbumForm < TestForm
9
9
  property :name
10
10
  collection :songs do
11
11
  property :title
@@ -33,7 +33,7 @@ class SetupTest < MiniTest::Spec
33
33
 
34
34
  form.name.must_equal "The Dissent Of Man"
35
35
  form.songs[0].title.must_equal "Broken"
36
- form.songs[0].composer.must_equal nil
36
+ assert_nil form.songs[0].composer
37
37
  form.songs[1].title.must_equal "Resist Stance"
38
38
  form.songs[1].composer.name.must_equal "Greg Graffin"
39
39
  form.artist.name.must_equal "Bad Religion"
@@ -2,13 +2,13 @@ require 'test_helper'
2
2
 
3
3
  class SkipIfTest < BaseTest
4
4
 
5
- class AlbumForm < Reform::Form
5
+ class AlbumForm < TestForm
6
6
  property :title
7
7
 
8
8
  property :hit, skip_if: lambda { |options| options[:fragment]["title"]=="" } do
9
9
  property :title
10
10
  validation do
11
- key(:title).required
11
+ required(:title).filled
12
12
  end
13
13
  end
14
14
 
@@ -36,7 +36,7 @@ class SkipIfTest < BaseTest
36
36
  it do
37
37
  form = AlbumForm.new(Album.new)
38
38
  form.validate("hit" => {"title" => ""}).must_equal true
39
- form.hit.must_equal nil # hit hasn't been deserialised.
39
+ assert_nil form.hit # hit hasn't been deserialised.
40
40
  end
41
41
 
42
42
  # skips deserialization when not present.
@@ -50,7 +50,7 @@ end
50
50
 
51
51
  class SkipIfAllBlankTest < BaseTest
52
52
  # skip_if: :all_blank"
53
- class AlbumForm < Reform::Form
53
+ class AlbumForm < TestForm
54
54
  collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
55
55
  property :title
56
56
  property :length
@@ -7,7 +7,7 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
7
7
  Album = Struct.new(:title, :artist)
8
8
  Artist = Struct.new(:name)
9
9
 
10
- class AlbumForm < Reform::Form
10
+ class AlbumForm < TestForm
11
11
  property :title
12
12
 
13
13
  def title
@@ -3,9 +3,20 @@ require 'minitest/autorun'
3
3
  require "representable/debug"
4
4
  require "declarative/testing"
5
5
  require "pp"
6
+ require 'byebug'
7
+
8
+ require "reform/form/dry"
9
+ # setup test classes so we can test without dry being included
10
+ class TestForm < Reform::Form
11
+ feature Reform::Form::Dry
12
+ end
13
+
14
+ class TestContract < Reform::Contract
15
+ feature Reform::Form::Dry
16
+ end
6
17
 
7
18
  class BaseTest < MiniTest::Spec
8
- class AlbumForm < Reform::Form
19
+ class AlbumForm < TestForm
9
20
  property :title
10
21
 
11
22
  property :hit do
@@ -17,7 +28,7 @@ class BaseTest < MiniTest::Spec
17
28
  end
18
29
  end
19
30
 
20
- Song = Struct.new(:title, :length)
31
+ Song = Struct.new(:title, :length, :rating)
21
32
  Album = Struct.new(:title, :hit, :songs, :band)
22
33
  Band = Struct.new(:label)
23
34
  Label = Struct.new(:name)
@@ -39,11 +50,3 @@ MiniTest::Spec.class_eval do
39
50
  end
40
51
  end
41
52
 
42
- require "reform/form/dry"
43
- Reform::Contract.class_eval do
44
- feature Reform::Form::Dry
45
- end
46
- # FIXME!
47
- Reform::Form.class_eval do
48
- feature Reform::Form::Dry
49
- end
@@ -5,21 +5,21 @@ class ContractValidateTest < MiniTest::Spec
5
5
  Album = Struct.new(:name, :songs, :artist)
6
6
  Artist = Struct.new(:name)
7
7
 
8
- class AlbumForm < Reform::Contract
8
+ class AlbumForm < TestContract
9
9
  property :name
10
10
  validation do
11
- key(:name).required
11
+ required(:name).filled
12
12
  end
13
13
 
14
14
  collection :songs do
15
15
  property :title
16
16
  validation do
17
- key(:title).required
17
+ required(:title).filled
18
18
  end
19
19
 
20
20
  property :composer do
21
21
  validation do
22
- key(:name).required
22
+ required(:name).filled
23
23
  end
24
24
  property :name
25
25
  end
@@ -50,7 +50,7 @@ class ContractValidateTest < MiniTest::Spec
50
50
  album.name = nil
51
51
 
52
52
  form.validate.must_equal false
53
- form.errors.messages.inspect.must_equal "{:name=>[\"is missing\"], :\"songs.composer.name\"=>[\"is missing\"]}"
53
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"]}"
54
54
  end
55
55
  end
56
56
 
@@ -61,23 +61,23 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
61
61
  Album = Struct.new(:name, :songs, :artist)
62
62
  Artist = Struct.new(:name)
63
63
 
64
- class AlbumForm < Reform::Form
64
+ class AlbumForm < TestForm
65
65
  property :name
66
66
  validation do
67
- key(:name).required
67
+ required(:name).filled
68
68
  end
69
69
 
70
70
  collection :songs do
71
71
 
72
72
  property :title
73
73
  validation do
74
- key(:title).required
74
+ required(:title).filled
75
75
  end
76
76
 
77
77
  property :composer do
78
78
  property :name
79
79
  validation do
80
- key(:name).required
80
+ required(:name).filled
81
81
  end
82
82
  end
83
83
  end
@@ -160,10 +160,10 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
160
160
  Album = Struct.new(:name, :songs, :artist)
161
161
  Artist = Struct.new(:name)
162
162
 
163
- class AlbumForm < Reform::Form
163
+ class AlbumForm < TestForm
164
164
  property :name
165
165
  validation do
166
- key(:name).required
166
+ required(:name).filled
167
167
  end
168
168
 
169
169
  collection :songs,
@@ -173,13 +173,13 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
173
173
 
174
174
  property :title
175
175
  validation do
176
- key(:title).required
176
+ required(:title).filled
177
177
  end
178
178
 
179
179
  property :composer, internal_populator: lambda { |input, options| (item = options[:represented].composer) ? item : Artist.new } do
180
180
  property :name
181
181
  validation do
182
- key(:name).required
182
+ required(:name).filled
183
183
  end
184
184
  end
185
185
  end
@@ -187,7 +187,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
187
187
  property :artist, internal_populator: lambda { |input, options| (item = options[:represented].artist) ? item : Artist.new } do
188
188
  property :name
189
189
  validation do
190
- key(:name).required
190
+ required(:name).filled
191
191
  end
192
192
  end
193
193
  end
@@ -268,21 +268,21 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
268
268
 
269
269
 
270
270
  # allow writeable: false even in the deserializer.
271
- class SongForm < Reform::Form
272
- property :title, deserializer: {writeable: false}
271
+ class SongForm < TestForm
272
+ property :title, deserializer: { writeable: false }
273
273
  end
274
274
 
275
275
  it do
276
276
  form = SongForm.new(song = Song.new)
277
277
  form.validate("title" => "Ignore me!")
278
- form.title.must_equal nil
278
+ assert_nil form.title
279
279
  form.title = "Unopened"
280
280
  form.sync # only the deserializer is marked as not-writeable.
281
281
  song.title.must_equal "Unopened"
282
282
  end
283
283
  end
284
284
 
285
- # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/apotonick/reform/pull/104
285
+ # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/trailblazer/reform/pull/104
286
286
  # # describe ":populator with :empty" do
287
287
  # # let (:form) {
288
288
  # # Class.new(Reform::Form) do
@@ -1,20 +1,208 @@
1
+ # encoding: utf-8
1
2
  require "test_helper"
2
3
  require "reform/form/dry"
4
+ require "reform/form/coercion"
5
+
6
+ #---
7
+ # one "nested" Schema per form.
8
+ class DryValidationErrorsAPITest < Minitest::Spec
9
+ Album = Struct.new(:title, :artist, :songs)
10
+ Song = Struct.new(:title)
11
+ Artist = Struct.new(:email, :label)
12
+ Label = Struct.new(:location)
13
+
14
+ class AlbumForm < TestForm
15
+ property :title
16
+
17
+ validation do
18
+ # required(:title).filled
19
+ required(:title).filled(min_size?: 2)
20
+ end
21
+
22
+ property :artist do
23
+ property :email
24
+
25
+ validation do
26
+ required(:email).filled
27
+ end
28
+
29
+ property :label do
30
+ property :location
31
+
32
+ validation do
33
+ required(:location).filled
34
+ end
35
+ end
36
+ end
37
+
38
+ # note the validation block is *in* the collection block, per item, so to speak.
39
+ collection :songs do
40
+ property :title
41
+
42
+ validation do
43
+ configure do
44
+ config.messages_file = 'test/fixtures/dry_error_messages.yml'
45
+ end
46
+
47
+ required(:title).filled
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ let (:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
54
+
55
+ it "everything wrong" do
56
+ result = form.({ title: nil, artist: { email: "" }, songs: [{ title: "Clams have feelings too" }, { title: "" }] })
57
+
58
+ result.success?.must_equal false
59
+
60
+ # errors.messages
61
+ form.errors.messages.must_equal({:title=>["must be filled", "size cannot be less than 2"], :"artist.email"=>["must be filled"], :"artist.label.location"=>["must be filled"], :"songs.title"=>["must be filled"]})
62
+ form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
63
+ form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
64
+ form.songs[0].errors.messages.must_equal({})
65
+ form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
66
+
67
+ # #errors[]
68
+ form.errors[:nonsense].must_be_nil
69
+ form.errors[:title].must_equal ["must be filled", "size cannot be less than 2"]
70
+ form.artist.errors[:email].must_equal ["must be filled"]
71
+ form.artist.label.errors[:location].must_equal ["must be filled"]
72
+ form.songs[0].errors[:title].must_be_nil
73
+ form.songs[1].errors[:title].must_equal ["must be filled"]
74
+
75
+ # #to_result
76
+ form.to_result.errors.must_equal({:title=>["must be filled"]})
77
+ form.to_result.messages.must_equal({:title=>["must be filled", "size cannot be less than 2"]})
78
+ form.to_result.hints.must_equal({:title=>["size cannot be less than 2"]})
79
+ form.artist.to_result.errors.must_equal({:email=>["must be filled"]})
80
+ form.artist.to_result.messages.must_equal({:email=>["must be filled"]})
81
+ form.artist.to_result.hints.must_equal({:email=>[]})
82
+ form.artist.label.to_result.errors.must_equal({:location=>["must be filled"]})
83
+ form.artist.label.to_result.messages.must_equal({:location=>["must be filled"]})
84
+ form.artist.label.to_result.hints.must_equal({:location=>[]})
85
+ form.songs[0].to_result.errors.must_equal({})
86
+ form.songs[0].to_result.messages.must_equal({})
87
+ form.songs[0].to_result.hints.must_equal({})
88
+ form.songs[1].to_result.errors.must_equal({:title=>["must be filled"]})
89
+ form.songs[1].to_result.messages.must_equal({:title=>["must be filled"]})
90
+ form.songs[1].to_result.hints.must_equal({:title=>[]})
91
+ form.songs[1].to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
92
+ form.songs[1].to_result.messages(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
93
+ form.songs[1].to_result.hints(locale: :de).must_equal({:title=>[]})
94
+ end
95
+
96
+ it "only nested property is invalid." do
97
+ result = form.({ title: "Black Star", artist: { email: "" } })
98
+
99
+ result.success?.must_equal false
100
+
101
+ # errors.messages
102
+ form.errors.messages.must_equal({:"artist.email"=>["must be filled"], :"artist.label.location"=>["must be filled"], :"songs.title"=>["must be filled"]})
103
+ form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
104
+ form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
105
+ end
106
+
107
+ it "nested collection invalid" do
108
+ result = form.({ title: "Black Star", artist: { email: "uhm", label: { location: "Hannover" } }, songs: [ { title: "" } ] })
109
+
110
+ result.success?.must_equal false
111
+ form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
112
+ end
113
+
114
+ #---
115
+ #- validation .each
116
+ class CollectionExternalValidationsForm < TestForm
117
+ collection :songs do
118
+ property :title
119
+ end
120
+
121
+ validation do
122
+ required(:songs).each do
123
+ schema do
124
+ required(:title).filled
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ it do
131
+ form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
132
+ form.validate(songs: [ { title: "Liar"}, { title: ""} ])
133
+
134
+ form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
135
+ form.songs[0].errors.messages.must_equal({})
136
+ form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
137
+ end
138
+ end
139
+
140
+ class DryValidationExplicitSchemaTest < Minitest::Spec
141
+ Session = Struct.new(:name, :email)
142
+ SessionSchema = Dry::Validation.Schema do
143
+ required(:name).filled
144
+ required(:email).filled
145
+ end
146
+
147
+ class SessionForm < TestForm
148
+ include Coercion
149
+
150
+ property :name
151
+ property :email
152
+
153
+ validation schema: SessionSchema
154
+ end
155
+
156
+ let (:form) { SessionForm.new(Session.new) }
157
+
158
+ # valid.
159
+ it do
160
+ form.validate(name: "Helloween", email: "yep").must_equal true
161
+ form.errors.messages.inspect.must_equal "{}"
162
+ end
163
+
164
+ it "invalid" do
165
+ form.validate(name: "", email: "yep").must_equal false
166
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"]}"
167
+ end
168
+ end
3
169
 
4
170
  class DryValidationDefaultGroupTest < Minitest::Spec
5
- Session = Struct.new(:username, :email, :password, :confirm_password)
171
+ Session = Struct.new(:username, :email, :password, :confirm_password, :starts_at, :active, :color)
6
172
 
7
- class SessionForm < Reform::Form
8
- include Reform::Form::Dry
173
+ class SessionForm < TestForm
174
+ include Coercion
9
175
 
10
176
  property :username
11
177
  property :email
12
178
  property :password
13
179
  property :confirm_password
180
+ property :starts_at, type: Types::Form::DateTime
181
+ property :active, type: Types::Form::Bool
182
+ property :color
14
183
 
15
184
  validation do
16
- key(:username).required
17
- key(:email).required
185
+ required(:username).filled
186
+ required(:email).filled
187
+ required(:starts_at).filled(:date_time?)
188
+ required(:active).filled(:bool?)
189
+ end
190
+
191
+ validation name: :another_block do
192
+ required(:confirm_password).filled
193
+ end
194
+
195
+ validation name: :dynamic_args, with: { form: true } do
196
+ configure do
197
+ def colors
198
+ form.colors
199
+ end
200
+ end
201
+ required(:color).maybe(included_in?: colors)
202
+ end
203
+
204
+ def colors
205
+ %(red orange green)
18
206
  end
19
207
  end
20
208
 
@@ -23,38 +211,52 @@ class DryValidationDefaultGroupTest < Minitest::Spec
23
211
  # valid.
24
212
  it do
25
213
  form.validate(username: "Helloween",
26
- email: "yep").must_equal true
214
+ email: "yep",
215
+ starts_at: "01/01/2000 - 11:00",
216
+ active: "true",
217
+ confirm_password: 'pA55w0rd').must_equal true
27
218
  form.errors.messages.inspect.must_equal "{}"
28
219
  end
220
+
221
+ it "invalid" do
222
+ form.validate(username: "Helloween",
223
+ email: "yep",
224
+ active: 'hello',
225
+ starts_at: "01/01/2000 - 11:00",
226
+ color: 'purple').must_equal false
227
+ form.errors.messages.inspect.must_equal "{:active=>[\"must be boolean\"], :confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
228
+ end
29
229
  end
30
230
 
31
231
  class ValidationGroupsTest < MiniTest::Spec
32
232
  describe "basic validations" do
33
- Session = Struct.new(:username, :email, :password, :confirm_password)
233
+ Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
234
+ SomeClass= Struct.new(:id)
34
235
 
35
- class SessionForm < Reform::Form
36
- include Reform::Form::Dry::Validations
236
+ class SessionForm < TestForm
37
237
 
38
238
  property :username
39
239
  property :email
40
240
  property :password
41
241
  property :confirm_password
242
+ property :special_class
42
243
 
43
- validation :default do
44
- key(:username).required
45
- key(:email).required
244
+ validation do
245
+ required(:username).filled
246
+ required(:email).filled
247
+ required(:special_class).filled(type?: SomeClass)
46
248
  end
47
249
 
48
- validation :email, if: :default do
49
- key(:email).required(min_size?: 3)
250
+ validation name: :email, if: :default do
251
+ required(:email).filled(min_size?: 3)
50
252
  end
51
253
 
52
- validation :nested, if: :default do
53
- key(:password).required(min_size?: 2)
254
+ validation name: :nested, if: :default do
255
+ required(:password).filled(min_size?: 2)
54
256
  end
55
257
 
56
- validation :confirm, if: :default, after: :email do
57
- key(:confirm_password).required(min_size?: 2)
258
+ validation name: :confirm, if: :default, after: :email do
259
+ required(:confirm_password).filled(min_size?: 2)
58
260
  end
59
261
  end
60
262
 
@@ -63,6 +265,7 @@ class ValidationGroupsTest < MiniTest::Spec
63
265
  # valid.
64
266
  it do
65
267
  form.validate({ username: "Helloween",
268
+ special_class: SomeClass.new(id: 15),
66
269
  email: "yep",
67
270
  password: "99",
68
271
  confirm_password: "99" }).must_equal true
@@ -72,160 +275,274 @@ class ValidationGroupsTest < MiniTest::Spec
72
275
  # invalid.
73
276
  it do
74
277
  form.validate({}).must_equal false
75
- form.errors.messages.inspect.must_equal "{:username=>[\"is missing\"], :email=>[\"is missing\"]}"
278
+ form.errors.messages.must_equal({:username=>["must be filled"], :email=>["must be filled"], :special_class=>["must be filled", "must be ValidationGroupsTest::SomeClass"]})
76
279
  end
77
280
 
78
281
  # partially invalid.
79
282
  # 2nd group fails.
80
283
  it do
81
- form.validate(username: "Helloween", email: "yo", confirm_password:"9").must_equal false
82
- form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"], :password=>[\"is missing\", \"size cannot be less than 2\"]}"
284
+ form.validate(username: "Helloween", email: "yo", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
285
+ form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
83
286
  end
84
287
  # 3rd group fails.
85
288
  it do
86
- form.validate(username: "Helloween", email: "yo!", confirm_password:"9").must_equal false
289
+ form.validate(username: "Helloween", email: "yo!", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
87
290
  form.errors.messages.inspect
88
- .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"is missing\", \"size cannot be less than 2\"]}"
291
+ .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
89
292
  end
90
293
  # 4th group with after: fails.
91
294
  it do
92
- form.validate(username: "Helloween", email: "yo!", password: "", confirm_password: "9").must_equal false
93
- form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
295
+ form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
296
+ form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"size cannot be less than 2\"]}"
297
+ end
298
+ end
299
+
300
+ class ValidationWithOptionsTest < MiniTest::Spec
301
+ describe "basic validations" do
302
+ Session = Struct.new(:username)
303
+ class SessionForm < TestForm
304
+ property :username
305
+
306
+ validation name: :default, with: { user: OpenStruct.new(name: "Nick") } do
307
+ configure do
308
+ def users_name
309
+ user.name
310
+ end
311
+ end
312
+ required(:username).filled(eql?: users_name)
313
+ end
314
+ end
315
+
316
+ let (:form) { SessionForm.new(Session.new) }
317
+
318
+ # valid.
319
+ it do
320
+ form.validate({ username: "Nick" }).must_equal true
321
+ form.errors.messages.inspect.must_equal "{}"
322
+ end
323
+
324
+ # invalid.
325
+ it do
326
+ form.validate({ username: 'Fred'}).must_equal false
327
+ form.errors.messages.inspect.must_equal "{:username=>[\"must be equal to Nick\"]}"
328
+ end
94
329
  end
95
330
  end
96
331
 
97
- describe "Nested validations" do
98
- class AlbumForm < Reform::Form
99
- include Reform::Form::Dry::Validations
332
+ #---
333
+ #- validation( schema: MySchema )
334
+ describe "with custom schema" do
335
+ Session2 = Struct.new(:username, :email, :password)
336
+
337
+ MySchema = Dry::Validation.Schema do
338
+ configure do
339
+ config.messages_file = 'test/fixtures/dry_error_messages.yml'
340
+
341
+ def good_musical_taste?(val)
342
+ val.is_a? String
343
+ end
344
+
345
+ end
346
+
347
+ required(:password).filled(:min_size? => 6)
348
+ end
349
+
350
+ class Session2Form < TestForm
351
+ property :username
352
+ property :email
353
+ property :password
100
354
 
355
+ validation schema: MySchema do
356
+ required(:username).filled
357
+ required(:email).filled(:good_musical_taste?)
358
+ end
359
+ end
360
+
361
+ let (:form) { Session2Form.new(Session2.new) }
362
+
363
+ # valid.
364
+ it do
365
+ form.validate({ username: "Helloween", email: "yep", password: "extrasafe" }).must_equal true
366
+ form.errors.messages.inspect.must_equal "{}"
367
+ end
368
+
369
+ # invalid.
370
+ it do
371
+ form.validate({}).must_equal false
372
+ form.errors.messages.must_equal({:password=>["must be filled", "size cannot be less than 6"], :username=>["must be filled"], :email=>["must be filled", "you're a bad person"]})
373
+ end
374
+
375
+ it do
376
+ form.validate({email: 1}).must_equal false
377
+ form.errors.messages.inspect.must_equal "{:password=>[\"must be filled\", \"size cannot be less than 6\"], :username=>[\"must be filled\"], :email=>[\"you're a bad person\"]}"
378
+ end
379
+ end
380
+
381
+ describe "MIXED nested validations" do
382
+ class AlbumForm < TestForm
101
383
  property :title
102
384
 
103
385
  property :hit do
104
386
  property :title
105
387
 
106
- # FIX ME: this doesn't work now, @apotonick said he knows why
107
- # The error is that this validation block act as an AM:V instead of the Dry one.
108
- # validation :default do
109
- # key(:title, &:filled?)
110
- # end
388
+ validation do
389
+ required(:title).filled
390
+ end
111
391
  end
112
392
 
113
393
  collection :songs do
114
394
  property :title
395
+
396
+ validation do
397
+ required(:title).filled
398
+ end
399
+ end
400
+
401
+ # we test this one by running an each / schema dry-v check on the main block
402
+ collection :producers do
403
+ property :name
115
404
  end
116
405
 
117
406
  property :band do
118
407
  property :name
119
408
  property :label do
120
- property :name
409
+ property :location
121
410
  end
122
411
  end
123
412
 
124
- validation :default do
125
-
126
- key(:title).required
127
-
128
- key(:band).schema do
129
- key(:name).required
130
- key(:label).schema do
131
- key(:name).required
132
- end
133
- end
134
-
413
+ validation do
135
414
  configure do
136
- option :form
415
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
137
416
  # message need to be defined on fixtures/dry_error_messages
138
417
  # d-v expects you to define your custome messages on the .yml file
139
418
  def good_musical_taste?(value)
140
419
  value != 'Nickelback'
141
420
  end
421
+ end
422
+
423
+ required(:title).filled(:good_musical_taste?)
142
424
 
143
- def form_access_validation?(value)
144
- form.title == 'Reform'
425
+ required(:band).schema do
426
+ required(:name).filled
427
+ required(:label).schema do
428
+ required(:location).filled
429
+ end
430
+ end
431
+
432
+ required(:producers).each do
433
+ schema do
434
+ required(:name).filled
145
435
  end
146
436
  end
147
437
 
148
- key(:title).required(:good_musical_taste?)
149
- key(:title).required(:form_access_validation?)
150
438
  end
151
439
  end
152
440
 
153
441
  let (:album) do
154
442
  OpenStruct.new(
155
- :title => "Blackhawks Over Los Angeles",
156
- :hit => song,
157
- :songs => songs,
158
- :band => Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
443
+ :hit => OpenStruct.new,
444
+ :songs => [OpenStruct.new, OpenStruct.new],
445
+ :band => Struct.new(:name, :label).new("", OpenStruct.new),
446
+ :producers => [OpenStruct.new, OpenStruct.new, OpenStruct.new],
159
447
  )
160
448
  end
161
- let (:song) { OpenStruct.new(:title => "Downtown") }
162
- let (:songs) { [ song = OpenStruct.new(:title => "Calling"), song ] }
449
+
163
450
  let (:form) { AlbumForm.new(album) }
164
451
 
165
- # correct #validate.
166
- it do
452
+ it "maps errors to form objects correctly" do
167
453
  result = form.validate(
168
- "title" => "Reform",
169
- "songs" => [
170
- {"title" => "Fallout"},
171
- {"title" => "Roxanne", "composer" => {"name" => "Sting"}}
172
- ],
173
- "band" => {"label" => {"name" => "Epitaph"}},
454
+ "title" => "Nickelback",
455
+ "songs" => [ {"title" => ""}, {"title" => ""} ],
456
+ "band" => {"size" => "", "label" => {"location" => ""}},
457
+ "producers" => [{"name" => ''}, {"name" => 'something lovely'}]
174
458
  )
175
459
 
176
- result.must_equal true
177
- form.errors.messages.inspect.must_equal "{}"
178
- end
179
- end
460
+ result.must_equal false
461
+ # from nested validation
462
+ form.errors.messages.must_equal({:title=>["you're a bad person"], :"hit.title"=>["must be filled"], :"songs.title"=>["must be filled"], :"producers.name"=>["must be filled"], :"band.name"=>["must be filled"], :"band.label.location"=>["must be filled"]})
463
+
464
+ # songs have their own validation.
465
+ form.songs[0].errors.messages.must_equal({:title=>["must be filled"]})
466
+ # hit got its own validation group.
467
+ form.hit.errors.messages.must_equal({:title=>["must be filled"]})
180
468
 
469
+ form.band.label.errors.messages.must_equal({:location=>["must be filled"]})
470
+ form.band.errors.messages.must_equal({:name=>["must be filled"], :"label.location"=>["must be filled"]})
471
+ form.producers[0].errors.messages.must_equal({:name=>["must be filled"]})
472
+
473
+ # TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
474
+ form.producers[0].to_result.errors.must_equal({:name=>["must be filled"]})
475
+ form.producers[0].to_result.messages.must_equal({:name=>["must be filled"]})
476
+ form.producers[0].to_result.hints.must_equal({:name=>[]})
477
+ end
181
478
 
182
- describe "fails with :validate, :validates and :validates_with" do
479
+ # FIXME: fix the "must be filled error"
183
480
 
184
- it "throws a goddamn error" do
185
- e = proc do
186
- class FailingForm < Reform::Form
187
- include Reform::Form::Dry::Validations
481
+ it "renders full messages correctly" do
482
+ result = form.validate(
483
+ "title" => "",
484
+ "songs" => [ {"title" => ""}, {"title" => ""} ],
485
+ "band" => {"size" => "", "label" => {"name" => ""}},
486
+ "producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
487
+ )
188
488
 
189
- property :username
489
+ result.must_equal false
490
+ form.band.errors.full_messages.must_equal ["Name must be filled", "Label Location must be filled"]
491
+ form.band.label.errors.full_messages.must_equal ["Location must be filled"]
492
+ form.producers.first.errors.full_messages.must_equal ["Name must be filled"]
493
+ form.errors.full_messages.must_equal ["Title must be filled", "Title you're a bad person", "Hit Title must be filled", "Songs Title must be filled", "Producers Name must be filled", "Band Name must be filled", "Band Label Location must be filled"]
494
+ end
190
495
 
191
- validation :email do
192
- validates(:email, &:filled?)
496
+ describe "only 1 nested validation" do
497
+ class AlbumFormWith1NestedVal < TestForm
498
+ property :title
499
+ property :band do
500
+ property :name
501
+ property :label do
502
+ property :location
193
503
  end
194
504
  end
195
- end.must_raise(NoMethodError)
196
- # e.message.must_equal 'validates() is not supported by Dry Validation backend.'
197
505
 
198
- e = proc do
199
- class FailingForm < Reform::Form
200
- include Reform::Form::Dry::Validations
506
+ validation do
507
+ configure do
508
+ config.messages_file = 'test/fixtures/dry_error_messages.yml'
509
+ end
201
510
 
202
- property :username
511
+ required(:title).filled
203
512
 
204
- validation :email do
205
- validate(:email, &:filled?)
513
+ required(:band).schema do
514
+ required(:name).filled
515
+ required(:label).schema do
516
+ required(:location).filled
517
+ end
206
518
  end
207
519
  end
208
- end.must_raise(NoMethodError)
209
- # e.message.must_equal 'validate() is not supported by Dry Validation backend.'
520
+ end
210
521
 
211
- e = proc do
212
- class FailingForm < Reform::Form
213
- include Reform::Form::Dry::Validations
522
+ let (:form) { AlbumFormWith1NestedVal.new(album) }
214
523
 
215
- property :username
524
+ it "allows to access dry's result semantics per nested form" do
525
+ result = form.validate(
526
+ "title" => "",
527
+ "songs" => [ {"title" => ""}, {"title" => ""} ],
528
+ "band" => {"size" => "", "label" => {"name" => ""}},
529
+ "producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
530
+ )
216
531
 
217
- validation :email do
218
- validates_with(:email, &:filled?)
219
- end
220
- end
221
- end.must_raise(NoMethodError)
222
- # e.message.must_equal (NoMethodError)'validates_with() is not supported by Dry Validation backend.'
532
+ form.to_result.errors.must_equal({:title=>["must be filled"]})
533
+ form.band.to_result.errors.must_equal({:name=>["must be filled"]})
534
+ form.band.label.to_result.errors.must_equal({:location=>["must be filled"]})
535
+
536
+ # with locale: "de"
537
+ form.to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
538
+ form.band.to_result.errors(locale: :de).must_equal({:name=>["muss abgefüllt sein"]})
539
+ form.band.label.to_result.errors(locale: :de).must_equal({:location=>["muss abgefüllt sein"]})
540
+ end
223
541
  end
224
542
  end
225
543
 
226
-
227
544
  # describe "same-named group" do
228
- # class OverwritingForm < Reform::Form
545
+ # class OverwritingForm < TestForm
229
546
  # include Reform::Form::Dry::Validations
230
547
 
231
548
  # property :username
@@ -256,18 +573,16 @@ class ValidationGroupsTest < MiniTest::Spec
256
573
 
257
574
 
258
575
  describe "inherit: true in same group" do
259
- class InheritSameGroupForm < Reform::Form
260
- include Reform::Form::Dry::Validations
261
-
576
+ class InheritSameGroupForm < TestForm
262
577
  property :username
263
578
  property :email
264
579
 
265
- validation :email do
266
- key(:email).required
580
+ validation name: :email do
581
+ required(:email).filled
267
582
  end
268
583
 
269
- validation :email, inherit: true do # extends the above.
270
- key(:username).required
584
+ validation name: :email, inherit: true do # extends the above.
585
+ required(:username).filled
271
586
  end
272
587
  end
273
588
 
@@ -281,31 +596,29 @@ class ValidationGroupsTest < MiniTest::Spec
281
596
  # invalid.
282
597
  it do
283
598
  form.validate({}).must_equal false
284
- form.errors.messages.inspect.must_equal "{:email=>[\"is missing\"], :username=>[\"is missing\"]}"
599
+ form.errors.messages.inspect.must_equal "{:email=>[\"must be filled\"], :username=>[\"must be filled\"]}"
285
600
  end
286
601
  end
287
602
 
288
603
 
289
604
  describe "if: with lambda" do
290
- class IfWithLambdaForm < Reform::Form
291
- include Reform::Form::Dry::Validations # ::build_errors.
292
-
605
+ class IfWithLambdaForm < TestForm
293
606
  property :username
294
607
  property :email
295
608
  property :password
296
609
 
297
- validation :email do
298
- key(:email).required
610
+ validation name: :email do
611
+ required(:email).filled
299
612
  end
300
613
 
301
614
  # run this is :email group is true.
302
- validation :after_email, if: lambda { |results| results[:email]==true } do # extends the above.
303
- key(:username).required
615
+ validation name: :after_email, if: lambda { |results| results[:email].success? } do # extends the above.
616
+ required(:username).filled
304
617
  end
305
618
 
306
619
  # block gets evaled in form instance context.
307
- validation :password, if: lambda { |results| email == "john@trb.org" } do
308
- key(:password).required
620
+ validation name: :password, if: lambda { |results| email == "john@trb.org" } do
621
+ required(:password).filled
309
622
  end
310
623
  end
311
624
 
@@ -319,7 +632,7 @@ class ValidationGroupsTest < MiniTest::Spec
319
632
  # invalid.
320
633
  it do
321
634
  form.validate({email: 9}).must_equal false
322
- form.errors.messages.inspect.must_equal "{:username=>[\"is missing\"]}"
635
+ form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
323
636
  end
324
637
  end
325
638
 
@@ -329,7 +642,7 @@ class ValidationGroupsTest < MiniTest::Spec
329
642
  # more errors messages than only those that have failed.
330
643
  #
331
644
  # describe "multiple errors for property" do
332
- # class MultipleErrorsForPropertyForm < Reform::Form
645
+ # class MultipleErrorsForPropertyForm < TestForm
333
646
  # include Reform::Form::Dry::Validations
334
647
 
335
648
  # property :username