reform 2.3.0.rc1 → 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 (65) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +7 -11
  4. data/CHANGES.md +43 -3
  5. data/Gemfile +2 -5
  6. data/ISSUE_TEMPLATE.md +1 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +7 -9
  9. data/Rakefile +6 -10
  10. data/lib/reform/contract.rb +7 -7
  11. data/lib/reform/contract/custom_error.rb +41 -0
  12. data/lib/reform/contract/validate.rb +10 -6
  13. data/lib/reform/errors.rb +27 -15
  14. data/lib/reform/form.rb +22 -11
  15. data/lib/reform/form/call.rb +1 -1
  16. data/lib/reform/form/composition.rb +2 -2
  17. data/lib/reform/form/dry.rb +22 -60
  18. data/lib/reform/form/dry/input_hash.rb +37 -0
  19. data/lib/reform/form/populator.rb +9 -11
  20. data/lib/reform/form/prepopulate.rb +3 -2
  21. data/lib/reform/form/validate.rb +19 -12
  22. data/lib/reform/result.rb +36 -9
  23. data/lib/reform/validation.rb +10 -8
  24. data/lib/reform/validation/groups.rb +2 -4
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +9 -9
  27. data/test/benchmarking.rb +10 -11
  28. data/test/call_test.rb +8 -8
  29. data/test/changed_test.rb +13 -13
  30. data/test/coercion_test.rb +56 -24
  31. data/test/composition_test.rb +49 -51
  32. data/test/contract/custom_error_test.rb +55 -0
  33. data/test/contract_test.rb +18 -18
  34. data/test/default_test.rb +3 -3
  35. data/test/deserialize_test.rb +14 -17
  36. data/test/docs/validation_test.rb +134 -0
  37. data/test/errors_test.rb +131 -86
  38. data/test/feature_test.rb +9 -11
  39. data/test/fixtures/dry_error_messages.yml +65 -52
  40. data/test/form_option_test.rb +3 -3
  41. data/test/form_test.rb +6 -6
  42. data/test/from_test.rb +17 -21
  43. data/test/inherit_test.rb +28 -35
  44. data/test/module_test.rb +23 -28
  45. data/test/parse_option_test.rb +12 -12
  46. data/test/parse_pipeline_test.rb +3 -3
  47. data/test/populate_test.rb +146 -93
  48. data/test/populator_skip_test.rb +3 -4
  49. data/test/prepopulator_test.rb +20 -21
  50. data/test/read_only_test.rb +12 -1
  51. data/test/readable_test.rb +7 -7
  52. data/test/reform_test.rb +38 -42
  53. data/test/save_test.rb +16 -19
  54. data/test/setup_test.rb +15 -15
  55. data/test/skip_if_test.rb +30 -19
  56. data/test/skip_setter_and_getter_test.rb +8 -9
  57. data/test/test_helper.rb +12 -5
  58. data/test/validate_test.rb +160 -140
  59. data/test/validation/dry_validation_test.rb +407 -236
  60. data/test/validation/result_test.rb +29 -31
  61. data/test/validation_library_provided_test.rb +3 -3
  62. data/test/virtual_test.rb +46 -6
  63. data/test/writeable_test.rb +13 -13
  64. metadata +32 -29
  65. data/test/readonly_test.rb +0 -14
data/test/module_test.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require "test_helper"
2
- require 'reform/form/coercion'
2
+ require "reform/form/coercion"
3
3
 
4
4
  class ModuleInclusionTest < MiniTest::Spec
5
5
  module BandPropertyForm
@@ -9,7 +9,7 @@ class ModuleInclusionTest < MiniTest::Spec
9
9
  property :title
10
10
 
11
11
  validation do
12
- required(:title).filled
12
+ params { required(:title).filled }
13
13
  end
14
14
 
15
15
  def id # gets mixed into Form, too.
@@ -22,11 +22,11 @@ class ModuleInclusionTest < MiniTest::Spec
22
22
  end
23
23
 
24
24
  validation do
25
- required(:band).filled
25
+ params { required(:band).filled }
26
26
  end
27
27
 
28
- include Dry::Types.module # allows using Types::* in module.
29
- property :cool, type: Form::Bool # test coercion.
28
+ include Dry.Types(default: :nominal) # allows using Types::* in module.
29
+ property :cool, type: Types::Params::Bool # test coercion.
30
30
  end
31
31
 
32
32
  # TODO: test if works, move stuff into inherit_schema!
@@ -36,15 +36,14 @@ class ModuleInclusionTest < MiniTest::Spec
36
36
  collection :airplays do
37
37
  property :station
38
38
  validation do
39
- required(:station).filled
39
+ params { required(:station).filled }
40
40
  end
41
41
  end
42
42
  validation do
43
- required(:airplays).filled
43
+ params { required(:airplays).filled }
44
44
  end
45
45
  end
46
46
 
47
-
48
47
  # test:
49
48
  # by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
50
49
  class HitForm < TestForm
@@ -58,31 +57,29 @@ class ModuleInclusionTest < MiniTest::Spec
58
57
  include BandPropertyForm
59
58
  end
60
59
 
61
-
62
- let (:song) { OpenStruct.new(:band => OpenStruct.new(:title => "Time Again")) }
60
+ let(:song) { OpenStruct.new(band: OpenStruct.new(title: "Time Again")) }
63
61
 
64
62
  # nested form from module is present and creates accessor.
65
- it { SongForm.new(song).band.title.must_equal "Time Again" }
63
+ it { assert_equal SongForm.new(song).band.title, "Time Again" }
66
64
 
67
65
  # methods from module get included.
68
- it { SongForm.new(song).id.must_equal 1 }
69
- it { SongForm.new(song).band.id.must_equal 2 }
66
+ it { assert_equal SongForm.new(song).id, 1 }
67
+ it { assert_equal SongForm.new(song).band.id, 2 }
70
68
 
71
69
  # validators get inherited.
72
70
  it do
73
71
  form = SongForm.new(OpenStruct.new)
74
72
  form.validate({})
75
- form.errors.messages.must_equal({:band=>["must be filled"]})
73
+ assert_equal form.errors.messages, band: ["must be filled"]
76
74
  end
77
75
 
78
76
  # coercion works
79
77
  it do
80
78
  form = SongForm.new(OpenStruct.new)
81
- form.validate({cool: "1"})
82
- form.cool.must_equal true
79
+ form.validate(cool: "1")
80
+ assert form.cool
83
81
  end
84
82
 
85
-
86
83
  # include a module into a module into a class :)
87
84
  module AlbumFormModule
88
85
  include Reform::Form::Module
@@ -90,7 +87,7 @@ class ModuleInclusionTest < MiniTest::Spec
90
87
 
91
88
  property :name
92
89
  validation do
93
- required(:name).filled
90
+ params { required(:name).filled }
94
91
  end
95
92
  end
96
93
 
@@ -98,21 +95,20 @@ class ModuleInclusionTest < MiniTest::Spec
98
95
  include AlbumFormModule
99
96
 
100
97
  # pp heritage
101
- property :band, :inherit => true do
98
+ property :band, inherit: true do
102
99
  property :label
103
100
  validation do
104
- required(:label).filled
101
+ params { required(:label).filled }
105
102
  end
106
103
  end
107
104
  end
108
105
 
109
106
  it do
110
- form = AlbumForm.new(OpenStruct.new(:band => OpenStruct.new))
111
- form.validate({"band" => {}})
112
- form.errors.messages.must_equal({:"band.title"=>["must be filled"], :"band.label"=>["must be filled"], :name=>["must be filled"]})
107
+ form = AlbumForm.new(OpenStruct.new(band: OpenStruct.new))
108
+ form.validate("band" => {})
109
+ assert_equal form.errors.messages, "band.title": ["must be filled"], "band.label": ["must be filled"], name: ["must be filled"]
113
110
  end
114
111
 
115
-
116
112
  describe "module with custom accessors" do
117
113
  module SongModule
118
114
  include Reform::Form::Module
@@ -131,12 +127,11 @@ class ModuleInclusionTest < MiniTest::Spec
131
127
  include SongModule
132
128
  end
133
129
 
134
- let (:song) { OpenStruct.new(id: 1, title: "Instant Mash") }
130
+ let(:song) { OpenStruct.new(id: 1, title: "Instant Mash") }
135
131
 
136
132
  it do
137
- IncludingSongForm.new(song).id.must_equal 1
138
- IncludingSongForm.new(song).title.must_equal "INSTANT MASH"
133
+ assert_equal IncludingSongForm.new(song).id, 1
134
+ assert_equal IncludingSongForm.new(song).title, "INSTANT MASH"
139
135
  end
140
136
  end
141
137
  end
142
-
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class ParseOptionTest < MiniTest::Spec
4
4
  Comment = Struct.new(:content, :user)
@@ -9,32 +9,32 @@ class ParseOptionTest < MiniTest::Spec
9
9
  property :user, parse: false
10
10
  end
11
11
 
12
- let (:current_user) { User.new("Peter") }
13
- let (:form) { CommentForm.new(Comment.new, user: current_user) }
12
+ let(:current_user) { User.new("Peter") }
13
+ let(:form) { CommentForm.new(Comment.new, user: current_user) }
14
14
 
15
15
  it do
16
- form.user.must_equal current_user
16
+ assert_equal form.user, current_user
17
17
 
18
18
  lorem = "Lorem ipsum dolor sit amet..."
19
19
  form.validate("content" => lorem, "user" => "not the current user")
20
20
 
21
- form.content.must_equal lorem
22
- form.user.must_equal current_user
21
+ assert_equal form.content, lorem
22
+ assert_equal form.user, current_user
23
23
  end
24
24
 
25
25
  describe "using ':parse' option doesn't override other ':deserialize' options" do
26
26
  class ArticleCommentForm < TestForm
27
27
  property :content
28
- property :article, deserializer: { instance: "Instance" }
29
- property :user, parse: false, deserializer: { instance: "Instance" }
28
+ property :article, deserializer: {instance: "Instance"}
29
+ property :user, parse: false, deserializer: {instance: "Instance"}
30
30
  end
31
31
 
32
32
  it do
33
- ArticleCommentForm.definitions.get(:user)[:deserializer][:writeable].must_equal false
34
- ArticleCommentForm.definitions.get(:user)[:deserializer][:instance].must_equal "Instance"
33
+ assert_equal ArticleCommentForm.definitions.get(:user)[:deserializer][:writeable], false
34
+ assert_equal ArticleCommentForm.definitions.get(:user)[:deserializer][:instance], "Instance"
35
35
 
36
- ArticleCommentForm.definitions.get(:article)[:deserializer][:writeable].must_equal true
37
- ArticleCommentForm.definitions.get(:article)[:deserializer][:instance].must_equal "Instance"
36
+ assert ArticleCommentForm.definitions.get(:article)[:deserializer][:writeable]
37
+ assert_equal ArticleCommentForm.definitions.get(:article)[:deserializer][:instance], "Instance"
38
38
  end
39
39
  end
40
40
  end
@@ -4,12 +4,12 @@ class ParsePipelineTest < MiniTest::Spec
4
4
  Album = Struct.new(:name)
5
5
 
6
6
  class AlbumForm < TestForm
7
- property :name, deserializer: { parse_pipeline: ->(input, options) { Representable::Pipeline[->(ipt, opts) { opts[:represented].name = ipt.inspect }] } }
7
+ property :name, deserializer: {parse_pipeline: ->(input, options) { Representable::Pipeline[->(ipt, opts) { opts[:represented].name = ipt.inspect }] }}
8
8
  end
9
9
 
10
10
  it "allows passing :parse_pipeline directly" do
11
11
  form = AlbumForm.new(Album.new)
12
12
  form.validate("name" => "Greatest Hits")
13
- form.name.must_equal "{\"name\"=>\"Greatest Hits\"}"
13
+ assert_equal form.name, "{\"name\"=>\"Greatest Hits\"}"
14
14
  end
15
- end
15
+ end
@@ -8,22 +8,22 @@ class PopulatorTest < MiniTest::Spec
8
8
  class AlbumForm < TestForm
9
9
  property :name, populator: ->(options) { self.name = options[:fragment].reverse }
10
10
  validation do
11
- required(:name).filled
11
+ params { required(:name).filled }
12
12
  end
13
13
 
14
14
  collection :songs,
15
- populator: ->(fragment:, model:, index:, **) {
16
- (item = model[index]) ? item : model.insert(index, Song.new) } do
17
-
15
+ populator: ->(fragment:, model:, index:, **) {
16
+ (item = model[index]) ? item : model.insert(index, Song.new)
17
+ } do
18
18
  property :title
19
19
  validation do
20
- required(:title).filled
20
+ params { required(:title).filled }
21
21
  end
22
22
 
23
- property :composer, populator: ->(options) { options[:model] || self.composer= Artist.new } do
23
+ property :composer, populator: ->(options) { options[:model] || self.composer = Artist.new } do
24
24
  property :name
25
25
  validation do
26
- required(:name).filled
26
+ params { required(:name).filled }
27
27
  end
28
28
  end
29
29
  end
@@ -35,20 +35,20 @@ class PopulatorTest < MiniTest::Spec
35
35
  end
36
36
  end
37
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) }
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
43
 
44
- let (:form) { AlbumForm.new(album) }
44
+ let(:form) { AlbumForm.new(album) }
45
45
 
46
46
  it "runs populator on scalar" do
47
47
  form.validate(
48
48
  "name" => "override me!"
49
49
  )
50
50
 
51
- form.name.must_equal "!em edirrevo"
51
+ assert_equal form.name, "!em edirrevo"
52
52
  end
53
53
 
54
54
  # changing existing property :artist.
@@ -60,7 +60,7 @@ class PopulatorTest < MiniTest::Spec
60
60
  "artist" => {"name" => "Marcus Miller"}
61
61
  )
62
62
 
63
- form.artist.model.object_id.must_equal old_id
63
+ assert_equal form.artist.model.object_id, old_id
64
64
  end
65
65
 
66
66
  # use populator for default value on scalars?
@@ -68,37 +68,36 @@ class PopulatorTest < MiniTest::Spec
68
68
  # adding to collection via :populator.
69
69
  # valid.
70
70
  it "yyy" do
71
- form.validate(
71
+ assert form.validate(
72
72
  "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
73
73
  {"title" => "Rime Of The Ancient Mariner"}, # new song.
74
74
  {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
75
- ).must_equal true
75
+ )
76
76
 
77
- form.errors.messages.inspect.must_equal "{}"
77
+ assert_equal form.errors.messages.inspect, "{}"
78
78
 
79
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"
80
+ assert_equal form.name, "The Dissent Of Man"
81
+ assert_equal form.songs[0].title, "Fallout"
82
+ assert_equal form.songs[1].title, "Roxanne"
83
+ assert_equal form.songs[1].composer.name, "Greg Graffin"
84
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"
85
+ form.songs[1].composer.model.is_a? Artist
93
86
 
87
+ assert_equal form.songs[1].title, "Roxanne"
88
+ assert_equal form.songs[2].title, "Rime Of The Ancient Mariner" # new song added.
89
+ assert_equal form.songs[3].title, "Re-Education"
90
+ assert_equal form.songs[3].composer.name, "Rise Against"
91
+ assert_equal form.songs.size, 4
92
+ assert_equal form.artist.name, "Bad Religion"
94
93
 
95
94
  # model has not changed, yet.
96
- album.name.must_equal "The Dissent Of Man"
97
- album.songs[0].title.must_equal "Broken"
98
- album.songs[1].title.must_equal "Resist Stance"
99
- album.songs[1].composer.name.must_equal "Greg Graffin"
100
- album.songs.size.must_equal 2
101
- album.artist.name.must_equal "Bad Religion"
95
+ assert_equal album.name, "The Dissent Of Man"
96
+ assert_equal album.songs[0].title, "Broken"
97
+ assert_equal album.songs[1].title, "Resist Stance"
98
+ assert_equal album.songs[1].composer.name, "Greg Graffin"
99
+ assert_equal album.songs.size, 2
100
+ assert_equal album.artist.name, "Bad Religion"
102
101
  end
103
102
  end
104
103
 
@@ -113,12 +112,12 @@ class PopulateWithMethodTest < Minitest::Spec
113
112
  end
114
113
  end
115
114
 
116
- let (:form) { AlbumForm.new(Album.new) }
115
+ let(:form) { AlbumForm.new(Album.new) }
117
116
 
118
117
  it "runs populator method" do
119
118
  form.validate("title" => "override me!")
120
119
 
121
- form.title.must_equal "!em edirrevo"
120
+ assert_equal form.title, "!em edirrevo"
122
121
  end
123
122
  end
124
123
 
@@ -137,12 +136,12 @@ class PopulateWithCallableTest < Minitest::Spec
137
136
  property :title, populator: TitlePopulator.new
138
137
  end
139
138
 
140
- let (:form) { AlbumForm.new(Album.new) }
139
+ let(:form) { AlbumForm.new(Album.new) }
141
140
 
142
141
  it "runs populator method" do
143
142
  form.validate("title" => "override me!")
144
143
 
145
- form.title.must_equal "!em edirrevo"
144
+ assert_equal form.title, "!em edirrevo"
146
145
  end
147
146
  end
148
147
 
@@ -157,12 +156,12 @@ class PopulateWithProcTest < Minitest::Spec
157
156
  property :title, populator: TitlePopulator
158
157
  end
159
158
 
160
- let (:form) { AlbumForm.new(Album.new) }
159
+ let(:form) { AlbumForm.new(Album.new) }
161
160
 
162
161
  it "runs populator method" do
163
162
  form.validate("title" => "override me!")
164
163
 
165
- form.title.must_equal "!em edirrevo"
164
+ assert_equal form.title, "!em edirrevo"
166
165
  end
167
166
  end
168
167
 
@@ -171,13 +170,11 @@ class PopulateIfEmptyTest < MiniTest::Spec
171
170
  Album = Struct.new(:name, :songs, :artist)
172
171
  Artist = Struct.new(:name)
173
172
 
174
- let (:song) { Song.new("Broken") }
175
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
176
- let (:composer) { Artist.new("Greg Graffin") }
177
- let (:artist) { Artist.new("Bad Religion") }
178
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
179
-
180
-
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) }
181
178
 
182
179
  class AlbumForm < TestForm
183
180
  property :name
@@ -187,13 +184,13 @@ class PopulateIfEmptyTest < MiniTest::Spec
187
184
 
188
185
  property :title
189
186
  validation do
190
- required(:title).filled
187
+ params { required(:title).filled }
191
188
  end
192
189
 
193
190
  property :composer, populate_if_empty: :populate_composer! do # lambda works, too. in form context.
194
191
  property :name
195
192
  validation do
196
- required(:name).filled
193
+ params { required(:name).filled }
197
194
  end
198
195
  end
199
196
 
@@ -203,52 +200,51 @@ class PopulateIfEmptyTest < MiniTest::Spec
203
200
  end
204
201
  end
205
202
 
206
- property :artist, populate_if_empty: lambda { |args| create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
203
+ property :artist, populate_if_empty: ->(args) { create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
207
204
  property :name
208
205
  end
209
206
 
210
- private
207
+ private
211
208
  class Sting < Artist
212
209
  attr_accessor :args
213
210
  end
214
211
  def create_artist(input, user_options)
215
- Sting.new.tap { |artist| artist.args=([input, user_options].to_s) }
212
+ Sting.new.tap { |artist| artist.args = ([input, user_options].to_s) }
216
213
  end
217
214
  end
218
215
 
219
- let (:form) { AlbumForm.new(album) }
216
+ let(:form) { AlbumForm.new(album) }
220
217
 
221
218
  it do
222
- form.songs.size.must_equal 2
219
+ assert_equal form.songs.size, 2
223
220
 
224
- form.validate(
225
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
221
+ assert form.validate(
222
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
226
223
  {"title" => "Rime Of The Ancient Mariner"}, # new song.
227
224
  {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
228
- ).must_equal true
225
+ )
229
226
 
230
- form.errors.messages.inspect.must_equal "{}"
227
+ assert_equal form.errors.messages.inspect, "{}"
231
228
 
232
229
  # form has updated.
233
- form.name.must_equal "The Dissent Of Man"
234
- form.songs[0].title.must_equal "Fallout"
235
- form.songs[1].title.must_equal "Roxanne"
236
- form.songs[1].composer.name.must_equal "Greg Graffin"
237
- form.songs[1].title.must_equal "Roxanne"
238
- form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
239
- form.songs[3].title.must_equal "Re-Education"
240
- form.songs[3].composer.name.must_equal "Rise Against"
241
- form.songs.size.must_equal 4
242
- form.artist.name.must_equal "Bad Religion"
243
-
230
+ assert_equal form.name, "The Dissent Of Man"
231
+ assert_equal form.songs[0].title, "Fallout"
232
+ assert_equal form.songs[1].title, "Roxanne"
233
+ assert_equal form.songs[1].composer.name, "Greg Graffin"
234
+ assert_equal form.songs[1].title, "Roxanne"
235
+ assert_equal form.songs[2].title, "Rime Of The Ancient Mariner" # new song added.
236
+ assert_equal form.songs[3].title, "Re-Education"
237
+ assert_equal form.songs[3].composer.name, "Rise Against"
238
+ assert_equal form.songs.size, 4
239
+ assert_equal form.artist.name, "Bad Religion"
244
240
 
245
241
  # model has not changed, yet.
246
- album.name.must_equal "The Dissent Of Man"
247
- album.songs[0].title.must_equal "Broken"
248
- album.songs[1].title.must_equal "Resist Stance"
249
- album.songs[1].composer.name.must_equal "Greg Graffin"
250
- album.songs.size.must_equal 2
251
- album.artist.name.must_equal "Bad Religion"
242
+ assert_equal album.name, "The Dissent Of Man"
243
+ assert_equal album.songs[0].title, "Broken"
244
+ assert_equal album.songs[1].title, "Resist Stance"
245
+ assert_equal album.songs[1].composer.name, "Greg Graffin"
246
+ assert_equal album.songs.size, 2
247
+ assert_equal album.artist.name, "Bad Religion"
252
248
  end
253
249
 
254
250
  # trigger artist populator. lambda calling form instance method.
@@ -256,26 +252,24 @@ class PopulateIfEmptyTest < MiniTest::Spec
256
252
  form = AlbumForm.new(album = Album.new)
257
253
  form.validate("artist" => {"name" => "From Autumn To Ashes"})
258
254
 
259
- form.artist.name.must_equal "From Autumn To Ashes"
255
+ assert_equal form.artist.name, "From Autumn To Ashes"
260
256
  # test lambda was executed in form context.
261
- form.artist.model.must_be_instance_of AlbumForm::Sting
257
+ assert form.artist.model.is_a? AlbumForm::Sting
262
258
  # test lambda block arguments.
263
- form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
259
+ assert_equal form.artist.model.args.to_s, "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
264
260
 
265
261
  assert_nil album.artist
266
262
  end
267
263
  end
268
264
 
269
-
270
265
  # delete songs while deserializing.
271
266
  class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
272
267
  Song = Struct.new(:title, :album, :composer)
273
268
  Album = Struct.new(:name, :songs, :artist)
274
269
 
275
- let (:song) { Song.new("Broken") }
276
- let (:song2) { Song.new("Resist Stance") }
277
- let (:album) { Album.new("The Dissent Of Man", [song, song2]) }
278
-
270
+ let(:song) { Song.new("Broken") }
271
+ let(:song2) { Song.new("Resist Stance") }
272
+ let(:album) { Album.new("The Dissent Of Man", [song, song2]) }
279
273
 
280
274
  class AlbumForm < TestForm
281
275
  property :name
@@ -285,7 +279,7 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
285
279
 
286
280
  property :title
287
281
  validation do
288
- required(:title).filled
282
+ params { required(:title).filled }
289
283
  end
290
284
  end
291
285
 
@@ -295,16 +289,75 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
295
289
  end
296
290
  end
297
291
 
298
- let (:form) { AlbumForm.new(album) }
292
+ let(:form) { AlbumForm.new(album) }
299
293
 
300
294
  it do
301
- form.validate(
302
- "songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
303
- ).must_equal true
295
+ assert form.validate(
296
+ "songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
297
+ )
298
+
299
+ assert_equal form.errors.messages.inspect, "{}"
304
300
 
305
- form.errors.messages.inspect.must_equal "{}"
301
+ assert_equal form.songs.size, 1
302
+ assert_equal form.songs[0].title, "Roxanne"
303
+ end
304
+ end
305
+
306
+ class PopulateWithFormKeyTest < MiniTest::Spec
307
+ Song = Struct.new(:title, :album, :composer)
308
+ Album = Struct.new(:name, :songs, :artist)
306
309
 
307
- form.songs.size.must_equal 1
308
- form.songs[0].title.must_equal "Roxanne"
310
+ let(:song) { Song.new('Broken') }
311
+ let(:song2) { Song.new('Resist Stance') }
312
+ let(:album) { Album.new('The Dissent Of Man', [song, song2]) }
313
+
314
+ class SongForm < TestForm
315
+ property :title
316
+
317
+ validation do
318
+ params { required(:title).filled }
319
+ end
320
+ end
321
+
322
+ class AlbumForm < TestForm
323
+ property :name
324
+
325
+ collection :songs, form: SongForm, populator: :populator!, model_identifier: :title
326
+
327
+ def populator!(fragment:, **)
328
+ item = songs.find { |song| song.title == fragment['title'] }
329
+ if item && fragment['delete'] == '1'
330
+ songs.delete(item)
331
+ return skip!
332
+ end
333
+ item || songs.append(Song.new)
334
+ end
335
+ end
336
+
337
+ let(:form) { AlbumForm.new(album) }
338
+
339
+ it do
340
+ assert_equal 2, form.songs.size
341
+
342
+ assert form.validate(
343
+ 'songs' => [
344
+ { 'title' => 'Broken' },
345
+ { 'title' => 'Resist Stance' },
346
+ { 'title' => 'Rime Of The Ancient Mariner' }
347
+ ]
348
+ )
349
+
350
+ assert_equal 3, form.songs.size
351
+
352
+ assert form.validate(
353
+ 'songs' => [
354
+ { 'title' => 'Broken', 'delete' => '1' },
355
+ { 'title' => 'Resist Stance' },
356
+ { 'title' => 'Rime Of The Ancient Mariner' }
357
+ ]
358
+ )
359
+ assert_equal 2, form.songs.size
360
+ assert_equal 'Resist Stance', form.songs.first.title
361
+ assert_equal 'Rime Of The Ancient Mariner', form.songs.last.title
309
362
  end
310
363
  end