reform 2.2.4 → 2.3.3
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/.travis.yml +11 -6
- data/Appraisals +8 -0
- data/CHANGES.md +57 -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 +16 -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 +45 -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 +14 -13
- data/test/benchmarking.rb +39 -6
- data/test/call_new_api.rb +23 -0
- data/test/call_old_api.rb +23 -0
- data/test/changed_test.rb +14 -14
- data/test/coercion_test.rb +57 -25
- data/test/composition_new_api.rb +186 -0
- data/test/composition_old_api.rb +184 -0
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_new_api.rb +77 -0
- data/test/contract_old_api.rb +77 -0
- data/test/default_test.rb +4 -4
- data/test/deserialize_test.rb +17 -20
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +10 -12
- 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} +8 -8
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +5 -5
- data/test/from_test.rb +18 -22
- 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} +26 -31
- data/test/module_old_api.rb +146 -0
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +4 -4
- data/test/populate_new_api.rb +304 -0
- data/test/populate_old_api.rb +304 -0
- data/test/populator_skip_test.rb +11 -11
- data/test/prepopulator_test.rb +23 -24
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +9 -9
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +44 -65
- data/test/save_new_api.rb +101 -0
- data/test/save_old_api.rb +101 -0
- data/test/setup_test.rb +17 -17
- data/test/skip_if_new_api.rb +85 -0
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +9 -10
- data/test/test_helper.rb +25 -14
- data/test/validate_new_api.rb +453 -0
- data/test/{validate_test.rb → validate_old_api.rb} +121 -131
- data/test/validation/dry_validation_new_api.rb +835 -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 +38 -9
- metadata +111 -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/call_test.rb +0 -23
- data/test/composition_test.rb +0 -149
- data/test/contract_test.rb +0 -77
- data/test/deprecation_test.rb +0 -27
- data/test/errors_test.rb +0 -165
- data/test/inherit_test.rb +0 -119
- data/test/populate_test.rb +0 -270
- data/test/readonly_test.rb +0 -14
- data/test/save_test.rb +0 -89
- data/test/skip_if_test.rb +0 -74
- 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,5 +1,5 @@
|
|
1
1
|
require "test_helper"
|
2
|
-
require
|
2
|
+
require "reform/form/coercion"
|
3
3
|
|
4
4
|
class ModuleInclusionTest < MiniTest::Spec
|
5
5
|
module BandPropertyForm
|
@@ -9,7 +9,7 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
9
9
|
property :title
|
10
10
|
|
11
11
|
validation do
|
12
|
-
|
12
|
+
params { required(:title).filled }
|
13
13
|
end
|
14
14
|
|
15
15
|
def id # gets mixed into Form, too.
|
@@ -22,11 +22,11 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
22
22
|
end
|
23
23
|
|
24
24
|
validation do
|
25
|
-
|
25
|
+
params { required(:band).filled }
|
26
26
|
end
|
27
27
|
|
28
28
|
include Dry::Types.module # allows using Types::* in module.
|
29
|
-
property :cool, type:
|
29
|
+
property :cool, type: DRY_TYPES_CONSTANT::Bool # test coercion.
|
30
30
|
end
|
31
31
|
|
32
32
|
# TODO: test if works, move stuff into inherit_schema!
|
@@ -36,53 +36,50 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
36
36
|
collection :airplays do
|
37
37
|
property :station
|
38
38
|
validation do
|
39
|
-
|
39
|
+
params { required(:station).filled }
|
40
40
|
end
|
41
41
|
end
|
42
42
|
validation do
|
43
|
-
|
43
|
+
params { required(:airplays).filled }
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
47
|
# test:
|
49
48
|
# by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
|
50
|
-
class HitForm <
|
49
|
+
class HitForm < TestForm
|
51
50
|
include BandPropertyForm
|
52
51
|
end
|
53
52
|
|
54
|
-
class SongForm <
|
53
|
+
class SongForm < TestForm
|
55
54
|
include Coercion
|
56
55
|
property :title
|
57
56
|
|
58
57
|
include BandPropertyForm
|
59
58
|
end
|
60
59
|
|
61
|
-
|
62
|
-
let (:song) { OpenStruct.new(:band => OpenStruct.new(:title => "Time Again")) }
|
60
|
+
let(:song) { OpenStruct.new(band: OpenStruct.new(title: "Time Again")) }
|
63
61
|
|
64
62
|
# nested form from module is present and creates accessor.
|
65
|
-
it { SongForm.new(song).band.title.must_equal "Time Again" }
|
63
|
+
it { _(SongForm.new(song).band.title).must_equal "Time Again" }
|
66
64
|
|
67
65
|
# methods from module get included.
|
68
|
-
it { SongForm.new(song).id.must_equal 1 }
|
69
|
-
it { SongForm.new(song).band.id.must_equal 2 }
|
66
|
+
it { _(SongForm.new(song).id).must_equal 1 }
|
67
|
+
it { _(SongForm.new(song).band.id).must_equal 2 }
|
70
68
|
|
71
69
|
# validators get inherited.
|
72
70
|
it do
|
73
71
|
form = SongForm.new(OpenStruct.new)
|
74
72
|
form.validate({})
|
75
|
-
form.errors.messages.must_equal(
|
73
|
+
_(form.errors.messages).must_equal(band: ["must be filled"])
|
76
74
|
end
|
77
75
|
|
78
76
|
# coercion works
|
79
77
|
it do
|
80
78
|
form = SongForm.new(OpenStruct.new)
|
81
|
-
form.validate(
|
82
|
-
form.cool.must_equal true
|
79
|
+
form.validate(cool: "1")
|
80
|
+
_(form.cool).must_equal true
|
83
81
|
end
|
84
82
|
|
85
|
-
|
86
83
|
# include a module into a module into a class :)
|
87
84
|
module AlbumFormModule
|
88
85
|
include Reform::Form::Module
|
@@ -90,29 +87,28 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
90
87
|
|
91
88
|
property :name
|
92
89
|
validation do
|
93
|
-
|
90
|
+
params { required(:name).filled }
|
94
91
|
end
|
95
92
|
end
|
96
93
|
|
97
|
-
class AlbumForm <
|
94
|
+
class AlbumForm < TestForm
|
98
95
|
include AlbumFormModule
|
99
96
|
|
100
97
|
# pp heritage
|
101
|
-
property :band, :
|
98
|
+
property :band, inherit: true do
|
102
99
|
property :label
|
103
100
|
validation do
|
104
|
-
|
101
|
+
params { required(:label).filled }
|
105
102
|
end
|
106
103
|
end
|
107
104
|
end
|
108
105
|
|
109
106
|
it do
|
110
|
-
form = AlbumForm.new(OpenStruct.new(:
|
111
|
-
form.validate(
|
112
|
-
form.errors.messages.must_equal(
|
107
|
+
form = AlbumForm.new(OpenStruct.new(band: OpenStruct.new))
|
108
|
+
form.validate("band" => {})
|
109
|
+
_(form.errors.messages).must_equal("band.title": ["must be filled"], "band.label": ["must be filled"], name: ["must be filled"])
|
113
110
|
end
|
114
111
|
|
115
|
-
|
116
112
|
describe "module with custom accessors" do
|
117
113
|
module SongModule
|
118
114
|
include Reform::Form::Module
|
@@ -127,16 +123,15 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
127
123
|
end
|
128
124
|
end
|
129
125
|
|
130
|
-
class IncludingSongForm <
|
126
|
+
class IncludingSongForm < TestForm
|
131
127
|
include SongModule
|
132
128
|
end
|
133
129
|
|
134
|
-
let
|
130
|
+
let(:song) { OpenStruct.new(id: 1, title: "Instant Mash") }
|
135
131
|
|
136
132
|
it do
|
137
|
-
IncludingSongForm.new(song).id.must_equal 1
|
138
|
-
IncludingSongForm.new(song).title.must_equal "INSTANT MASH"
|
133
|
+
_(IncludingSongForm.new(song).id).must_equal 1
|
134
|
+
_(IncludingSongForm.new(song).title).must_equal "INSTANT MASH"
|
139
135
|
end
|
140
136
|
end
|
141
137
|
end
|
142
|
-
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "reform/form/coercion"
|
3
|
+
|
4
|
+
class ModuleInclusionTest < MiniTest::Spec
|
5
|
+
module BandPropertyForm
|
6
|
+
include Reform::Form::Module
|
7
|
+
|
8
|
+
property :artist
|
9
|
+
property :band do
|
10
|
+
property :title
|
11
|
+
|
12
|
+
validation do
|
13
|
+
required(:title).filled
|
14
|
+
end
|
15
|
+
|
16
|
+
def id # gets mixed into Form, too.
|
17
|
+
2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def id # gets mixed into Form, too.
|
22
|
+
1
|
23
|
+
end
|
24
|
+
|
25
|
+
validation do
|
26
|
+
required(:band).filled
|
27
|
+
end
|
28
|
+
|
29
|
+
include Dry::Types.module # allows using Types::* in module.
|
30
|
+
property :cool, type: DRY_TYPES_CONSTANT::Bool # test coercion.
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
def artist=(new_value)
|
34
|
+
errors.add(:artist, "this needs to be filled") if new_value.nil?
|
35
|
+
super(new_value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: test if works, move stuff into inherit_schema!
|
41
|
+
module AirplaysPropertyForm
|
42
|
+
include Reform::Form::Module
|
43
|
+
|
44
|
+
collection :airplays do
|
45
|
+
property :station
|
46
|
+
validation do
|
47
|
+
required(:station).filled
|
48
|
+
end
|
49
|
+
end
|
50
|
+
validation do
|
51
|
+
required(:airplays).filled
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# test:
|
56
|
+
# by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
|
57
|
+
class HitForm < TestForm
|
58
|
+
include BandPropertyForm
|
59
|
+
end
|
60
|
+
|
61
|
+
class SongForm < TestForm
|
62
|
+
include Coercion
|
63
|
+
property :title
|
64
|
+
|
65
|
+
include BandPropertyForm
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:song) { OpenStruct.new(band: OpenStruct.new(title: "Time Again"), artist: "Ketama") }
|
69
|
+
|
70
|
+
# nested form from module is present and creates accessor.
|
71
|
+
it { _(SongForm.new(song).band.title).must_equal "Time Again" }
|
72
|
+
it { _(SongForm.new(song).artist).must_equal "Ketama" }
|
73
|
+
|
74
|
+
# methods from module get included.
|
75
|
+
it { _(SongForm.new(song).id).must_equal 1 }
|
76
|
+
it { _(SongForm.new(song).band.id).must_equal 2 }
|
77
|
+
|
78
|
+
# validators get inherited.
|
79
|
+
it do
|
80
|
+
form = SongForm.new(OpenStruct.new)
|
81
|
+
form.validate(artist: nil)
|
82
|
+
_(form.errors.messages).must_equal(artist: ["this needs to be filled"], band: ["must be filled"])
|
83
|
+
end
|
84
|
+
|
85
|
+
# coercion works
|
86
|
+
it do
|
87
|
+
form = SongForm.new(OpenStruct.new)
|
88
|
+
form.validate({cool: "1"})
|
89
|
+
_(form.cool).must_equal true
|
90
|
+
end
|
91
|
+
|
92
|
+
# include a module into a module into a class :)
|
93
|
+
module AlbumFormModule
|
94
|
+
include Reform::Form::Module
|
95
|
+
include BandPropertyForm
|
96
|
+
|
97
|
+
property :name
|
98
|
+
validation do
|
99
|
+
required(:name).filled
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class AlbumForm < TestForm
|
104
|
+
include AlbumFormModule
|
105
|
+
|
106
|
+
# pp heritage
|
107
|
+
property :band, inherit: true do
|
108
|
+
property :label
|
109
|
+
validation do
|
110
|
+
required(:label).filled
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it do
|
116
|
+
form = AlbumForm.new(OpenStruct.new(band: OpenStruct.new))
|
117
|
+
form.validate("band" => {})
|
118
|
+
_(form.errors.messages).must_equal("band.title": ["must be filled"], "band.label": ["must be filled"], name: ["must be filled"])
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "module with custom accessors" do
|
122
|
+
module SongModule
|
123
|
+
include Reform::Form::Module
|
124
|
+
|
125
|
+
property :id # no custom accessor for id.
|
126
|
+
property :title # has custom accessor.
|
127
|
+
|
128
|
+
module InstanceMethods
|
129
|
+
def title
|
130
|
+
super.upcase
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class IncludingSongForm < TestForm
|
136
|
+
include SongModule
|
137
|
+
end
|
138
|
+
|
139
|
+
let(:song) { OpenStruct.new(id: 1, title: "Instant Mash") }
|
140
|
+
|
141
|
+
it do
|
142
|
+
_(IncludingSongForm.new(song).id).must_equal 1
|
143
|
+
_(IncludingSongForm.new(song).title).must_equal "INSTANT MASH"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ParseOptionTest < MiniTest::Spec
|
4
|
+
Comment = Struct.new(:content, :user)
|
5
|
+
User = Struct.new(:name)
|
6
|
+
|
7
|
+
class CommentForm < TestForm
|
8
|
+
property :content
|
9
|
+
property :user, parse: false
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:current_user) { User.new("Peter") }
|
13
|
+
let(:form) { CommentForm.new(Comment.new, user: current_user) }
|
14
|
+
|
15
|
+
it do
|
16
|
+
_(form.user).must_equal current_user
|
17
|
+
|
18
|
+
lorem = "Lorem ipsum dolor sit amet..."
|
19
|
+
form.validate("content" => lorem, "user" => "not the current user")
|
20
|
+
|
21
|
+
_(form.content).must_equal lorem
|
22
|
+
_(form.user).must_equal current_user
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "using ':parse' option doesn't override other ':deserialize' options" do
|
26
|
+
class ArticleCommentForm < TestForm
|
27
|
+
property :content
|
28
|
+
property :article, deserializer: {instance: "Instance"}
|
29
|
+
property :user, parse: false, deserializer: {instance: "Instance"}
|
30
|
+
end
|
31
|
+
|
32
|
+
it do
|
33
|
+
_(ArticleCommentForm.definitions.get(:user)[:deserializer][:writeable]).must_equal false
|
34
|
+
_(ArticleCommentForm.definitions.get(:user)[:deserializer][:instance]).must_equal "Instance"
|
35
|
+
|
36
|
+
_(ArticleCommentForm.definitions.get(:article)[:deserializer][:writeable]).must_equal true
|
37
|
+
_(ArticleCommentForm.definitions.get(:article)[:deserializer][:instance]).must_equal "Instance"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/test/parse_pipeline_test.rb
CHANGED
@@ -3,13 +3,13 @@ require "test_helper"
|
|
3
3
|
class ParsePipelineTest < MiniTest::Spec
|
4
4
|
Album = Struct.new(:name)
|
5
5
|
|
6
|
-
class AlbumForm <
|
7
|
-
property :name, deserializer: {
|
6
|
+
class AlbumForm < TestForm
|
7
|
+
property :name, deserializer: {parse_pipeline: ->(input, options) { Representable::Pipeline[->(ipt, opts) { opts[:represented].name = ipt.inspect }] }}
|
8
8
|
end
|
9
9
|
|
10
10
|
it "allows passing :parse_pipeline directly" do
|
11
11
|
form = AlbumForm.new(Album.new)
|
12
12
|
form.validate("name" => "Greatest Hits")
|
13
|
-
form.name.must_equal "{\"name\"=>\"Greatest Hits\"}"
|
13
|
+
_(form.name).must_equal "{\"name\"=>\"Greatest Hits\"}"
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PopulatorTest < 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 < TestForm
|
9
|
+
property :name, populator: ->(options) { self.name = options[:fragment].reverse }
|
10
|
+
validation do
|
11
|
+
params { required(:name).filled }
|
12
|
+
end
|
13
|
+
|
14
|
+
collection :songs,
|
15
|
+
populator: ->(fragment:, model:, index:, **) {
|
16
|
+
(item = model[index]) ? item : model.insert(index, Song.new)
|
17
|
+
} do
|
18
|
+
property :title
|
19
|
+
validation do
|
20
|
+
params { required(:title).filled }
|
21
|
+
end
|
22
|
+
|
23
|
+
property :composer, populator: ->(options) { options[:model] || self.composer = Artist.new } do
|
24
|
+
property :name
|
25
|
+
validation do
|
26
|
+
params { required(:name).filled }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# property :artist, populator: lambda { |fragment, options| (item = options.binding.get) ? item : Artist.new } do
|
32
|
+
# NOTE: we have to document that model here is the twin!
|
33
|
+
property :artist, populator: ->(options) { options[:model] || self.artist = Artist.new } do
|
34
|
+
property :name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:song) { Song.new("Broken") }
|
39
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
40
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
41
|
+
let(:artist) { Artist.new("Bad Religion") }
|
42
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
43
|
+
|
44
|
+
let(:form) { AlbumForm.new(album) }
|
45
|
+
|
46
|
+
it "runs populator on scalar" do
|
47
|
+
form.validate(
|
48
|
+
"name" => "override me!"
|
49
|
+
)
|
50
|
+
|
51
|
+
_(form.name).must_equal "!em edirrevo"
|
52
|
+
end
|
53
|
+
|
54
|
+
# changing existing property :artist.
|
55
|
+
# TODO: check with artist==nil
|
56
|
+
it do
|
57
|
+
old_id = artist.object_id
|
58
|
+
|
59
|
+
form.validate(
|
60
|
+
"artist" => {"name" => "Marcus Miller"}
|
61
|
+
)
|
62
|
+
|
63
|
+
_(form.artist.model.object_id).must_equal old_id
|
64
|
+
end
|
65
|
+
|
66
|
+
# use populator for default value on scalars?
|
67
|
+
|
68
|
+
# adding to collection via :populator.
|
69
|
+
# valid.
|
70
|
+
it "yyy" do
|
71
|
+
_(form.validate(
|
72
|
+
"songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
|
73
|
+
{"title" => "Rime Of The Ancient Mariner"}, # new song.
|
74
|
+
{"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
|
75
|
+
)).must_equal true
|
76
|
+
|
77
|
+
_(form.errors.messages.inspect).must_equal "{}"
|
78
|
+
|
79
|
+
# form has updated.
|
80
|
+
_(form.name).must_equal "The Dissent Of Man"
|
81
|
+
_(form.songs[0].title).must_equal "Fallout"
|
82
|
+
_(form.songs[1].title).must_equal "Roxanne"
|
83
|
+
_(form.songs[1].composer.name).must_equal "Greg Graffin"
|
84
|
+
|
85
|
+
_(form.songs[1].composer.model).must_be_instance_of Artist
|
86
|
+
|
87
|
+
_(form.songs[1].title).must_equal "Roxanne"
|
88
|
+
_(form.songs[2].title).must_equal "Rime Of The Ancient Mariner" # new song added.
|
89
|
+
_(form.songs[3].title).must_equal "Re-Education"
|
90
|
+
_(form.songs[3].composer.name).must_equal "Rise Against"
|
91
|
+
_(form.songs.size).must_equal 4
|
92
|
+
_(form.artist.name).must_equal "Bad Religion"
|
93
|
+
|
94
|
+
# model has not changed, yet.
|
95
|
+
_(album.name).must_equal "The Dissent Of Man"
|
96
|
+
_(album.songs[0].title).must_equal "Broken"
|
97
|
+
_(album.songs[1].title).must_equal "Resist Stance"
|
98
|
+
_(album.songs[1].composer.name).must_equal "Greg Graffin"
|
99
|
+
_(album.songs.size).must_equal 2
|
100
|
+
_(album.artist.name).must_equal "Bad Religion"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class PopulateWithMethodTest < Minitest::Spec
|
105
|
+
Album = Struct.new(:title)
|
106
|
+
|
107
|
+
class AlbumForm < TestForm
|
108
|
+
property :title, populator: :title!
|
109
|
+
|
110
|
+
def title!(options)
|
111
|
+
self.title = options[:fragment].reverse
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
let(:form) { AlbumForm.new(Album.new) }
|
116
|
+
|
117
|
+
it "runs populator method" do
|
118
|
+
form.validate("title" => "override me!")
|
119
|
+
|
120
|
+
_(form.title).must_equal "!em edirrevo"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class PopulateWithCallableTest < Minitest::Spec
|
125
|
+
Album = Struct.new(:title)
|
126
|
+
|
127
|
+
class TitlePopulator
|
128
|
+
include Uber::Callable
|
129
|
+
|
130
|
+
def call(form, options)
|
131
|
+
form.title = options[:fragment].reverse
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class AlbumForm < TestForm
|
136
|
+
property :title, populator: TitlePopulator.new
|
137
|
+
end
|
138
|
+
|
139
|
+
let(:form) { AlbumForm.new(Album.new) }
|
140
|
+
|
141
|
+
it "runs populator method" do
|
142
|
+
form.validate("title" => "override me!")
|
143
|
+
|
144
|
+
_(form.title).must_equal "!em edirrevo"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class PopulateWithProcTest < Minitest::Spec
|
149
|
+
Album = Struct.new(:title)
|
150
|
+
|
151
|
+
TitlePopulator = ->(options) do
|
152
|
+
options[:represented].title = options[:fragment].reverse
|
153
|
+
end
|
154
|
+
|
155
|
+
class AlbumForm < TestForm
|
156
|
+
property :title, populator: TitlePopulator
|
157
|
+
end
|
158
|
+
|
159
|
+
let(:form) { AlbumForm.new(Album.new) }
|
160
|
+
|
161
|
+
it "runs populator method" do
|
162
|
+
form.validate("title" => "override me!")
|
163
|
+
|
164
|
+
_(form.title).must_equal "!em edirrevo"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class PopulateIfEmptyTest < MiniTest::Spec
|
169
|
+
Song = Struct.new(:title, :album, :composer)
|
170
|
+
Album = Struct.new(:name, :songs, :artist)
|
171
|
+
Artist = Struct.new(:name)
|
172
|
+
|
173
|
+
let(:song) { Song.new("Broken") }
|
174
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
175
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
176
|
+
let(:artist) { Artist.new("Bad Religion") }
|
177
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
178
|
+
|
179
|
+
class AlbumForm < TestForm
|
180
|
+
property :name
|
181
|
+
|
182
|
+
collection :songs,
|
183
|
+
populate_if_empty: Song do # class name works.
|
184
|
+
|
185
|
+
property :title
|
186
|
+
validation do
|
187
|
+
params { required(:title).filled }
|
188
|
+
end
|
189
|
+
|
190
|
+
property :composer, populate_if_empty: :populate_composer! do # lambda works, too. in form context.
|
191
|
+
property :name
|
192
|
+
validation do
|
193
|
+
params { required(:name).filled }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
def populate_composer!(options)
|
199
|
+
Artist.new
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
property :artist, populate_if_empty: ->(args) { create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
|
204
|
+
property :name
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
class Sting < Artist
|
209
|
+
attr_accessor :args
|
210
|
+
end
|
211
|
+
def create_artist(input, user_options)
|
212
|
+
Sting.new.tap { |artist| artist.args = ([input, user_options].to_s) }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
let(:form) { AlbumForm.new(album) }
|
217
|
+
|
218
|
+
it do
|
219
|
+
_(form.songs.size).must_equal 2
|
220
|
+
|
221
|
+
_(form.validate(
|
222
|
+
"songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
|
223
|
+
{"title" => "Rime Of The Ancient Mariner"}, # new song.
|
224
|
+
{"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
|
225
|
+
)).must_equal true
|
226
|
+
|
227
|
+
_(form.errors.messages.inspect).must_equal "{}"
|
228
|
+
|
229
|
+
# form has updated.
|
230
|
+
_(form.name).must_equal "The Dissent Of Man"
|
231
|
+
_(form.songs[0].title).must_equal "Fallout"
|
232
|
+
_(form.songs[1].title).must_equal "Roxanne"
|
233
|
+
_(form.songs[1].composer.name).must_equal "Greg Graffin"
|
234
|
+
_(form.songs[1].title).must_equal "Roxanne"
|
235
|
+
_(form.songs[2].title).must_equal "Rime Of The Ancient Mariner" # new song added.
|
236
|
+
_(form.songs[3].title).must_equal "Re-Education"
|
237
|
+
_(form.songs[3].composer.name).must_equal "Rise Against"
|
238
|
+
_(form.songs.size).must_equal 4
|
239
|
+
_(form.artist.name).must_equal "Bad Religion"
|
240
|
+
|
241
|
+
# model has not changed, yet.
|
242
|
+
_(album.name).must_equal "The Dissent Of Man"
|
243
|
+
_(album.songs[0].title).must_equal "Broken"
|
244
|
+
_(album.songs[1].title).must_equal "Resist Stance"
|
245
|
+
_(album.songs[1].composer.name).must_equal "Greg Graffin"
|
246
|
+
_(album.songs.size).must_equal 2
|
247
|
+
_(album.artist.name).must_equal "Bad Religion"
|
248
|
+
end
|
249
|
+
|
250
|
+
# trigger artist populator. lambda calling form instance method.
|
251
|
+
it "xxxx" do
|
252
|
+
form = AlbumForm.new(album = Album.new)
|
253
|
+
form.validate("artist" => {"name" => "From Autumn To Ashes"})
|
254
|
+
|
255
|
+
_(form.artist.name).must_equal "From Autumn To Ashes"
|
256
|
+
# test lambda was executed in form context.
|
257
|
+
_(form.artist.model).must_be_instance_of AlbumForm::Sting
|
258
|
+
# test lambda block arguments.
|
259
|
+
_(form.artist.model.args.to_s).must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
|
260
|
+
|
261
|
+
assert_nil album.artist
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# delete songs while deserializing.
|
266
|
+
class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
|
267
|
+
Song = Struct.new(:title, :album, :composer)
|
268
|
+
Album = Struct.new(:name, :songs, :artist)
|
269
|
+
|
270
|
+
let(:song) { Song.new("Broken") }
|
271
|
+
let(:song2) { Song.new("Resist Stance") }
|
272
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song2]) }
|
273
|
+
|
274
|
+
class AlbumForm < TestForm
|
275
|
+
property :name
|
276
|
+
|
277
|
+
collection :songs,
|
278
|
+
populate_if_empty: Song, skip_if: :delete_song! do
|
279
|
+
|
280
|
+
property :title
|
281
|
+
validation do
|
282
|
+
params { required(:title).filled }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def delete_song!(options)
|
287
|
+
songs.delete(songs[0]) and return true if options[:fragment]["title"] == "Broken, delete me!"
|
288
|
+
false
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
let(:form) { AlbumForm.new(album) }
|
293
|
+
|
294
|
+
it do
|
295
|
+
_(form.validate(
|
296
|
+
"songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
|
297
|
+
)).must_equal true
|
298
|
+
|
299
|
+
_(form.errors.messages.inspect).must_equal "{}"
|
300
|
+
|
301
|
+
_(form.songs.size).must_equal 1
|
302
|
+
_(form.songs[0].title).must_equal "Roxanne"
|
303
|
+
end
|
304
|
+
end
|