reform 2.2.3 → 2.3.2
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 +56 -0
- 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 +47 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +11 -29
- data/lib/reform/form/prepopulate.rb +5 -4
- 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 +13 -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 +453 -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 +101 -72
- 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
@@ -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,8 +3,8 @@ 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
|
@@ -12,4 +12,4 @@ class ParsePipelineTest < MiniTest::Spec
|
|
12
12
|
form.validate("name" => "Greatest Hits")
|
13
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
|