reform 2.2.4 → 2.3.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.
- checksums.yaml +5 -5
- data/.gitignore +5 -1
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +460 -0
- data/.travis.yml +11 -6
- data/Appraisals +8 -0
- data/CHANGES.md +54 -4
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +2 -16
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +5 -7
- data/Rakefile +18 -9
- data/gemfiles/0.13.0.gemfile +8 -0
- data/gemfiles/1.5.0.gemfile +9 -0
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +7 -17
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +53 -23
- data/lib/reform/errors.rb +61 -0
- data/lib/reform/form.rb +36 -10
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +10 -58
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/dry/new_api.rb +46 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +11 -27
- data/lib/reform/form/prepopulate.rb +4 -3
- data/lib/reform/form/validate.rb +28 -13
- data/lib/reform/result.rb +90 -0
- data/lib/reform/validation.rb +19 -11
- data/lib/reform/validation/groups.rb +12 -27
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +15 -13
- data/test/benchmarking.rb +39 -6
- data/test/call_new_api.rb +23 -0
- data/test/{call_test.rb → call_old_api.rb} +4 -4
- data/test/changed_test.rb +8 -8
- data/test/coercion_test.rb +51 -19
- data/test/composition_new_api.rb +186 -0
- data/test/{composition_test.rb → composition_old_api.rb} +66 -31
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_new_api.rb +77 -0
- data/test/{contract_test.rb → contract_old_api.rb} +13 -13
- data/test/default_test.rb +2 -2
- data/test/deserialize_test.rb +11 -14
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +8 -10
- data/test/fixtures/dry_error_messages.yml +73 -23
- data/test/fixtures/dry_new_api_error_messages.yml +104 -0
- data/test/form_new_api.rb +57 -0
- data/test/{form_test.rb → form_old_api.rb} +5 -5
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
- data/test/from_test.rb +9 -13
- data/test/inherit_new_api.rb +105 -0
- data/test/inherit_old_api.rb +105 -0
- data/test/{module_test.rb → module_new_api.rb} +20 -25
- data/test/module_old_api.rb +146 -0
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +3 -3
- data/test/populate_new_api.rb +304 -0
- data/test/{populate_test.rb → populate_old_api.rb} +83 -49
- data/test/populator_skip_test.rb +9 -9
- data/test/prepopulator_test.rb +8 -9
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +7 -7
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +30 -51
- data/test/save_new_api.rb +101 -0
- data/test/{save_test.rb → save_old_api.rb} +32 -20
- data/test/setup_test.rb +8 -8
- data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +3 -4
- data/test/test_helper.rb +25 -14
- data/test/validate_new_api.rb +408 -0
- data/test/{validate_test.rb → validate_old_api.rb} +59 -69
- data/test/validation/dry_validation_new_api.rb +836 -0
- data/test/validation/dry_validation_old_api.rb +772 -0
- data/test/validation/result_test.rb +77 -0
- data/test/validation_library_provided_test.rb +16 -0
- data/test/virtual_test.rb +47 -7
- data/test/writeable_test.rb +35 -6
- metadata +127 -56
- data/gemfiles/Gemfile.disposable-0.3 +0 -6
- data/lib/reform/contract/errors.rb +0 -43
- data/lib/reform/form/mongoid.rb +0 -37
- data/lib/reform/form/orm.rb +0 -26
- data/lib/reform/mongoid.rb +0 -4
- data/test/deprecation_test.rb +0 -27
- data/test/errors_test.rb +0 -165
- data/test/inherit_test.rb +0 -119
- data/test/readonly_test.rb +0 -14
- data/test/validation/dry_test.rb +0 -60
- data/test/validation/dry_validation_test.rb +0 -352
- data/test/validation/errors.yml +0 -4
@@ -1,14 +1,16 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class SkipIfTest < BaseTest
|
4
|
+
let(:hit) { Song.new }
|
5
|
+
let(:album) { Album.new(nil, hit, [], nil) }
|
4
6
|
|
5
|
-
class AlbumForm <
|
7
|
+
class AlbumForm < TestForm
|
6
8
|
property :title
|
7
9
|
|
8
|
-
property :hit, skip_if:
|
10
|
+
property :hit, skip_if: ->(options) { options[:fragment]["title"] == "" } do
|
9
11
|
property :title
|
10
12
|
validation do
|
11
|
-
|
13
|
+
params { required(:title).filled }
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
@@ -21,10 +23,6 @@ class SkipIfTest < BaseTest
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
|
25
|
-
let (:hit) { Song.new }
|
26
|
-
let (:album) { Album.new(nil, hit, [], nil) }
|
27
|
-
|
28
26
|
# deserializes when present.
|
29
27
|
it do
|
30
28
|
form = AlbumForm.new(album)
|
@@ -36,7 +34,7 @@ class SkipIfTest < BaseTest
|
|
36
34
|
it do
|
37
35
|
form = AlbumForm.new(Album.new)
|
38
36
|
form.validate("hit" => {"title" => ""}).must_equal true
|
39
|
-
form.hit
|
37
|
+
assert_nil form.hit # hit hasn't been deserialised.
|
40
38
|
end
|
41
39
|
|
42
40
|
# skips deserialization when not present.
|
@@ -50,7 +48,7 @@ end
|
|
50
48
|
|
51
49
|
class SkipIfAllBlankTest < BaseTest
|
52
50
|
# skip_if: :all_blank"
|
53
|
-
class AlbumForm <
|
51
|
+
class AlbumForm < TestForm
|
54
52
|
collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
|
55
53
|
property :title
|
56
54
|
property :length
|
@@ -60,15 +58,28 @@ class SkipIfAllBlankTest < BaseTest
|
|
60
58
|
# create only one object.
|
61
59
|
it do
|
62
60
|
form = AlbumForm.new(OpenStruct.new(songs: []))
|
63
|
-
form.validate("songs" => [{"title"=>"Apathy"}, {"title"=>"", "length" => ""}]).must_equal true
|
61
|
+
form.validate("songs" => [{"title" => "Apathy"}, {"title" => "", "length" => ""}]).must_equal true
|
64
62
|
form.songs.size.must_equal 1
|
65
63
|
form.songs[0].title.must_equal "Apathy"
|
66
64
|
end
|
67
65
|
|
68
66
|
it do
|
69
67
|
form = AlbumForm.new(OpenStruct.new(songs: []))
|
70
|
-
form.validate("songs" => [{"title"=>"", "length" => ""}, {"title"=>"Apathy"}]).must_equal true
|
68
|
+
form.validate("songs" => [{"title" => "", "length" => ""}, {"title" => "Apathy"}]).must_equal true
|
71
69
|
form.songs.size.must_equal 1
|
72
70
|
form.songs[0].title.must_equal "Apathy"
|
73
71
|
end
|
74
72
|
end
|
73
|
+
|
74
|
+
class InvalidOptionsCombinationTest < BaseTest
|
75
|
+
it do
|
76
|
+
assert_raises(Reform::Form::InvalidOptionsCombinationError) do
|
77
|
+
class AlbumForm < TestForm
|
78
|
+
collection :songs, skip_if: :all_blank, populator: -> {} do
|
79
|
+
property :title
|
80
|
+
property :length
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class SkipIfTest < BaseTest
|
4
|
+
let(:hit) { Song.new }
|
5
|
+
let(:album) { Album.new(nil, hit, [], nil) }
|
6
|
+
|
7
|
+
class AlbumForm < TestForm
|
8
|
+
property :title
|
9
|
+
|
10
|
+
property :hit, skip_if: ->(options) { options[:fragment]["title"] == "" } do
|
11
|
+
property :title
|
12
|
+
validation do
|
13
|
+
required(:title).filled
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
collection :songs, skip_if: :skip_song?, populate_if_empty: BaseTest::Song do
|
18
|
+
property :title
|
19
|
+
end
|
20
|
+
|
21
|
+
def skip_song?(options)
|
22
|
+
options[:fragment]["title"].nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# deserializes when present.
|
27
|
+
it do
|
28
|
+
form = AlbumForm.new(album)
|
29
|
+
form.validate("hit" => {"title" => "Altar Of Sacrifice"}).must_equal true
|
30
|
+
form.hit.title.must_equal "Altar Of Sacrifice"
|
31
|
+
end
|
32
|
+
|
33
|
+
# skips deserialization when not present.
|
34
|
+
it do
|
35
|
+
form = AlbumForm.new(Album.new)
|
36
|
+
form.validate("hit" => {"title" => ""}).must_equal true
|
37
|
+
assert_nil form.hit # hit hasn't been deserialised.
|
38
|
+
end
|
39
|
+
|
40
|
+
# skips deserialization when not present.
|
41
|
+
it do
|
42
|
+
form = AlbumForm.new(Album.new(nil, nil, []))
|
43
|
+
form.validate("songs" => [{"title" => "Waste Of Breath"}, {"title" => nil}]).must_equal true
|
44
|
+
form.songs.size.must_equal 1
|
45
|
+
form.songs[0].title.must_equal "Waste Of Breath"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class SkipIfAllBlankTest < BaseTest
|
50
|
+
# skip_if: :all_blank"
|
51
|
+
class AlbumForm < TestForm
|
52
|
+
collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
|
53
|
+
property :title
|
54
|
+
property :length
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# create only one object.
|
59
|
+
it do
|
60
|
+
form = AlbumForm.new(OpenStruct.new(songs: []))
|
61
|
+
form.validate("songs" => [{"title" => "Apathy"}, {"title" => "", "length" => ""}]).must_equal true
|
62
|
+
form.songs.size.must_equal 1
|
63
|
+
form.songs[0].title.must_equal "Apathy"
|
64
|
+
end
|
65
|
+
|
66
|
+
it do
|
67
|
+
form = AlbumForm.new(OpenStruct.new(songs: []))
|
68
|
+
form.validate("songs" => [{"title" => "", "length" => ""}, {"title" => "Apathy"}]).must_equal true
|
69
|
+
form.songs.size.must_equal 1
|
70
|
+
form.songs[0].title.must_equal "Apathy"
|
71
|
+
end
|
72
|
+
|
73
|
+
it do
|
74
|
+
form = AlbumForm.new(OpenStruct.new(songs: []))
|
75
|
+
form.validate(:songs => [{:title=>"", :length => ""}, {:title=>"Apathy"}]).must_equal true
|
76
|
+
form.songs.size.must_equal 1
|
77
|
+
form.songs[0].title.must_equal "Apathy"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class InvalidOptionsCombinationTest < BaseTest
|
82
|
+
it do
|
83
|
+
assert_raises(Reform::Form::InvalidOptionsCombinationError) do
|
84
|
+
class AlbumForm < TestForm
|
85
|
+
collection :songs, skip_if: :all_blank, populator: -> {} do
|
86
|
+
property :title
|
87
|
+
property :length
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -7,7 +7,7 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
|
|
7
7
|
Album = Struct.new(:title, :artist)
|
8
8
|
Artist = Struct.new(:name)
|
9
9
|
|
10
|
-
class AlbumForm <
|
10
|
+
class AlbumForm < TestForm
|
11
11
|
property :title
|
12
12
|
|
13
13
|
def title
|
@@ -31,8 +31,7 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
let
|
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
|
data/test/test_helper.rb
CHANGED
@@ -1,11 +1,32 @@
|
|
1
1
|
require "reform"
|
2
|
-
require
|
2
|
+
require "minitest/autorun"
|
3
3
|
require "representable/debug"
|
4
4
|
require "declarative/testing"
|
5
5
|
require "pp"
|
6
|
+
require "pry-byebug"
|
7
|
+
|
8
|
+
require "reform/form/dry"
|
9
|
+
|
10
|
+
# setup test classes so we can test without dry being included
|
11
|
+
class TestForm < Reform::Form
|
12
|
+
feature Reform::Form::Dry
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestContract < Reform::Contract
|
16
|
+
feature Reform::Form::Dry
|
17
|
+
end
|
18
|
+
|
19
|
+
module Types
|
20
|
+
DRY_MODULE = Gem::Version.new(Dry::Types::VERSION) < Gem::Version.new("0.15.0") ? Dry::Types.module : Dry.Types()
|
21
|
+
include DRY_MODULE
|
22
|
+
end
|
23
|
+
|
24
|
+
DRY_TYPES_VERSION = Gem::Version.new(Dry::Types::VERSION)
|
25
|
+
DRY_TYPES_CONSTANT = DRY_TYPES_VERSION < Gem::Version.new("0.13.0") ? Types::Form : Types::Params
|
26
|
+
DRY_TYPES_INT_CONSTANT = DRY_TYPES_VERSION < Gem::Version.new("0.13.0") ? Types::Form::Int : Types::Params::Integer
|
6
27
|
|
7
28
|
class BaseTest < MiniTest::Spec
|
8
|
-
class AlbumForm <
|
29
|
+
class AlbumForm < TestForm
|
9
30
|
property :title
|
10
31
|
|
11
32
|
property :hit do
|
@@ -17,14 +38,13 @@ class BaseTest < MiniTest::Spec
|
|
17
38
|
end
|
18
39
|
end
|
19
40
|
|
20
|
-
Song = Struct.new(:title, :length)
|
41
|
+
Song = Struct.new(:title, :length, :rating)
|
21
42
|
Album = Struct.new(:title, :hit, :songs, :band)
|
22
43
|
Band = Struct.new(:label)
|
23
44
|
Label = Struct.new(:name)
|
24
45
|
Length = Struct.new(:minutes, :seconds)
|
25
46
|
|
26
|
-
|
27
|
-
let (:hit) { Song.new("Roxanne") }
|
47
|
+
let(:hit) { Song.new("Roxanne") }
|
28
48
|
end
|
29
49
|
|
30
50
|
MiniTest::Spec.class_eval do
|
@@ -38,12 +58,3 @@ MiniTest::Spec.class_eval do
|
|
38
58
|
end
|
39
59
|
end
|
40
60
|
end
|
41
|
-
|
42
|
-
require "reform/form/dry"
|
43
|
-
Reform::Contract.class_eval do
|
44
|
-
feature Reform::Form::Dry
|
45
|
-
end
|
46
|
-
# FIXME!
|
47
|
-
Reform::Form.class_eval do
|
48
|
-
feature Reform::Form::Dry
|
49
|
-
end
|
@@ -0,0 +1,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
|