reform 2.2.3 → 2.3.2

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 (97) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +11 -6
  4. data/Appraisals +8 -0
  5. data/CHANGES.md +56 -0
  6. data/CONTRIBUTING.md +31 -0
  7. data/Gemfile +2 -16
  8. data/ISSUE_TEMPLATE.md +25 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +5 -7
  11. data/Rakefile +16 -9
  12. data/gemfiles/0.13.0.gemfile +8 -0
  13. data/gemfiles/1.5.0.gemfile +9 -0
  14. data/lib/reform.rb +1 -0
  15. data/lib/reform/contract.rb +7 -17
  16. data/lib/reform/contract/custom_error.rb +41 -0
  17. data/lib/reform/contract/validate.rb +53 -23
  18. data/lib/reform/errors.rb +61 -0
  19. data/lib/reform/form.rb +36 -10
  20. data/lib/reform/form/call.rb +1 -1
  21. data/lib/reform/form/composition.rb +2 -2
  22. data/lib/reform/form/dry.rb +10 -58
  23. data/lib/reform/form/dry/input_hash.rb +37 -0
  24. data/lib/reform/form/dry/new_api.rb +47 -0
  25. data/lib/reform/form/dry/old_api.rb +61 -0
  26. data/lib/reform/form/populator.rb +11 -29
  27. data/lib/reform/form/prepopulate.rb +5 -4
  28. data/lib/reform/form/validate.rb +28 -13
  29. data/lib/reform/result.rb +90 -0
  30. data/lib/reform/validation.rb +19 -11
  31. data/lib/reform/validation/groups.rb +12 -27
  32. data/lib/reform/version.rb +1 -1
  33. data/reform.gemspec +13 -13
  34. data/test/benchmarking.rb +39 -6
  35. data/test/call_new_api.rb +23 -0
  36. data/test/{call_test.rb → call_old_api.rb} +4 -4
  37. data/test/changed_test.rb +8 -8
  38. data/test/coercion_test.rb +51 -19
  39. data/test/composition_new_api.rb +186 -0
  40. data/test/{composition_test.rb → composition_old_api.rb} +66 -31
  41. data/test/contract/custom_error_test.rb +55 -0
  42. data/test/contract_new_api.rb +77 -0
  43. data/test/{contract_test.rb → contract_old_api.rb} +13 -13
  44. data/test/default_test.rb +2 -2
  45. data/test/deserialize_test.rb +11 -14
  46. data/test/errors_new_api.rb +225 -0
  47. data/test/errors_old_api.rb +230 -0
  48. data/test/feature_test.rb +8 -10
  49. data/test/fixtures/dry_error_messages.yml +73 -23
  50. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  51. data/test/form_new_api.rb +57 -0
  52. data/test/{form_test.rb → form_old_api.rb} +5 -5
  53. data/test/form_option_new_api.rb +24 -0
  54. data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
  55. data/test/from_test.rb +9 -13
  56. data/test/inherit_new_api.rb +105 -0
  57. data/test/inherit_old_api.rb +105 -0
  58. data/test/{module_test.rb → module_new_api.rb} +20 -25
  59. data/test/module_old_api.rb +146 -0
  60. data/test/parse_option_test.rb +40 -0
  61. data/test/parse_pipeline_test.rb +3 -3
  62. data/test/populate_new_api.rb +304 -0
  63. data/test/{populate_test.rb → populate_old_api.rb} +83 -49
  64. data/test/populator_skip_test.rb +9 -9
  65. data/test/prepopulator_test.rb +8 -9
  66. data/test/read_only_test.rb +12 -1
  67. data/test/readable_test.rb +7 -7
  68. data/test/reform_new_api.rb +204 -0
  69. data/test/{reform_test.rb → reform_old_api.rb} +30 -51
  70. data/test/save_new_api.rb +101 -0
  71. data/test/{save_test.rb → save_old_api.rb} +32 -20
  72. data/test/setup_test.rb +8 -8
  73. data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
  74. data/test/skip_if_old_api.rb +92 -0
  75. data/test/skip_setter_and_getter_test.rb +3 -4
  76. data/test/test_helper.rb +25 -14
  77. data/test/validate_new_api.rb +453 -0
  78. data/test/{validate_test.rb → validate_old_api.rb} +59 -69
  79. data/test/validation/dry_validation_new_api.rb +836 -0
  80. data/test/validation/dry_validation_old_api.rb +772 -0
  81. data/test/validation/result_test.rb +77 -0
  82. data/test/validation_library_provided_test.rb +16 -0
  83. data/test/virtual_test.rb +47 -7
  84. data/test/writeable_test.rb +35 -6
  85. metadata +101 -72
  86. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  87. data/lib/reform/contract/errors.rb +0 -43
  88. data/lib/reform/form/mongoid.rb +0 -37
  89. data/lib/reform/form/orm.rb +0 -26
  90. data/lib/reform/mongoid.rb +0 -4
  91. data/test/deprecation_test.rb +0 -27
  92. data/test/errors_test.rb +0 -165
  93. data/test/inherit_test.rb +0 -119
  94. data/test/readonly_test.rb +0 -14
  95. data/test/validation/dry_test.rb +0 -60
  96. data/test/validation/dry_validation_test.rb +0 -352
  97. data/test/validation/errors.yml +0 -4
@@ -1,14 +1,16 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class SkipIfTest < BaseTest
4
+ let(:hit) { Song.new }
5
+ let(:album) { Album.new(nil, hit, [], nil) }
4
6
 
5
- class AlbumForm < Reform::Form
7
+ class AlbumForm < TestForm
6
8
  property :title
7
9
 
8
- property :hit, skip_if: lambda { |options| options[:fragment]["title"]=="" } do
10
+ property :hit, skip_if: ->(options) { options[:fragment]["title"] == "" } do
9
11
  property :title
10
12
  validation do
11
- key(:title).required
13
+ params { required(:title).filled }
12
14
  end
13
15
  end
14
16
 
@@ -21,10 +23,6 @@ class SkipIfTest < BaseTest
21
23
  end
22
24
  end
23
25
 
24
-
25
- let (:hit) { Song.new }
26
- let (:album) { Album.new(nil, hit, [], nil) }
27
-
28
26
  # deserializes when present.
29
27
  it do
30
28
  form = AlbumForm.new(album)
@@ -36,7 +34,7 @@ class SkipIfTest < BaseTest
36
34
  it do
37
35
  form = AlbumForm.new(Album.new)
38
36
  form.validate("hit" => {"title" => ""}).must_equal true
39
- form.hit.must_equal nil # hit hasn't been deserialised.
37
+ assert_nil form.hit # hit hasn't been deserialised.
40
38
  end
41
39
 
42
40
  # skips deserialization when not present.
@@ -50,7 +48,7 @@ end
50
48
 
51
49
  class SkipIfAllBlankTest < BaseTest
52
50
  # skip_if: :all_blank"
53
- class AlbumForm < Reform::Form
51
+ class AlbumForm < TestForm
54
52
  collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
55
53
  property :title
56
54
  property :length
@@ -60,15 +58,28 @@ class SkipIfAllBlankTest < BaseTest
60
58
  # create only one object.
61
59
  it do
62
60
  form = AlbumForm.new(OpenStruct.new(songs: []))
63
- form.validate("songs" => [{"title"=>"Apathy"}, {"title"=>"", "length" => ""}]).must_equal true
61
+ form.validate("songs" => [{"title" => "Apathy"}, {"title" => "", "length" => ""}]).must_equal true
64
62
  form.songs.size.must_equal 1
65
63
  form.songs[0].title.must_equal "Apathy"
66
64
  end
67
65
 
68
66
  it do
69
67
  form = AlbumForm.new(OpenStruct.new(songs: []))
70
- form.validate("songs" => [{"title"=>"", "length" => ""}, {"title"=>"Apathy"}]).must_equal true
68
+ form.validate("songs" => [{"title" => "", "length" => ""}, {"title" => "Apathy"}]).must_equal true
71
69
  form.songs.size.must_equal 1
72
70
  form.songs[0].title.must_equal "Apathy"
73
71
  end
74
72
  end
73
+
74
+ class InvalidOptionsCombinationTest < BaseTest
75
+ it do
76
+ assert_raises(Reform::Form::InvalidOptionsCombinationError) do
77
+ class AlbumForm < TestForm
78
+ collection :songs, skip_if: :all_blank, populator: -> {} do
79
+ property :title
80
+ property :length
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,92 @@
1
+ require "test_helper"
2
+
3
+ class SkipIfTest < BaseTest
4
+ let(:hit) { Song.new }
5
+ let(:album) { Album.new(nil, hit, [], nil) }
6
+
7
+ class AlbumForm < TestForm
8
+ property :title
9
+
10
+ property :hit, skip_if: ->(options) { options[:fragment]["title"] == "" } do
11
+ property :title
12
+ validation do
13
+ required(:title).filled
14
+ end
15
+ end
16
+
17
+ collection :songs, skip_if: :skip_song?, populate_if_empty: BaseTest::Song do
18
+ property :title
19
+ end
20
+
21
+ def skip_song?(options)
22
+ options[:fragment]["title"].nil?
23
+ end
24
+ end
25
+
26
+ # deserializes when present.
27
+ it do
28
+ form = AlbumForm.new(album)
29
+ form.validate("hit" => {"title" => "Altar Of Sacrifice"}).must_equal true
30
+ form.hit.title.must_equal "Altar Of Sacrifice"
31
+ end
32
+
33
+ # skips deserialization when not present.
34
+ it do
35
+ form = AlbumForm.new(Album.new)
36
+ form.validate("hit" => {"title" => ""}).must_equal true
37
+ assert_nil form.hit # hit hasn't been deserialised.
38
+ end
39
+
40
+ # skips deserialization when not present.
41
+ it do
42
+ form = AlbumForm.new(Album.new(nil, nil, []))
43
+ form.validate("songs" => [{"title" => "Waste Of Breath"}, {"title" => nil}]).must_equal true
44
+ form.songs.size.must_equal 1
45
+ form.songs[0].title.must_equal "Waste Of Breath"
46
+ end
47
+ end
48
+
49
+ class SkipIfAllBlankTest < BaseTest
50
+ # skip_if: :all_blank"
51
+ class AlbumForm < TestForm
52
+ collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
53
+ property :title
54
+ property :length
55
+ end
56
+ end
57
+
58
+ # create only one object.
59
+ it do
60
+ form = AlbumForm.new(OpenStruct.new(songs: []))
61
+ form.validate("songs" => [{"title" => "Apathy"}, {"title" => "", "length" => ""}]).must_equal true
62
+ form.songs.size.must_equal 1
63
+ form.songs[0].title.must_equal "Apathy"
64
+ end
65
+
66
+ it do
67
+ form = AlbumForm.new(OpenStruct.new(songs: []))
68
+ form.validate("songs" => [{"title" => "", "length" => ""}, {"title" => "Apathy"}]).must_equal true
69
+ form.songs.size.must_equal 1
70
+ form.songs[0].title.must_equal "Apathy"
71
+ end
72
+
73
+ it do
74
+ form = AlbumForm.new(OpenStruct.new(songs: []))
75
+ form.validate(:songs => [{:title=>"", :length => ""}, {:title=>"Apathy"}]).must_equal true
76
+ form.songs.size.must_equal 1
77
+ form.songs[0].title.must_equal "Apathy"
78
+ end
79
+ end
80
+
81
+ class InvalidOptionsCombinationTest < BaseTest
82
+ it do
83
+ assert_raises(Reform::Form::InvalidOptionsCombinationError) do
84
+ class AlbumForm < TestForm
85
+ collection :songs, skip_if: :all_blank, populator: -> {} do
86
+ property :title
87
+ property :length
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -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
@@ -31,8 +31,7 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
31
31
  end
32
32
  end
33
33
 
34
- let (:artist) { Artist.new("Bad Religion") }
35
-
34
+ let(:artist) { Artist.new("Bad Religion") }
36
35
 
37
36
  it do
38
37
  album = Album.new("Greatest Hits", artist)
@@ -51,4 +50,4 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
51
50
  album.title.must_equal "ecnatstsiseR" # setter called, but not getter.
52
51
  album.artist.name.must_equal "Greg Graffi"
53
52
  end
54
- end
53
+ end
@@ -1,11 +1,32 @@
1
1
  require "reform"
2
- require 'minitest/autorun'
2
+ require "minitest/autorun"
3
3
  require "representable/debug"
4
4
  require "declarative/testing"
5
5
  require "pp"
6
+ require "pry-byebug"
7
+
8
+ require "reform/form/dry"
9
+
10
+ # setup test classes so we can test without dry being included
11
+ class TestForm < Reform::Form
12
+ feature Reform::Form::Dry
13
+ end
14
+
15
+ class TestContract < Reform::Contract
16
+ feature Reform::Form::Dry
17
+ end
18
+
19
+ module Types
20
+ DRY_MODULE = Gem::Version.new(Dry::Types::VERSION) < Gem::Version.new("0.15.0") ? Dry::Types.module : Dry.Types()
21
+ include DRY_MODULE
22
+ end
23
+
24
+ DRY_TYPES_VERSION = Gem::Version.new(Dry::Types::VERSION)
25
+ DRY_TYPES_CONSTANT = DRY_TYPES_VERSION < Gem::Version.new("0.13.0") ? Types::Form : Types::Params
26
+ DRY_TYPES_INT_CONSTANT = DRY_TYPES_VERSION < Gem::Version.new("0.13.0") ? Types::Form::Int : Types::Params::Integer
6
27
 
7
28
  class BaseTest < MiniTest::Spec
8
- class AlbumForm < Reform::Form
29
+ class AlbumForm < TestForm
9
30
  property :title
10
31
 
11
32
  property :hit do
@@ -17,14 +38,13 @@ class BaseTest < MiniTest::Spec
17
38
  end
18
39
  end
19
40
 
20
- Song = Struct.new(:title, :length)
41
+ Song = Struct.new(:title, :length, :rating)
21
42
  Album = Struct.new(:title, :hit, :songs, :band)
22
43
  Band = Struct.new(:label)
23
44
  Label = Struct.new(:name)
24
45
  Length = Struct.new(:minutes, :seconds)
25
46
 
26
-
27
- let (:hit) { Song.new("Roxanne") }
47
+ let(:hit) { Song.new("Roxanne") }
28
48
  end
29
49
 
30
50
  MiniTest::Spec.class_eval do
@@ -38,12 +58,3 @@ MiniTest::Spec.class_eval do
38
58
  end
39
59
  end
40
60
  end
41
-
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
@@ -0,0 +1,453 @@
1
+ require "test_helper"
2
+
3
+ class ContractValidateTest < 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 < TestContract
9
+ property :name
10
+ validation do
11
+ params { required(:name).filled }
12
+ end
13
+
14
+ collection :songs do
15
+ property :title
16
+ validation do
17
+ params { required(:title).filled }
18
+ end
19
+
20
+ property :composer do
21
+ validation do
22
+ params { required(:name).filled }
23
+ end
24
+ property :name
25
+ end
26
+ end
27
+
28
+ property :artist do
29
+ property :name
30
+ end
31
+ end
32
+
33
+ let(:song) { Song.new("Broken") }
34
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
35
+ let(:composer) { Artist.new("Greg Graffin") }
36
+ let(:artist) { Artist.new("Bad Religion") }
37
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
38
+
39
+ let(:form) { AlbumForm.new(album) }
40
+
41
+ # valid
42
+ it do
43
+ form.validate.must_equal true
44
+ form.errors.messages.inspect.must_equal "{}"
45
+ end
46
+
47
+ # invalid
48
+ it do
49
+ album.songs[1].composer.name = nil
50
+ album.name = nil
51
+
52
+ form.validate.must_equal false
53
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"]}"
54
+ end
55
+ end
56
+
57
+ # no configuration results in "sync" (formerly known as parse_strategy: :sync).
58
+ class ValidateWithoutConfigurationTest < MiniTest::Spec
59
+ Song = Struct.new(:title, :album, :composer)
60
+ Album = Struct.new(:name, :songs, :artist)
61
+ Artist = Struct.new(:name)
62
+
63
+ class AlbumForm < TestForm
64
+ property :name
65
+ validation do
66
+ params { required(:name).filled }
67
+ end
68
+
69
+ collection :songs do
70
+ property :title
71
+ validation do
72
+ params { required(:title).filled }
73
+ end
74
+
75
+ property :composer do
76
+ property :name
77
+ validation do
78
+ params { required(:name).filled }
79
+ end
80
+ end
81
+ end
82
+
83
+ property :artist do
84
+ property :name
85
+ end
86
+ end
87
+
88
+ let(:song) { Song.new("Broken") }
89
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
90
+ let(:composer) { Artist.new("Greg Graffin") }
91
+ let(:artist) { Artist.new("Bad Religion") }
92
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
93
+
94
+ let(:form) { AlbumForm.new(album) }
95
+
96
+ # valid.
97
+ it do
98
+ object_ids = {
99
+ song: form.songs[0].object_id, song_with_composer: form.songs[1].object_id,
100
+ artist: form.artist.object_id, composer: form.songs[1].composer.object_id
101
+ }
102
+
103
+ form.validate(
104
+ "name" => "Best Of",
105
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => "Sting"}}],
106
+ "artist" => {"name" => "The Police"}
107
+ ).must_equal true
108
+
109
+ form.errors.messages.inspect.must_equal "{}"
110
+
111
+ # form has updated.
112
+ form.name.must_equal "Best Of"
113
+ form.songs[0].title.must_equal "Fallout"
114
+ form.songs[1].title.must_equal "Roxanne"
115
+ form.songs[1].composer.name.must_equal "Sting"
116
+ form.artist.name.must_equal "The Police"
117
+
118
+ # objects are still the same.
119
+ form.songs[0].object_id.must_equal object_ids[:song]
120
+ form.songs[1].object_id.must_equal object_ids[:song_with_composer]
121
+ form.songs[1].composer.object_id.must_equal object_ids[:composer]
122
+ form.artist.object_id.must_equal object_ids[:artist]
123
+
124
+ # model has not changed, yet.
125
+ album.name.must_equal "The Dissent Of Man"
126
+ album.songs[0].title.must_equal "Broken"
127
+ album.songs[1].title.must_equal "Resist Stance"
128
+ album.songs[1].composer.name.must_equal "Greg Graffin"
129
+ album.artist.name.must_equal "Bad Religion"
130
+ end
131
+
132
+ # with symbols.
133
+ it do
134
+ form.validate(
135
+ name: "Best Of",
136
+ songs: [{title: "The X-Creep"}, {title: "Trudging", composer: {name: "SNFU"}}],
137
+ artist: {name: "The Police"}
138
+ ).must_equal true
139
+
140
+ form.name.must_equal "Best Of"
141
+ form.songs[0].title.must_equal "The X-Creep"
142
+ form.songs[1].title.must_equal "Trudging"
143
+ form.songs[1].composer.name.must_equal "SNFU"
144
+ form.artist.name.must_equal "The Police"
145
+ end
146
+
147
+ # throws exception when no populators.
148
+ it do
149
+ album = Album.new("The Dissent Of Man", [])
150
+
151
+ assert_raises RuntimeError do
152
+ AlbumForm.new(album).validate(songs: {title: "Resist-Stance"})
153
+ end
154
+ end
155
+ end
156
+
157
+ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
158
+ Song = Struct.new(:title, :album, :composer)
159
+ Album = Struct.new(:name, :songs, :artist)
160
+ Artist = Struct.new(:name)
161
+
162
+ class AlbumForm < TestForm
163
+ property :name
164
+ validation do
165
+ params { required(:name).filled }
166
+ end
167
+
168
+ collection :songs,
169
+ internal_populator: ->(input, options) {
170
+ collection = options[:represented].songs
171
+ (item = collection[options[:index]]) ? item : collection.insert(options[:index], Song.new)
172
+ } do
173
+ property :title
174
+ validation do
175
+ params { required(:title).filled }
176
+ end
177
+
178
+ property :composer, internal_populator: ->(input, options) { (item = options[:represented].composer) ? item : Artist.new } do
179
+ property :name
180
+ validation do
181
+ params { required(:name).filled }
182
+ end
183
+ end
184
+ end
185
+
186
+ property :artist, internal_populator: ->(input, options) { (item = options[:represented].artist) ? item : Artist.new } do
187
+ property :name
188
+ validation do
189
+ params { required(:name).filled }
190
+ end
191
+ end
192
+ end
193
+
194
+ let(:song) { Song.new("Broken") }
195
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
196
+ let(:composer) { Artist.new("Greg Graffin") }
197
+ let(:artist) { Artist.new("Bad Religion") }
198
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
199
+
200
+ let(:form) { AlbumForm.new(album) }
201
+
202
+ # valid.
203
+ it("xxx") do
204
+ form.validate(
205
+ "name" => "Best Of",
206
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => "Sting"}}],
207
+ "artist" => {"name" => "The Police"},
208
+ ).must_equal true
209
+
210
+ form.errors.messages.inspect.must_equal "{}"
211
+
212
+ # form has updated.
213
+ form.name.must_equal "Best Of"
214
+ form.songs[0].title.must_equal "Fallout"
215
+ form.songs[1].title.must_equal "Roxanne"
216
+ form.songs[1].composer.name.must_equal "Sting"
217
+ form.artist.name.must_equal "The Police"
218
+
219
+ # model has not changed, yet.
220
+ album.name.must_equal "The Dissent Of Man"
221
+ album.songs[0].title.must_equal "Broken"
222
+ album.songs[1].title.must_equal "Resist Stance"
223
+ album.songs[1].composer.name.must_equal "Greg Graffin"
224
+ album.artist.name.must_equal "Bad Religion"
225
+ end
226
+
227
+ # invalid.
228
+ it do
229
+ form.validate(
230
+ "name" => "",
231
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => ""}}],
232
+ "artist" => {"name" => ""},
233
+ ).must_equal false
234
+
235
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"], :\"artist.name\"=>[\"must be filled\"]}"
236
+ end
237
+
238
+ # adding to collection via :instance.
239
+ # valid.
240
+ it do
241
+ form.validate(
242
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}, {"title" => "Rime Of The Ancient Mariner"}],
243
+ ).must_equal true
244
+
245
+ form.errors.messages.inspect.must_equal "{}"
246
+
247
+ # form has updated.
248
+ form.name.must_equal "The Dissent Of Man"
249
+ form.songs[0].title.must_equal "Fallout"
250
+ form.songs[1].title.must_equal "Roxanne"
251
+ form.songs[1].composer.name.must_equal "Greg Graffin"
252
+ form.songs[1].title.must_equal "Roxanne"
253
+ form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
254
+ form.songs.size.must_equal 3
255
+ form.artist.name.must_equal "Bad Religion"
256
+
257
+ # model has not changed, yet.
258
+ album.name.must_equal "The Dissent Of Man"
259
+ album.songs[0].title.must_equal "Broken"
260
+ album.songs[1].title.must_equal "Resist Stance"
261
+ album.songs[1].composer.name.must_equal "Greg Graffin"
262
+ album.songs.size.must_equal 2
263
+ album.artist.name.must_equal "Bad Religion"
264
+ end
265
+
266
+ # allow writeable: false even in the deserializer.
267
+ class SongForm < TestForm
268
+ property :title, deserializer: {writeable: false}
269
+ end
270
+
271
+ it do
272
+ form = SongForm.new(song = Song.new)
273
+ form.validate("title" => "Ignore me!")
274
+ assert_nil form.title
275
+ form.title = "Unopened"
276
+ form.sync # only the deserializer is marked as not-writeable.
277
+ song.title.must_equal "Unopened"
278
+ end
279
+ end
280
+
281
+ class ValidateUsingDifferentFormObject < MiniTest::Spec
282
+ Album = Struct.new(:name)
283
+
284
+ class AlbumForm < TestForm
285
+ property :name
286
+
287
+ validation do
288
+ option :form
289
+
290
+ params { required(:name).filled(:str?) }
291
+
292
+ rule(:name) do
293
+ if form.name == 'invalid'
294
+ key.failure('Invalid name')
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ let(:album) { Album.new }
301
+
302
+ let(:form) { AlbumForm.new(album) }
303
+
304
+ it 'sets name correctly' do
305
+ assert form.validate(name: 'valid')
306
+ form.sync
307
+ assert_equal form.model.name, 'valid'
308
+ end
309
+
310
+ it 'validates presence of name' do
311
+ refute form.validate(name: nil)
312
+ assert_equal form.errors[:name], ["must be filled"]
313
+ end
314
+
315
+ it 'validates type of name' do
316
+ refute form.validate(name: 1)
317
+ assert_equal form.errors[:name], ["must be a string"]
318
+ end
319
+
320
+ it 'when name is invalid' do
321
+ refute form.validate(name: 'invalid')
322
+ assert_equal form.errors[:name], ["Invalid name"]
323
+ end
324
+ end
325
+
326
+ # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/trailblazer/reform/pull/104
327
+ # # describe ":populator with :empty" do
328
+ # # let(:form) {
329
+ # # Class.new(Reform::Form) do
330
+ # # collection :songs, :empty => true, :populator => lambda { |fragment, index, args|
331
+ # # songs[index] = args.binding[:form].new(Song.new)
332
+ # # } do
333
+ # # property :title
334
+ # # end
335
+ # # end
336
+ # # }
337
+
338
+ # # let(:params) {
339
+ # # {
340
+ # # "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
341
+ # # }
342
+ # # }
343
+
344
+ # # subject { form.new(Album.new("Hits", [], [])) }
345
+
346
+ # # before { subject.validate(params) }
347
+
348
+ # # it { subject.songs[0].title.must_equal "Fallout" }
349
+ # # it { subject.songs[1].title.must_equal "Roxanne" }
350
+ # # end
351
+
352
+ # # test cardinalities.
353
+ # describe "with empty collection and cardinality" do
354
+ # let(:album) { Album.new }
355
+
356
+ # subject { Class.new(Reform::Form) do
357
+ # include Reform::Form::ActiveModel
358
+ # model :album
359
+
360
+ # collection :songs do
361
+ # property :title
362
+ # end
363
+
364
+ # property :hit do
365
+ # property :title
366
+ # end
367
+
368
+ # validates :songs, :length => {:minimum => 1}
369
+ # validates :hit, :presence => true
370
+ # end.new(album) }
371
+
372
+ # describe "invalid" do
373
+ # before { subject.validate({}).must_equal false }
374
+
375
+ # it do
376
+ # # ensure that only hit and songs keys are present
377
+ # subject.errors.messages.keys.sort.must_equal([:hit, :songs])
378
+ # # validate content of hit and songs keys
379
+ # subject.errors.messages[:hit].must_equal(["must be filled"])
380
+ # subject.errors.messages[:songs].first.must_match(/\Ais too short \(minimum is 1 characters?\)\z/)
381
+ # end
382
+ # end
383
+
384
+ # describe "valid" do
385
+ # let(:album) { Album.new(nil, Song.new, [Song.new("Urban Myth")]) }
386
+
387
+ # before {
388
+ # subject.validate({"songs" => [{"title"=>"Daddy, Brother, Lover, Little Boy"}], "hit" => {"title"=>"The Horse"}}).
389
+ # must_equal true
390
+ # }
391
+
392
+ # it { subject.errors.messages.must_equal({}) }
393
+ # end
394
+ # end
395
+
396
+ # # providing manual validator method allows accessing form's API.
397
+ # describe "with ::validate" do
398
+ # let(:form) {
399
+ # Class.new(Reform::Form) do
400
+ # property :title
401
+
402
+ # validate :title?
403
+
404
+ # def title?
405
+ # errors.add :title, "not lowercase" if title == "Fallout"
406
+ # end
407
+ # end
408
+ # }
409
+
410
+ # let(:params) { {"title" => "Fallout"} }
411
+ # let(:song) { Song.new("Englishman") }
412
+
413
+ # subject { form.new(song) }
414
+
415
+ # before { @res = subject.validate(params) }
416
+
417
+ # it { @res.must_equal false }
418
+ # it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
419
+ # end
420
+
421
+ # # overriding the reader for a nested form should only be considered when rendering.
422
+ # describe "with overridden reader for nested form" do
423
+ # let(:form) {
424
+ # Class.new(Reform::Form) do
425
+ # property :band, :populate_if_empty => lambda { |*| Band.new } do
426
+ # property :label
427
+ # end
428
+
429
+ # collection :songs, :populate_if_empty => lambda { |*| Song.new } do
430
+ # property :title
431
+ # end
432
+
433
+ # def band
434
+ # raise "only call me when rendering the form!"
435
+ # end
436
+
437
+ # def songs
438
+ # raise "only call me when rendering the form!"
439
+ # end
440
+ # end.new(album)
441
+ # }
442
+
443
+ # let(:album) { Album.new }
444
+
445
+ # # don't use #artist when validating!
446
+ # it do
447
+ # form.validate("band" => {"label" => "Hellcat"}, "songs" => [{"title" => "Stand Your Ground"}, {"title" => "Otherside"}])
448
+ # form.sync
449
+ # album.band.label.must_equal "Hellcat"
450
+ # album.songs.first.title.must_equal "Stand Your Ground"
451
+ # end
452
+ # end
453
+ # end