reform 2.3.2 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +17 -0
  3. data/.gitignore +1 -1
  4. data/CHANGES.md +23 -0
  5. data/Gemfile +1 -1
  6. data/LICENSE.txt +1 -1
  7. data/README.md +5 -5
  8. data/Rakefile +1 -12
  9. data/lib/reform/contract/validate.rb +1 -1
  10. data/lib/reform/form/dry.rb +47 -9
  11. data/lib/reform/form/populator.rb +13 -3
  12. data/lib/reform/form/prepopulate.rb +1 -1
  13. data/lib/reform/form/validate.rb +3 -3
  14. data/lib/reform/validation/groups.rb +0 -1
  15. data/lib/reform/version.rb +1 -1
  16. data/reform.gemspec +2 -2
  17. data/test/call_test.rb +23 -0
  18. data/test/changed_test.rb +6 -6
  19. data/test/coercion_test.rb +17 -17
  20. data/test/{composition_new_api.rb → composition_test.rb} +27 -28
  21. data/test/{contract_new_api.rb → contract_test.rb} +8 -8
  22. data/test/default_test.rb +2 -2
  23. data/test/deserialize_test.rb +8 -8
  24. data/test/docs/validation_test.rb +134 -0
  25. data/test/{errors_new_api.rb → errors_test.rb} +41 -41
  26. data/test/feature_test.rb +2 -2
  27. data/test/fixtures/dry_error_messages.yml +64 -54
  28. data/test/{form_option_new_api.rb → form_option_test.rb} +1 -1
  29. data/test/{form_new_api.rb → form_test.rb} +3 -3
  30. data/test/from_test.rb +10 -10
  31. data/test/{inherit_new_api.rb → inherit_test.rb} +17 -17
  32. data/test/{module_new_api.rb → module_test.rb} +10 -10
  33. data/test/parse_option_test.rb +7 -7
  34. data/test/parse_pipeline_test.rb +1 -1
  35. data/test/{populate_new_api.rb → populate_test.rb} +136 -53
  36. data/test/populator_skip_test.rb +2 -2
  37. data/test/prepopulator_test.rb +16 -16
  38. data/test/read_only_test.rb +2 -2
  39. data/test/readable_test.rb +3 -3
  40. data/test/{reform_new_api.rb → reform_test.rb} +19 -19
  41. data/test/{save_new_api.rb → save_test.rb} +4 -4
  42. data/test/setup_test.rb +9 -9
  43. data/test/{skip_if_new_api.rb → skip_if_test.rb} +12 -12
  44. data/test/skip_setter_and_getter_test.rb +6 -6
  45. data/test/test_helper.rb +5 -6
  46. data/test/{validate_new_api.rb → validate_test.rb} +65 -78
  47. data/test/validation/{dry_validation_new_api.rb → dry_validation_test.rb} +128 -128
  48. data/test/validation/result_test.rb +14 -14
  49. data/test/virtual_test.rb +7 -7
  50. data/test/writeable_test.rb +8 -8
  51. metadata +42 -75
  52. data/.travis.yml +0 -16
  53. data/Appraisals +0 -8
  54. data/gemfiles/0.13.0.gemfile +0 -8
  55. data/gemfiles/1.5.0.gemfile +0 -9
  56. data/lib/reform/form/dry/new_api.rb +0 -47
  57. data/lib/reform/form/dry/old_api.rb +0 -61
  58. data/test/call_new_api.rb +0 -23
  59. data/test/call_old_api.rb +0 -23
  60. data/test/composition_old_api.rb +0 -184
  61. data/test/contract_old_api.rb +0 -77
  62. data/test/errors_old_api.rb +0 -230
  63. data/test/fixtures/dry_new_api_error_messages.yml +0 -104
  64. data/test/form_old_api.rb +0 -57
  65. data/test/form_option_old_api.rb +0 -24
  66. data/test/inherit_old_api.rb +0 -105
  67. data/test/module_old_api.rb +0 -146
  68. data/test/populate_old_api.rb +0 -304
  69. data/test/reform_old_api.rb +0 -202
  70. data/test/save_old_api.rb +0 -101
  71. data/test/skip_if_old_api.rb +0 -92
  72. data/test/validate_old_api.rb +0 -410
  73. data/test/validation/dry_validation_old_api.rb +0 -772
data/test/save_old_api.rb DELETED
@@ -1,101 +0,0 @@
1
- require "test_helper"
2
-
3
- class SaveTest < BaseTest
4
- Song = Struct.new(:title, :album, :composer)
5
- Album = Struct.new(:name, :songs, :artist)
6
- Artist = Struct.new(:name)
7
-
8
- class AlbumForm < TestForm
9
- property :name
10
- validation do
11
- required(:name).filled
12
- end
13
-
14
- collection :songs do
15
- property :title
16
- validation do
17
- required(:title).filled
18
- end
19
-
20
- property :composer do
21
- property :name
22
- validation do
23
- required(:name).filled
24
- end
25
- end
26
- end
27
-
28
- property :artist, save: false do
29
- property :name
30
- end
31
- end
32
-
33
- module Saveable
34
- def save
35
- @saved = true
36
- end
37
-
38
- def saved?
39
- defined?(@saved) && @saved
40
- end
41
- end
42
-
43
- let(:song) { Song.new("Broken").extend(Saveable) }
44
- # let(:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
45
- let(:composer) { Artist.new("Greg Graffin").extend(Saveable) }
46
- let(:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
47
- let(:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
48
-
49
- let(:form) { AlbumForm.new(album) }
50
-
51
- it do
52
- form.validate("songs" => [{"title" => "Fixed"}])
53
-
54
- form.save
55
-
56
- album.saved?.must_equal true
57
- album.songs[0].title.must_equal "Fixed"
58
- album.songs[0].saved?.must_equal true
59
- assert_nil album.artist.saved?
60
- end
61
-
62
- describe "#sync with block" do
63
- it do
64
- form = AlbumForm.new(Album.new("Greatest Hits"))
65
-
66
- form.validate(name: nil) # nil-out the title.
67
-
68
- nested_hash = nil
69
- form.sync do |hash|
70
- nested_hash = hash
71
- end
72
-
73
- nested_hash.must_equal({"name" => nil, "artist" => nil})
74
- end
75
- end
76
- end
77
-
78
- # class SaveWithDynamicOptionsTest < MiniTest::Spec
79
- # Song = Struct.new(:id, :title, :length) do
80
- # include Saveable
81
- # end
82
-
83
- # class SongForm < TestForm
84
- # property :title#, save: false
85
- # property :length, virtual: true
86
- # end
87
-
88
- # let(:song) { Song.new }
89
- # let(:form) { SongForm.new(song) }
90
-
91
- # # we have access to original input value and outside parameters.
92
- # it "xxx" do
93
- # form.validate("title" => "A Poor Man's Memory", "length" => 10)
94
- # length_seconds = 120
95
- # form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
96
-
97
- # song.title.must_equal "A Poor Man's Memory"
98
- # assert_nil song.length
99
- # song.id.must_equal "10: 120"
100
- # end
101
- # end
@@ -1,92 +0,0 @@
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
@@ -1,410 +0,0 @@
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
- required(:name).filled
12
- end
13
-
14
- collection :songs do
15
- property :title
16
- validation do
17
- required(:title).filled
18
- end
19
-
20
- property :composer do
21
- validation do
22
- 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
- required(:name).filled
67
- end
68
-
69
- collection :songs do
70
- property :title
71
- validation do
72
- required(:title).filled
73
- end
74
-
75
- property :composer do
76
- property :name
77
- validation do
78
- 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
- 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
- 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
- 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
- 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
- form.valid?.must_equal true
210
-
211
- form.errors.messages.inspect.must_equal "{}"
212
-
213
- # form has updated.
214
- form.name.must_equal "Best Of"
215
- form.songs[0].title.must_equal "Fallout"
216
- form.songs[1].title.must_equal "Roxanne"
217
- form.songs[1].composer.name.must_equal "Sting"
218
- form.artist.name.must_equal "The Police"
219
-
220
- # model has not changed, yet.
221
- album.name.must_equal "The Dissent Of Man"
222
- album.songs[0].title.must_equal "Broken"
223
- album.songs[1].title.must_equal "Resist Stance"
224
- album.songs[1].composer.name.must_equal "Greg Graffin"
225
- album.artist.name.must_equal "Bad Religion"
226
- end
227
-
228
- # invalid.
229
- it do
230
- form.validate(
231
- "name" => "",
232
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => ""}}],
233
- "artist" => {"name" => ""},
234
- ).must_equal false
235
- form.valid?.must_equal false
236
-
237
- form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"], :\"artist.name\"=>[\"must be filled\"]}"
238
- end
239
-
240
- # adding to collection via :instance.
241
- # valid.
242
- it do
243
- form.validate(
244
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}, {"title" => "Rime Of The Ancient Mariner"}],
245
- ).must_equal true
246
-
247
- form.errors.messages.inspect.must_equal "{}"
248
-
249
- # form has updated.
250
- form.name.must_equal "The Dissent Of Man"
251
- form.songs[0].title.must_equal "Fallout"
252
- form.songs[1].title.must_equal "Roxanne"
253
- form.songs[1].composer.name.must_equal "Greg Graffin"
254
- form.songs[1].title.must_equal "Roxanne"
255
- form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
256
- form.songs.size.must_equal 3
257
- form.artist.name.must_equal "Bad Religion"
258
-
259
- # model has not changed, yet.
260
- album.name.must_equal "The Dissent Of Man"
261
- album.songs[0].title.must_equal "Broken"
262
- album.songs[1].title.must_equal "Resist Stance"
263
- album.songs[1].composer.name.must_equal "Greg Graffin"
264
- album.songs.size.must_equal 2
265
- album.artist.name.must_equal "Bad Religion"
266
- end
267
-
268
- # allow writeable: false even in the deserializer.
269
- class SongForm < TestForm
270
- property :title, deserializer: {writeable: false}
271
- end
272
-
273
- it do
274
- form = SongForm.new(song = Song.new)
275
- form.validate("title" => "Ignore me!")
276
- assert_nil form.title
277
- form.title = "Unopened"
278
- form.sync # only the deserializer is marked as not-writeable.
279
- song.title.must_equal "Unopened"
280
- end
281
- end
282
-
283
- # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/trailblazer/reform/pull/104
284
- # # describe ":populator with :empty" do
285
- # # let(:form) {
286
- # # Class.new(Reform::Form) do
287
- # # collection :songs, :empty => true, :populator => lambda { |fragment, index, args|
288
- # # songs[index] = args.binding[:form].new(Song.new)
289
- # # } do
290
- # # property :title
291
- # # end
292
- # # end
293
- # # }
294
-
295
- # # let(:params) {
296
- # # {
297
- # # "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
298
- # # }
299
- # # }
300
-
301
- # # subject { form.new(Album.new("Hits", [], [])) }
302
-
303
- # # before { subject.validate(params) }
304
-
305
- # # it { subject.songs[0].title.must_equal "Fallout" }
306
- # # it { subject.songs[1].title.must_equal "Roxanne" }
307
- # # end
308
-
309
- # # test cardinalities.
310
- # describe "with empty collection and cardinality" do
311
- # let(:album) { Album.new }
312
-
313
- # subject { Class.new(Reform::Form) do
314
- # include Reform::Form::ActiveModel
315
- # model :album
316
-
317
- # collection :songs do
318
- # property :title
319
- # end
320
-
321
- # property :hit do
322
- # property :title
323
- # end
324
-
325
- # validates :songs, :length => {:minimum => 1}
326
- # validates :hit, :presence => true
327
- # end.new(album) }
328
-
329
- # describe "invalid" do
330
- # before { subject.validate({}).must_equal false }
331
-
332
- # it do
333
- # # ensure that only hit and songs keys are present
334
- # subject.errors.messages.keys.sort.must_equal([:hit, :songs])
335
- # # validate content of hit and songs keys
336
- # subject.errors.messages[:hit].must_equal(["must be filled"])
337
- # subject.errors.messages[:songs].first.must_match(/\Ais too short \(minimum is 1 characters?\)\z/)
338
- # end
339
- # end
340
-
341
- # describe "valid" do
342
- # let(:album) { Album.new(nil, Song.new, [Song.new("Urban Myth")]) }
343
-
344
- # before {
345
- # subject.validate({"songs" => [{"title"=>"Daddy, Brother, Lover, Little Boy"}], "hit" => {"title"=>"The Horse"}}).
346
- # must_equal true
347
- # }
348
-
349
- # it { subject.errors.messages.must_equal({}) }
350
- # end
351
- # end
352
-
353
- # # providing manual validator method allows accessing form's API.
354
- # describe "with ::validate" do
355
- # let(:form) {
356
- # Class.new(Reform::Form) do
357
- # property :title
358
-
359
- # validate :title?
360
-
361
- # def title?
362
- # errors.add :title, "not lowercase" if title == "Fallout"
363
- # end
364
- # end
365
- # }
366
-
367
- # let(:params) { {"title" => "Fallout"} }
368
- # let(:song) { Song.new("Englishman") }
369
-
370
- # subject { form.new(song) }
371
-
372
- # before { @res = subject.validate(params) }
373
-
374
- # it { @res.must_equal false }
375
- # it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
376
- # end
377
-
378
- # # overriding the reader for a nested form should only be considered when rendering.
379
- # describe "with overridden reader for nested form" do
380
- # let(:form) {
381
- # Class.new(Reform::Form) do
382
- # property :band, :populate_if_empty => lambda { |*| Band.new } do
383
- # property :label
384
- # end
385
-
386
- # collection :songs, :populate_if_empty => lambda { |*| Song.new } do
387
- # property :title
388
- # end
389
-
390
- # def band
391
- # raise "only call me when rendering the form!"
392
- # end
393
-
394
- # def songs
395
- # raise "only call me when rendering the form!"
396
- # end
397
- # end.new(album)
398
- # }
399
-
400
- # let(:album) { Album.new }
401
-
402
- # # don't use #artist when validating!
403
- # it do
404
- # form.validate("band" => {"label" => "Hellcat"}, "songs" => [{"title" => "Stand Your Ground"}, {"title" => "Otherside"}])
405
- # form.sync
406
- # album.band.label.must_equal "Hellcat"
407
- # album.songs.first.title.must_equal "Stand Your Ground"
408
- # end
409
- # end
410
- # end