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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +460 -0
- data/.travis.yml +26 -11
- data/CHANGES.md +25 -2
- data/Gemfile +6 -3
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +2 -4
- data/Rakefile +18 -9
- data/lib/reform/contract.rb +7 -7
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +9 -5
- data/lib/reform/errors.rb +27 -15
- data/lib/reform/form.rb +22 -11
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +10 -86
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/dry/new_api.rb +58 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +9 -11
- data/lib/reform/form/prepopulate.rb +3 -2
- data/lib/reform/form/validate.rb +19 -12
- data/lib/reform/result.rb +36 -9
- data/lib/reform/validation.rb +10 -8
- data/lib/reform/validation/groups.rb +2 -3
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +10 -9
- data/test/benchmarking.rb +10 -11
- data/test/call_new_api.rb +23 -0
- data/test/{call_test.rb → call_old_api.rb} +3 -3
- data/test/changed_test.rb +7 -7
- data/test/coercion_test.rb +50 -18
- data/test/composition_new_api.rb +186 -0
- data/test/{composition_test.rb → composition_old_api.rb} +23 -26
- 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} +8 -8
- data/test/default_test.rb +1 -1
- data/test/deserialize_test.rb +8 -11
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +7 -9
- data/test/fixtures/dry_error_messages.yml +5 -2
- 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} +2 -2
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
- data/test/from_test.rb +8 -12
- data/test/inherit_new_api.rb +105 -0
- data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
- data/test/module_new_api.rb +137 -0
- data/test/{module_test.rb → module_old_api.rb} +19 -15
- data/test/parse_option_test.rb +5 -5
- data/test/parse_pipeline_test.rb +2 -2
- data/test/populate_new_api.rb +304 -0
- data/test/{populate_test.rb → populate_old_api.rb} +28 -34
- data/test/populator_skip_test.rb +1 -2
- data/test/prepopulator_test.rb +5 -6
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +5 -5
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +17 -23
- data/test/save_new_api.rb +101 -0
- data/test/{save_test.rb → save_old_api.rb} +10 -13
- data/test/setup_test.rb +6 -6
- data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +2 -3
- data/test/test_helper.rb +13 -5
- data/test/validate_new_api.rb +408 -0
- data/test/{validate_test.rb → validate_old_api.rb} +43 -53
- data/test/validation/dry_validation_new_api.rb +826 -0
- data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
- data/test/validation/result_test.rb +20 -22
- data/test/validation_library_provided_test.rb +3 -3
- data/test/virtual_test.rb +46 -6
- data/test/writeable_test.rb +7 -7
- metadata +101 -51
- data/test/errors_test.rb +0 -180
- data/test/readonly_test.rb +0 -14
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
|
|
3
3
|
class FormCompositionInheritanceTest < MiniTest::Spec
|
|
4
4
|
module SizePrice
|
|
@@ -26,10 +26,9 @@ class FormCompositionInheritanceTest < MiniTest::Spec
|
|
|
26
26
|
property :size, inherit: true, on: :measurement
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
-
let (:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
|
|
29
|
+
let(:measurement) { Measurement.new(:l) }
|
|
30
|
+
let(:tshirt) { Tshirt.new(2, :m) }
|
|
31
|
+
let(:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
|
|
33
32
|
|
|
34
33
|
Tshirt = Struct.new(:price, :size)
|
|
35
34
|
Measurement = Struct.new(:size)
|
|
@@ -46,28 +45,28 @@ class FormCompositionTest < MiniTest::Spec
|
|
|
46
45
|
class RequestForm < TestForm
|
|
47
46
|
include Composition
|
|
48
47
|
|
|
49
|
-
property :name, :
|
|
50
|
-
property :requester_id, :
|
|
51
|
-
properties :title, :id, :
|
|
48
|
+
property :name, on: :requester
|
|
49
|
+
property :requester_id, on: :requester, from: :id
|
|
50
|
+
properties :title, :id, on: :song
|
|
52
51
|
# property :channel # FIXME: what about the "main model"?
|
|
53
|
-
property :channel, :
|
|
54
|
-
property :requester, :
|
|
55
|
-
property :captcha, :
|
|
52
|
+
property :channel, virtual: true, on: :song
|
|
53
|
+
property :requester, on: :requester
|
|
54
|
+
property :captcha, on: :song, virtual: true
|
|
56
55
|
|
|
57
56
|
validation do
|
|
58
57
|
required(:name).filled
|
|
59
58
|
required(:title).filled
|
|
60
59
|
end
|
|
61
60
|
|
|
62
|
-
property :band, :
|
|
61
|
+
property :band, on: :song do
|
|
63
62
|
property :title
|
|
64
63
|
end
|
|
65
64
|
end
|
|
66
65
|
|
|
67
|
-
let
|
|
68
|
-
let
|
|
69
|
-
let
|
|
70
|
-
let
|
|
66
|
+
let(:form) { RequestForm.new(song: song, requester: requester) }
|
|
67
|
+
let(:song) { Song.new(1, "Rio", band) }
|
|
68
|
+
let(:requester) { Requester.new(2, "Duran Duran", "MCP") }
|
|
69
|
+
let(:band) { Band.new("Duran^2") }
|
|
71
70
|
|
|
72
71
|
# delegation form -> composition works
|
|
73
72
|
it { form.id.must_equal 1 }
|
|
@@ -85,7 +84,6 @@ class FormCompositionTest < MiniTest::Spec
|
|
|
85
84
|
it { form.model[:requester].must_equal requester }
|
|
86
85
|
it { form.model[:song].must_equal song }
|
|
87
86
|
|
|
88
|
-
|
|
89
87
|
it "creates Composition for you" do
|
|
90
88
|
form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal true
|
|
91
89
|
form.validate("title" => "", "name" => "Frenzal Rhomb").must_equal false
|
|
@@ -101,7 +99,7 @@ class FormCompositionTest < MiniTest::Spec
|
|
|
101
99
|
hash[:title] = form.title
|
|
102
100
|
end
|
|
103
101
|
|
|
104
|
-
hash.must_equal({:
|
|
102
|
+
hash.must_equal({name: "Duran Duran", title: "Rio"})
|
|
105
103
|
end
|
|
106
104
|
|
|
107
105
|
it "provides nested symbolized hash as second block argument" do
|
|
@@ -114,9 +112,9 @@ class FormCompositionTest < MiniTest::Spec
|
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
hash.must_equal({
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
song: {"title" => "Greyhound", "id" => 1, "channel" => "JJJ", "captcha" => "wonderful", "band" => {"title" => "Duran^2"}},
|
|
116
|
+
requester: {"name" => "Frenzal Rhomb", "id" => 2, "requester" => "MCP"}
|
|
117
|
+
}
|
|
120
118
|
)
|
|
121
119
|
end
|
|
122
120
|
|
|
@@ -162,12 +160,11 @@ class FormCompositionTest < MiniTest::Spec
|
|
|
162
160
|
end
|
|
163
161
|
end
|
|
164
162
|
|
|
165
|
-
|
|
166
163
|
class FormCompositionCollectionTest < MiniTest::Spec
|
|
167
164
|
Book = Struct.new(:id, :name)
|
|
168
165
|
Library = Struct.new(:id) do
|
|
169
166
|
def books
|
|
170
|
-
[Book.new(1,"My book")]
|
|
167
|
+
[Book.new(1, "My book")]
|
|
171
168
|
end
|
|
172
169
|
end
|
|
173
170
|
|
|
@@ -180,8 +177,8 @@ class FormCompositionCollectionTest < MiniTest::Spec
|
|
|
180
177
|
end
|
|
181
178
|
end
|
|
182
179
|
|
|
183
|
-
let
|
|
184
|
-
let
|
|
180
|
+
let(:form) { LibraryForm.new(library: library) }
|
|
181
|
+
let(:library) { Library.new(2) }
|
|
185
182
|
|
|
186
|
-
it { form.save
|
|
183
|
+
it { form.save { |hash| hash.must_equal({library: {"books" => [{"id" => 1, "name" => "My book"}]}}) } }
|
|
187
184
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class CustomerErrorTest < MiniTest::Spec
|
|
4
|
+
let(:key) { :name }
|
|
5
|
+
let(:error_text) { "text2" }
|
|
6
|
+
let(:starting_error) { [OpenStruct.new(errors: {title: ["text1"]})] }
|
|
7
|
+
|
|
8
|
+
let(:custom_error) { Reform::Contract::CustomError.new(key, error_text, @results) }
|
|
9
|
+
|
|
10
|
+
before { @results = starting_error }
|
|
11
|
+
|
|
12
|
+
it "base class structure" do
|
|
13
|
+
assert_equal custom_error.success?, false
|
|
14
|
+
assert_equal custom_error.failure?, true
|
|
15
|
+
assert_equal custom_error.errors, key => [error_text]
|
|
16
|
+
assert_equal custom_error.messages, key => [error_text]
|
|
17
|
+
assert_equal custom_error.hint, {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "updates @results accordingly" do
|
|
21
|
+
it "add new key" do
|
|
22
|
+
custom_error
|
|
23
|
+
|
|
24
|
+
assert_equal @results.size, 2
|
|
25
|
+
errors = @results.map(&:errors)
|
|
26
|
+
|
|
27
|
+
assert_equal errors[0], starting_error.first.errors
|
|
28
|
+
assert_equal errors[1], custom_error.errors
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "when key error already exists in @results" do
|
|
32
|
+
let(:key) { :title }
|
|
33
|
+
|
|
34
|
+
it "merge errors text" do
|
|
35
|
+
custom_error
|
|
36
|
+
|
|
37
|
+
assert_equal @results.size, 1
|
|
38
|
+
|
|
39
|
+
assert_equal @results.first.errors.values, [%w[text1 text2]]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "add error text is already" do
|
|
43
|
+
let(:error_text) { "text1" }
|
|
44
|
+
|
|
45
|
+
it 'does not create duplicates' do
|
|
46
|
+
custom_error
|
|
47
|
+
|
|
48
|
+
assert_equal @results.size, 1
|
|
49
|
+
|
|
50
|
+
assert_equal @results.first.errors.values, [%w[text1]]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ContractTest < MiniTest::Spec
|
|
4
|
+
Song = Struct.new(:title, :album, :composer)
|
|
5
|
+
Album = Struct.new(:name, :duration, :songs, :artist)
|
|
6
|
+
Artist = Struct.new(:name)
|
|
7
|
+
|
|
8
|
+
class ArtistForm < TestForm
|
|
9
|
+
property :name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class AlbumForm < TestContract
|
|
13
|
+
property :name
|
|
14
|
+
|
|
15
|
+
properties :duration
|
|
16
|
+
properties :year, :style, readable: false
|
|
17
|
+
|
|
18
|
+
validation do
|
|
19
|
+
params { required(:name).filled }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
collection :songs do
|
|
23
|
+
property :title
|
|
24
|
+
validation do
|
|
25
|
+
params { required(:title).filled }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
property :composer do
|
|
29
|
+
property :name
|
|
30
|
+
validation do
|
|
31
|
+
params { required(:name).filled }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
property :artist, form: ArtistForm
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
let(:song) { Song.new("Broken") }
|
|
40
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
|
41
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
|
42
|
+
let(:artist) { Artist.new("Bad Religion") }
|
|
43
|
+
let(:album) { Album.new("The Dissent Of Man", 123, [song, song_with_composer], artist) }
|
|
44
|
+
|
|
45
|
+
let(:form) { AlbumForm.new(album) }
|
|
46
|
+
|
|
47
|
+
# accept `property form: SongForm`.
|
|
48
|
+
it do
|
|
49
|
+
form.artist.must_be_instance_of ArtistForm
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe ".properties" do
|
|
53
|
+
it "defines a property when called with one argument" do
|
|
54
|
+
form.must_respond_to :duration
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "defines several properties when called with multiple arguments" do
|
|
58
|
+
form.must_respond_to :year
|
|
59
|
+
form.must_respond_to :style
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "passes options to each property when options are provided" do
|
|
63
|
+
readable = AlbumForm.new(album).options_for(:style)[:readable]
|
|
64
|
+
readable.must_equal false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "returns the list of defined properties" do
|
|
68
|
+
returned_value = AlbumForm.properties(:hello, :world, virtual: true)
|
|
69
|
+
returned_value.must_equal %i[hello world]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "#options_for" do
|
|
74
|
+
it { AlbumForm.options_for(:name).extend(Declarative::Inspect).inspect.must_equal "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
|
|
75
|
+
it { AlbumForm.new(album).options_for(:name).extend(Declarative::Inspect).inspect.must_equal "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
|
|
3
3
|
class ContractTest < MiniTest::Spec
|
|
4
4
|
Song = Struct.new(:title, :album, :composer)
|
|
@@ -36,13 +36,13 @@ class ContractTest < MiniTest::Spec
|
|
|
36
36
|
property :artist, form: ArtistForm
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
let
|
|
40
|
-
let
|
|
41
|
-
let
|
|
42
|
-
let
|
|
43
|
-
let
|
|
39
|
+
let(:song) { Song.new("Broken") }
|
|
40
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
|
41
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
|
42
|
+
let(:artist) { Artist.new("Bad Religion") }
|
|
43
|
+
let(:album) { Album.new("The Dissent Of Man", 123, [song, song_with_composer], artist) }
|
|
44
44
|
|
|
45
|
-
let
|
|
45
|
+
let(:form) { AlbumForm.new(album) }
|
|
46
46
|
|
|
47
47
|
# accept `property form: SongForm`.
|
|
48
48
|
it do
|
|
@@ -66,7 +66,7 @@ class ContractTest < MiniTest::Spec
|
|
|
66
66
|
|
|
67
67
|
it "returns the list of defined properties" do
|
|
68
68
|
returned_value = AlbumForm.properties(:hello, :world, virtual: true)
|
|
69
|
-
returned_value.must_equal [
|
|
69
|
+
returned_value.must_equal %i[hello world]
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
data/test/default_test.rb
CHANGED
data/test/deserialize_test.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
require "representable/json"
|
|
3
3
|
|
|
4
4
|
class DeserializeTest < MiniTest::Spec
|
|
@@ -10,7 +10,7 @@ class DeserializeTest < MiniTest::Spec
|
|
|
10
10
|
module Json
|
|
11
11
|
def deserialize(params)
|
|
12
12
|
deserializer.new(self).
|
|
13
|
-
|
|
13
|
+
# extend(Representable::Debug).
|
|
14
14
|
from_json(params)
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ class DeserializeTest < MiniTest::Spec
|
|
|
18
18
|
Disposable::Rescheme.from(self.class,
|
|
19
19
|
include: [Representable::JSON],
|
|
20
20
|
superclass: Representable::Decorator,
|
|
21
|
-
definitions_from:
|
|
21
|
+
definitions_from: ->(inline) { inline.definitions },
|
|
22
22
|
options_from: :deserializer,
|
|
23
23
|
exclude_options: [:populator]
|
|
24
24
|
)
|
|
@@ -26,15 +26,13 @@ class DeserializeTest < MiniTest::Spec
|
|
|
26
26
|
end
|
|
27
27
|
include Json
|
|
28
28
|
|
|
29
|
-
|
|
30
29
|
property :title
|
|
31
30
|
property :artist, populate_if_empty: Artist do
|
|
32
31
|
property :name
|
|
33
32
|
end
|
|
34
33
|
end
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
let (:artist) { Artist.new("A-ha") }
|
|
35
|
+
let(:artist) { Artist.new("A-ha") }
|
|
38
36
|
it do
|
|
39
37
|
artist_id = artist.object_id
|
|
40
38
|
|
|
@@ -62,14 +60,13 @@ class DeserializeTest < MiniTest::Spec
|
|
|
62
60
|
# also tests the Form#deserializer API. # FIXME.
|
|
63
61
|
it "uses deserializer inferred from JsonAlbumForm but deserializes/populates to CompilationForm" do
|
|
64
62
|
form = CompilationForm.new(Album.new)
|
|
65
|
-
form.validate("artist"=> {"name" => "Horowitz"}) # the deserializer doesn't know symbols.
|
|
63
|
+
form.validate("artist" => {"name" => "Horowitz"}) # the deserializer doesn't know symbols.
|
|
66
64
|
form.sync
|
|
67
65
|
form.artist.model.must_equal Artist.new("Horowitz", %{{"name"=>"Horowitz"}})
|
|
68
66
|
end
|
|
69
67
|
end
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
|
|
73
70
|
class ValidateWithBlockTest < MiniTest::Spec
|
|
74
71
|
Song = Struct.new(:title, :album, :composer)
|
|
75
72
|
Album = Struct.new(:title, :artist)
|
|
@@ -90,13 +87,13 @@ class ValidateWithBlockTest < MiniTest::Spec
|
|
|
90
87
|
deserializer = Disposable::Rescheme.from(AlbumForm,
|
|
91
88
|
include: [Representable::JSON],
|
|
92
89
|
superclass: Representable::Decorator,
|
|
93
|
-
definitions_from:
|
|
90
|
+
definitions_from: ->(inline) { inline.definitions },
|
|
94
91
|
options_from: :deserializer
|
|
95
92
|
)
|
|
96
93
|
|
|
97
|
-
form.validate(json)
|
|
94
|
+
form.validate(json) { |params|
|
|
98
95
|
deserializer.new(form).from_json(params)
|
|
99
|
-
|
|
96
|
+
}.must_equal true # with block must return result, too.
|
|
100
97
|
|
|
101
98
|
form.title.must_equal "Apocalypse Soon"
|
|
102
99
|
form.artist.name.must_equal "Mute"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ErrorsTest < MiniTest::Spec
|
|
4
|
+
class AlbumForm < TestForm
|
|
5
|
+
property :title
|
|
6
|
+
validation do
|
|
7
|
+
params { required(:title).filled }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
property :artists, default: []
|
|
11
|
+
property :producer do
|
|
12
|
+
property :name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
property :hit do
|
|
16
|
+
property :title
|
|
17
|
+
validation do
|
|
18
|
+
params { required(:title).filled }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
collection :songs do
|
|
23
|
+
property :title
|
|
24
|
+
validation do
|
|
25
|
+
params { required(:title).filled }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
property :band do # yepp, people do crazy stuff like that.
|
|
30
|
+
property :name
|
|
31
|
+
property :label do
|
|
32
|
+
property :name
|
|
33
|
+
validation do
|
|
34
|
+
params { required(:name).filled }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
# TODO: make band a required object.
|
|
38
|
+
|
|
39
|
+
validation do
|
|
40
|
+
config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
|
|
41
|
+
|
|
42
|
+
params { required(:name).filled }
|
|
43
|
+
|
|
44
|
+
rule(:name) { key.failure(:good_musical_taste?) if value == "Nickelback" }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
validation do
|
|
49
|
+
params do
|
|
50
|
+
required(:title).filled
|
|
51
|
+
required(:artists).each(:str?)
|
|
52
|
+
required(:producer).hash do
|
|
53
|
+
required(:name).filled
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
let(:album_title) { "Blackhawks Over Los Angeles" }
|
|
60
|
+
let(:album) do
|
|
61
|
+
OpenStruct.new(
|
|
62
|
+
title: album_title,
|
|
63
|
+
hit: song,
|
|
64
|
+
songs: songs, # TODO: document this requirement,
|
|
65
|
+
band: Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
|
|
66
|
+
producer: Struct.new(:name).new("Sun Records")
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
let(:song) { OpenStruct.new(title: "Downtown") }
|
|
70
|
+
let(:songs) { [song = OpenStruct.new(title: "Calling"), song] }
|
|
71
|
+
let(:form) { AlbumForm.new(album) }
|
|
72
|
+
|
|
73
|
+
describe "#validate with invalid array property" do
|
|
74
|
+
it do
|
|
75
|
+
form.validate(
|
|
76
|
+
title: "Swimming Pool - EP",
|
|
77
|
+
band: {
|
|
78
|
+
name: "Marie Madeleine",
|
|
79
|
+
label: {name: "Ekler'o'shocK"}
|
|
80
|
+
},
|
|
81
|
+
artists: [42, "Good Charlotte", 43]
|
|
82
|
+
).must_equal false
|
|
83
|
+
form.errors.messages.must_equal(artists: {0 => ["must be a string"], 2 => ["must be a string"]})
|
|
84
|
+
form.errors.size.must_equal(1)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "#errors without #validate" do
|
|
89
|
+
it do
|
|
90
|
+
form.errors.size.must_equal 0
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "blank everywhere" do
|
|
95
|
+
before do
|
|
96
|
+
form.validate(
|
|
97
|
+
"hit" => {"title" => ""},
|
|
98
|
+
"title" => "",
|
|
99
|
+
"songs" => [{"title" => ""}, {"title" => ""}],
|
|
100
|
+
"producer" => {"name" => ""}
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it do
|
|
105
|
+
form.errors.messages.must_equal(
|
|
106
|
+
title: ["must be filled"],
|
|
107
|
+
"hit.title": ["must be filled"],
|
|
108
|
+
"songs.title": ["must be filled"],
|
|
109
|
+
"band.label.name": ["must be filled"],
|
|
110
|
+
"producer.name": ["must be filled"]
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# it do
|
|
115
|
+
# form.errors.must_equal({:title => ["must be filled"]})
|
|
116
|
+
# TODO: this should only contain local errors?
|
|
117
|
+
# end
|
|
118
|
+
|
|
119
|
+
# nested forms keep their own Errors:
|
|
120
|
+
it { form.producer.errors.messages.must_equal(name: ["must be filled"]) }
|
|
121
|
+
it { form.hit.errors.messages.must_equal(title: ["must be filled"]) }
|
|
122
|
+
it { form.songs[0].errors.messages.must_equal(title: ["must be filled"]) }
|
|
123
|
+
|
|
124
|
+
it do
|
|
125
|
+
form.errors.messages.must_equal(
|
|
126
|
+
title: ["must be filled"],
|
|
127
|
+
"hit.title": ["must be filled"],
|
|
128
|
+
"songs.title": ["must be filled"],
|
|
129
|
+
"band.label.name": ["must be filled"],
|
|
130
|
+
"producer.name": ["must be filled"]
|
|
131
|
+
)
|
|
132
|
+
form.errors.size.must_equal(5)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe "#validate with main form invalid" do
|
|
137
|
+
it do
|
|
138
|
+
form.validate("title" => "", "band" => {"label" => {name: "Fat Wreck"}}, "producer" => nil).must_equal false
|
|
139
|
+
form.errors.messages.must_equal(title: ["must be filled"], producer: ["must be a hash"])
|
|
140
|
+
form.errors.size.must_equal(2)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "#validate with middle nested form invalid" do
|
|
145
|
+
before { @result = form.validate("hit" => {"title" => ""}, "band" => {"label" => {name: "Fat Wreck"}}) }
|
|
146
|
+
|
|
147
|
+
it { @result.must_equal false }
|
|
148
|
+
it { form.errors.messages.must_equal("hit.title": ["must be filled"]) }
|
|
149
|
+
it { form.errors.size.must_equal(1) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe "#validate with collection form invalid" do
|
|
153
|
+
before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {name: "Fat Wreck"}}) }
|
|
154
|
+
|
|
155
|
+
it { @result.must_equal false }
|
|
156
|
+
it { form.errors.messages.must_equal("songs.title": ["must be filled"]) }
|
|
157
|
+
it { form.errors.size.must_equal(1) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
describe "#validate with collection and 2-level-nested invalid" do
|
|
161
|
+
before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
|
|
162
|
+
|
|
163
|
+
it { @result.must_equal false }
|
|
164
|
+
it { form.errors.messages.must_equal("songs.title": ["must be filled"], "band.label.name": ["must be filled"]) }
|
|
165
|
+
it { form.errors.size.must_equal(2) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe "#validate with nested form using :base invalid" do
|
|
169
|
+
it do
|
|
170
|
+
result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
|
|
171
|
+
result.must_equal false
|
|
172
|
+
form.errors.messages.must_equal("band.name": ["you're a bad person"])
|
|
173
|
+
form.errors.size.must_equal(1)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe "#add" do
|
|
178
|
+
let(:album_title) { nil }
|
|
179
|
+
it do
|
|
180
|
+
result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
|
|
181
|
+
result.must_equal false
|
|
182
|
+
form.errors.messages.must_equal(title: ["must be filled"], "band.name": ["you're a bad person"])
|
|
183
|
+
# add a new custom error
|
|
184
|
+
form.errors.add(:policy, "error_text")
|
|
185
|
+
form.errors.messages.must_equal(title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"])
|
|
186
|
+
# does not duplicate errors
|
|
187
|
+
form.errors.add(:title, "must be filled")
|
|
188
|
+
form.errors.messages.must_equal(title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"])
|
|
189
|
+
# merge existing errors
|
|
190
|
+
form.errors.add(:policy, "another error")
|
|
191
|
+
form.errors.messages.must_equal(title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text", "another error"])
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
describe "correct #validate" do
|
|
196
|
+
before do
|
|
197
|
+
@result = form.validate(
|
|
198
|
+
"hit" => {"title" => "Sacrifice"},
|
|
199
|
+
"title" => "Second Heat",
|
|
200
|
+
"songs" => [{"title" => "Heart Of A Lion"}],
|
|
201
|
+
"band" => {"label" => {name: "Fat Wreck"}}
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it { @result.must_equal true }
|
|
206
|
+
it { form.hit.title.must_equal "Sacrifice" }
|
|
207
|
+
it { form.title.must_equal "Second Heat" }
|
|
208
|
+
it { form.songs.first.title.must_equal "Heart Of A Lion" }
|
|
209
|
+
it do
|
|
210
|
+
skip "WE DON'T NEED COUNT AND EMPTY? ON THE CORE ERRORS OBJECT"
|
|
211
|
+
form.errors.size.must_equal(0)
|
|
212
|
+
form.errors.empty?.must_equal(true)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
describe "Errors#to_s" do
|
|
217
|
+
before { form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
|
|
218
|
+
|
|
219
|
+
# to_s is aliased to messages
|
|
220
|
+
it {
|
|
221
|
+
skip "why do we need Errors#to_s ?"
|
|
222
|
+
form.errors.to_s.must_equal "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}"
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
end
|