reform 2.3.0.rc1 → 2.3.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +26 -11
  6. data/CHANGES.md +25 -2
  7. data/Gemfile +6 -3
  8. data/ISSUE_TEMPLATE.md +1 -1
  9. data/README.md +2 -4
  10. data/Rakefile +18 -9
  11. data/lib/reform/contract.rb +7 -7
  12. data/lib/reform/contract/custom_error.rb +41 -0
  13. data/lib/reform/contract/validate.rb +9 -5
  14. data/lib/reform/errors.rb +27 -15
  15. data/lib/reform/form.rb +22 -11
  16. data/lib/reform/form/call.rb +1 -1
  17. data/lib/reform/form/composition.rb +2 -2
  18. data/lib/reform/form/dry.rb +10 -86
  19. data/lib/reform/form/dry/input_hash.rb +37 -0
  20. data/lib/reform/form/dry/new_api.rb +58 -0
  21. data/lib/reform/form/dry/old_api.rb +61 -0
  22. data/lib/reform/form/populator.rb +9 -11
  23. data/lib/reform/form/prepopulate.rb +3 -2
  24. data/lib/reform/form/validate.rb +19 -12
  25. data/lib/reform/result.rb +36 -9
  26. data/lib/reform/validation.rb +10 -8
  27. data/lib/reform/validation/groups.rb +2 -3
  28. data/lib/reform/version.rb +1 -1
  29. data/reform.gemspec +10 -9
  30. data/test/benchmarking.rb +10 -11
  31. data/test/call_new_api.rb +23 -0
  32. data/test/{call_test.rb → call_old_api.rb} +3 -3
  33. data/test/changed_test.rb +7 -7
  34. data/test/coercion_test.rb +50 -18
  35. data/test/composition_new_api.rb +186 -0
  36. data/test/{composition_test.rb → composition_old_api.rb} +23 -26
  37. data/test/contract/custom_error_test.rb +55 -0
  38. data/test/contract_new_api.rb +77 -0
  39. data/test/{contract_test.rb → contract_old_api.rb} +8 -8
  40. data/test/default_test.rb +1 -1
  41. data/test/deserialize_test.rb +8 -11
  42. data/test/errors_new_api.rb +225 -0
  43. data/test/errors_old_api.rb +230 -0
  44. data/test/feature_test.rb +7 -9
  45. data/test/fixtures/dry_error_messages.yml +5 -2
  46. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  47. data/test/form_new_api.rb +57 -0
  48. data/test/{form_test.rb → form_old_api.rb} +2 -2
  49. data/test/form_option_new_api.rb +24 -0
  50. data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
  51. data/test/from_test.rb +8 -12
  52. data/test/inherit_new_api.rb +105 -0
  53. data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
  54. data/test/module_new_api.rb +137 -0
  55. data/test/{module_test.rb → module_old_api.rb} +19 -15
  56. data/test/parse_option_test.rb +5 -5
  57. data/test/parse_pipeline_test.rb +2 -2
  58. data/test/populate_new_api.rb +304 -0
  59. data/test/{populate_test.rb → populate_old_api.rb} +28 -34
  60. data/test/populator_skip_test.rb +1 -2
  61. data/test/prepopulator_test.rb +5 -6
  62. data/test/read_only_test.rb +12 -1
  63. data/test/readable_test.rb +5 -5
  64. data/test/reform_new_api.rb +204 -0
  65. data/test/{reform_test.rb → reform_old_api.rb} +17 -23
  66. data/test/save_new_api.rb +101 -0
  67. data/test/{save_test.rb → save_old_api.rb} +10 -13
  68. data/test/setup_test.rb +6 -6
  69. data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
  70. data/test/skip_if_old_api.rb +92 -0
  71. data/test/skip_setter_and_getter_test.rb +2 -3
  72. data/test/test_helper.rb +13 -5
  73. data/test/validate_new_api.rb +408 -0
  74. data/test/{validate_test.rb → validate_old_api.rb} +43 -53
  75. data/test/validation/dry_validation_new_api.rb +826 -0
  76. data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
  77. data/test/validation/result_test.rb +20 -22
  78. data/test/validation_library_provided_test.rb +3 -3
  79. data/test/virtual_test.rb +46 -6
  80. data/test/writeable_test.rb +7 -7
  81. metadata +101 -51
  82. data/test/errors_test.rb +0 -180
  83. data/test/readonly_test.rb +0 -14
@@ -0,0 +1,101 @@
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
+ 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
+ property :name
22
+ validation do
23
+ params { 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,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class SaveTest < BaseTest
4
4
  Song = Struct.new(:title, :album, :composer)
@@ -40,15 +40,13 @@ class SaveTest < BaseTest
40
40
  end
41
41
  end
42
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) }
43
48
 
44
- let (:song) { Song.new("Broken").extend(Saveable) }
45
- # let (:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
46
- let (:composer) { Artist.new("Greg Graffin").extend(Saveable) }
47
- let (:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
48
- let (:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
49
-
50
- let (:form) { AlbumForm.new(album) }
51
-
49
+ let(:form) { AlbumForm.new(album) }
52
50
 
53
51
  it do
54
52
  form.validate("songs" => [{"title" => "Fixed"}])
@@ -72,12 +70,11 @@ class SaveTest < BaseTest
72
70
  nested_hash = hash
73
71
  end
74
72
 
75
- nested_hash.must_equal({"name"=>nil, "artist"=>nil})
73
+ nested_hash.must_equal({"name" => nil, "artist" => nil})
76
74
  end
77
75
  end
78
76
  end
79
77
 
80
-
81
78
  # class SaveWithDynamicOptionsTest < MiniTest::Spec
82
79
  # Song = Struct.new(:id, :title, :length) do
83
80
  # include Saveable
@@ -88,8 +85,8 @@ end
88
85
  # property :length, virtual: true
89
86
  # end
90
87
 
91
- # let (:song) { Song.new }
92
- # let (:form) { SongForm.new(song) }
88
+ # let(:song) { Song.new }
89
+ # let(:form) { SongForm.new(song) }
93
90
 
94
91
  # # we have access to original input value and outside parameters.
95
92
  # it "xxx" do
@@ -20,13 +20,13 @@ class SetupTest < MiniTest::Spec
20
20
  end
21
21
  end
22
22
 
23
- let (:song) { Song.new("Broken") }
24
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
25
- let (:composer) { Artist.new("Greg Graffin") }
26
- let (:artist) { Artist.new("Bad Religion") }
23
+ let(:song) { Song.new("Broken") }
24
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
25
+ let(:composer) { Artist.new("Greg Graffin") }
26
+ let(:artist) { Artist.new("Bad Religion") }
27
27
 
28
28
  describe "with nested objects" do
29
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
29
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
30
30
 
31
31
  it do
32
32
  form = AlbumForm.new(album)
@@ -45,4 +45,4 @@ class SetupTest < MiniTest::Spec
45
45
  form.artist.must_be_kind_of Reform::Form
46
46
  end
47
47
  end
48
- end
48
+ end
@@ -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
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
- required(:title).filled
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)
@@ -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
@@ -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,12 @@
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 'byebug'
6
+ require "byebug"
7
7
 
8
8
  require "reform/form/dry"
9
+
9
10
  # setup test classes so we can test without dry being included
10
11
  class TestForm < Reform::Form
11
12
  feature Reform::Form::Dry
@@ -15,6 +16,15 @@ class TestContract < Reform::Contract
15
16
  feature Reform::Form::Dry
16
17
  end
17
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
27
+
18
28
  class BaseTest < MiniTest::Spec
19
29
  class AlbumForm < TestForm
20
30
  property :title
@@ -34,8 +44,7 @@ class BaseTest < MiniTest::Spec
34
44
  Label = Struct.new(:name)
35
45
  Length = Struct.new(:minutes, :seconds)
36
46
 
37
-
38
- let (:hit) { Song.new("Roxanne") }
47
+ let(:hit) { Song.new("Roxanne") }
39
48
  end
40
49
 
41
50
  MiniTest::Spec.class_eval do
@@ -49,4 +58,3 @@ MiniTest::Spec.class_eval do
49
58
  end
50
59
  end
51
60
  end
52
-
@@ -0,0 +1,408 @@
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
+ # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/trailblazer/reform/pull/104
282
+ # # describe ":populator with :empty" do
283
+ # # let(:form) {
284
+ # # Class.new(Reform::Form) do
285
+ # # collection :songs, :empty => true, :populator => lambda { |fragment, index, args|
286
+ # # songs[index] = args.binding[:form].new(Song.new)
287
+ # # } do
288
+ # # property :title
289
+ # # end
290
+ # # end
291
+ # # }
292
+
293
+ # # let(:params) {
294
+ # # {
295
+ # # "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
296
+ # # }
297
+ # # }
298
+
299
+ # # subject { form.new(Album.new("Hits", [], [])) }
300
+
301
+ # # before { subject.validate(params) }
302
+
303
+ # # it { subject.songs[0].title.must_equal "Fallout" }
304
+ # # it { subject.songs[1].title.must_equal "Roxanne" }
305
+ # # end
306
+
307
+ # # test cardinalities.
308
+ # describe "with empty collection and cardinality" do
309
+ # let(:album) { Album.new }
310
+
311
+ # subject { Class.new(Reform::Form) do
312
+ # include Reform::Form::ActiveModel
313
+ # model :album
314
+
315
+ # collection :songs do
316
+ # property :title
317
+ # end
318
+
319
+ # property :hit do
320
+ # property :title
321
+ # end
322
+
323
+ # validates :songs, :length => {:minimum => 1}
324
+ # validates :hit, :presence => true
325
+ # end.new(album) }
326
+
327
+ # describe "invalid" do
328
+ # before { subject.validate({}).must_equal false }
329
+
330
+ # it do
331
+ # # ensure that only hit and songs keys are present
332
+ # subject.errors.messages.keys.sort.must_equal([:hit, :songs])
333
+ # # validate content of hit and songs keys
334
+ # subject.errors.messages[:hit].must_equal(["must be filled"])
335
+ # subject.errors.messages[:songs].first.must_match(/\Ais too short \(minimum is 1 characters?\)\z/)
336
+ # end
337
+ # end
338
+
339
+ # describe "valid" do
340
+ # let(:album) { Album.new(nil, Song.new, [Song.new("Urban Myth")]) }
341
+
342
+ # before {
343
+ # subject.validate({"songs" => [{"title"=>"Daddy, Brother, Lover, Little Boy"}], "hit" => {"title"=>"The Horse"}}).
344
+ # must_equal true
345
+ # }
346
+
347
+ # it { subject.errors.messages.must_equal({}) }
348
+ # end
349
+ # end
350
+
351
+ # # providing manual validator method allows accessing form's API.
352
+ # describe "with ::validate" do
353
+ # let(:form) {
354
+ # Class.new(Reform::Form) do
355
+ # property :title
356
+
357
+ # validate :title?
358
+
359
+ # def title?
360
+ # errors.add :title, "not lowercase" if title == "Fallout"
361
+ # end
362
+ # end
363
+ # }
364
+
365
+ # let(:params) { {"title" => "Fallout"} }
366
+ # let(:song) { Song.new("Englishman") }
367
+
368
+ # subject { form.new(song) }
369
+
370
+ # before { @res = subject.validate(params) }
371
+
372
+ # it { @res.must_equal false }
373
+ # it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
374
+ # end
375
+
376
+ # # overriding the reader for a nested form should only be considered when rendering.
377
+ # describe "with overridden reader for nested form" do
378
+ # let(:form) {
379
+ # Class.new(Reform::Form) do
380
+ # property :band, :populate_if_empty => lambda { |*| Band.new } do
381
+ # property :label
382
+ # end
383
+
384
+ # collection :songs, :populate_if_empty => lambda { |*| Song.new } do
385
+ # property :title
386
+ # end
387
+
388
+ # def band
389
+ # raise "only call me when rendering the form!"
390
+ # end
391
+
392
+ # def songs
393
+ # raise "only call me when rendering the form!"
394
+ # end
395
+ # end.new(album)
396
+ # }
397
+
398
+ # let(:album) { Album.new }
399
+
400
+ # # don't use #artist when validating!
401
+ # it do
402
+ # form.validate("band" => {"label" => "Hellcat"}, "songs" => [{"title" => "Stand Your Ground"}, {"title" => "Otherside"}])
403
+ # form.sync
404
+ # album.band.label.must_equal "Hellcat"
405
+ # album.songs.first.title.must_equal "Stand Your Ground"
406
+ # end
407
+ # end
408
+ # end