reform 2.3.0.rc1 → 2.3.0.rc2

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 (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