reform 2.0.5 → 2.1.0.rc1
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/.travis.yml +3 -1
- data/CHANGES.md +12 -0
- data/Gemfile +12 -2
- data/README.md +9 -14
- data/Rakefile +1 -1
- data/database.sqlite3 +0 -0
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +13 -20
- data/lib/reform/contract/validate.rb +9 -7
- data/lib/reform/form.rb +45 -31
- data/lib/reform/form/active_model.rb +10 -10
- data/lib/reform/form/active_model/form_builder_methods.rb +5 -4
- data/lib/reform/form/active_model/model_reflections.rb +2 -2
- data/lib/reform/form/active_model/model_validations.rb +3 -3
- data/lib/reform/form/active_model/validations.rb +49 -32
- data/lib/reform/form/dry.rb +55 -0
- data/lib/reform/form/lotus.rb +4 -1
- data/lib/reform/form/module.rb +3 -17
- data/lib/reform/form/multi_parameter_attributes.rb +0 -9
- data/lib/reform/form/populator.rb +72 -30
- data/lib/reform/form/validate.rb +19 -43
- data/lib/reform/form/validation/unique_validator.rb +39 -6
- data/lib/reform/validation.rb +40 -0
- data/lib/reform/validation/groups.rb +73 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +3 -1
- data/test/active_record_test.rb +2 -0
- data/test/contract_test.rb +2 -2
- data/test/deprecation_test.rb +27 -0
- data/test/deserialize_test.rb +29 -8
- data/test/dummy/config/locales/en.yml +4 -1
- data/test/errors_test.rb +4 -4
- data/test/feature_test.rb +2 -2
- data/test/fixtures/dry_error_messages.yml +43 -0
- data/test/form_builder_test.rb +10 -8
- data/test/form_test.rb +1 -36
- data/test/inherit_test.rb +20 -8
- data/test/module_test.rb +2 -30
- data/test/parse_pipeline_test.rb +15 -0
- data/test/populate_test.rb +41 -12
- data/test/populator_skip_test.rb +28 -0
- data/test/reform_test.rb +1 -1
- data/test/skip_if_test.rb +10 -3
- data/test/test_helper.rb +11 -2
- data/test/unique_test.rb +72 -1
- data/test/validate_test.rb +6 -7
- data/test/validation/activemodel_validation_test.rb +252 -0
- data/test/validation/dry_validation_test.rb +330 -0
- metadata +63 -10
- data/lib/reform/schema.rb +0 -13
data/test/form_test.rb
CHANGED
@@ -31,47 +31,12 @@ class FormTest < MiniTest::Spec
|
|
31
31
|
form.errors.to_s.must_equal "{:title=>[\"can't be blank\"], :genre=>[\"can't be blank\"], :band=>[\"can't be blank\"]}"
|
32
32
|
end
|
33
33
|
|
34
|
-
# ::schema
|
35
|
-
# TODO: refactor schema tests, this is all covered in Disposable.
|
36
|
-
describe "::schema" do
|
37
|
-
let (:schema) { AlbumForm.schema }
|
38
|
-
|
39
|
-
# it must be a clone
|
40
|
-
it { schema.wont_equal AlbumForm.representer_class }
|
41
|
-
it { assert schema < Representable::Decorator }
|
42
|
-
it { schema.representable_attrs.get(:title).name.must_equal "title" }
|
43
|
-
|
44
|
-
# hit is clone.
|
45
|
-
it { schema.representable_attrs.get(:hit).representer_module.object_id.wont_equal AlbumForm.representer_class.representable_attrs.get(:hit).representer_module.object_id }
|
46
|
-
it { assert schema.representable_attrs.get(:hit).representer_module < Representable::Decorator }
|
47
|
-
# we delete :prepare from schema.
|
48
|
-
it { schema.representable_attrs.get(:hit)[:prepare].must_equal nil }
|
49
|
-
|
50
|
-
# band:label is clone.
|
51
|
-
# this test might look ridiculous but it is mission-critical to assert that schema is really a clone and doesn't mess up the original structure.
|
52
|
-
let (:label) { schema.representable_attrs.get(:band).representer_module.representable_attrs.get(:label) }
|
53
|
-
it { assert label.representer_module < Representable::Decorator }
|
54
|
-
it { label.representer_module.object_id.wont_equal AlbumForm.representer_class.representable_attrs.get(:band).representer_module.representer_class.representable_attrs.get(:label).representer_module.object_id }
|
55
|
-
|
56
|
-
# #apply
|
57
|
-
it do
|
58
|
-
properties = []
|
59
|
-
|
60
|
-
schema.apply do |dfn|
|
61
|
-
properties << dfn.name
|
62
|
-
end
|
63
|
-
|
64
|
-
properties.must_equal ["title", "hit", "title", "songs", "title", "band", "label", "name"]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
|
69
34
|
describe "::dup" do
|
70
35
|
let (:cloned) { AlbumForm.clone }
|
71
36
|
|
72
37
|
# #dup is called in Op.inheritable_attr(:contract_class), it must be subclass of the original one.
|
73
38
|
it { cloned.wont_equal AlbumForm }
|
74
|
-
it { AlbumForm.
|
39
|
+
it { AlbumForm.definitions.wont_equal cloned.definitions }
|
75
40
|
|
76
41
|
it do
|
77
42
|
# currently, forms need a name for validation, even without AM.
|
data/test/inherit_test.rb
CHANGED
@@ -2,9 +2,10 @@ require 'test_helper'
|
|
2
2
|
require 'representable/json'
|
3
3
|
|
4
4
|
class InheritTest < BaseTest
|
5
|
+
Populator = Reform::Form::Populator
|
6
|
+
|
5
7
|
class AlbumForm < Reform::Form
|
6
8
|
property :title, deserializer: {instance: "Instance"}, skip_if: "skip_if in AlbumForm" # allow direct configuration of :deserializer.
|
7
|
-
# puts "[#{options_for(:title)[:deserializer].object_id}] ALB@@@@@ #{options_for(:title)[:deserializer].inspect}"
|
8
9
|
|
9
10
|
property :hit, populator: "Populator" do
|
10
11
|
property :title
|
@@ -75,28 +76,29 @@ require "pp"
|
|
75
76
|
AlbumForm.options_for(:title)[:deserializer].object_id.wont_equal CompilationForm.options_for(:title)[:deserializer].object_id
|
76
77
|
|
77
78
|
# don't overwrite direct deserializer: {} configuration.
|
78
|
-
AlbumForm.options_for(:title)[:
|
79
|
+
AlbumForm.options_for(:title)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync
|
79
80
|
AlbumForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if in AlbumForm"
|
80
81
|
|
81
|
-
AlbumForm.options_for(:hit)[:
|
82
|
+
AlbumForm.options_for(:hit)[:internal_populator].inspect.must_match /Reform::Form::Populator:.+ @user_proc="Populator"/
|
82
83
|
# AlbumForm.options_for(:hit)[:deserializer][:instance].inspect.must_be_instance_with Reform::Form::Populator, user_proc: "Populator"
|
83
84
|
|
84
85
|
|
85
|
-
AlbumForm.options_for(:songs)[:
|
86
|
+
AlbumForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
|
86
87
|
AlbumForm.options_for(:songs)[:deserializer][:skip_parse].must_be_instance_of Reform::Form::Validate::Skip::AllBlank
|
87
88
|
|
88
|
-
AlbumForm.options_for(:artist)[:
|
89
|
+
AlbumForm.options_for(:artist)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
|
89
90
|
|
90
91
|
|
91
92
|
|
92
93
|
CompilationForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if from CompilationForm"
|
93
94
|
# pp CompilationForm.options_for(:songs)
|
94
|
-
CompilationForm.options_for(:songs)[:
|
95
|
+
CompilationForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
|
96
|
+
|
95
97
|
|
96
|
-
CompilationForm.options_for(:artist)[:
|
98
|
+
CompilationForm.options_for(:artist)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
|
97
99
|
|
98
100
|
# completely overwrite inherited.
|
99
|
-
CompilationForm.options_for(:hit)[:
|
101
|
+
CompilationForm.options_for(:hit)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync # reset to default.
|
100
102
|
CompilationForm.options_for(:hit)[:deserializer][:skip_parse].must_equal "SkipParse"
|
101
103
|
|
102
104
|
|
@@ -104,4 +106,14 @@ require "pp"
|
|
104
106
|
AlbumForm.new(OpenStruct.new(artist: OpenStruct.new)).artist.artist_id.must_equal 1
|
105
107
|
CompilationForm.new(OpenStruct.new(artist: OpenStruct.new)).artist.artist_id.must_equal 1
|
106
108
|
end
|
109
|
+
|
110
|
+
|
111
|
+
class CDForm < AlbumForm
|
112
|
+
# override :artist's original populate_if_empty but with :inherit.
|
113
|
+
property :artist, inherit: true, populator: "CD Populator" do
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it { CDForm.options_for(:artist)[:internal_populator].instance_variable_get(:@user_proc).must_equal "CD Populator" }
|
107
119
|
end
|
data/test/module_test.rb
CHANGED
@@ -81,50 +81,22 @@ class ModuleInclusionTest < MiniTest::Spec
|
|
81
81
|
|
82
82
|
property :name
|
83
83
|
validates :name, :presence => true
|
84
|
+
|
84
85
|
end
|
85
86
|
|
86
87
|
class AlbumForm < Reform::Form
|
87
88
|
include AlbumFormModule
|
88
89
|
|
90
|
+
# pp heritage
|
89
91
|
property :band, :inherit => true do
|
90
92
|
property :label
|
91
93
|
validates :label, :presence => true
|
92
94
|
end
|
93
95
|
end
|
94
|
-
# puts "......"+ AlbumForm.representer_class.representable_attrs.get(:band).inspect
|
95
96
|
|
96
97
|
it do
|
97
98
|
form = AlbumForm.new(OpenStruct.new(:band => OpenStruct.new))
|
98
99
|
form.validate({"band" => {}})
|
99
100
|
form.errors.messages.must_equal({:"band.title"=>["can't be blank"], :"band.label"=>["can't be blank"], :name=>["can't be blank"]})
|
100
101
|
end
|
101
|
-
|
102
|
-
|
103
|
-
# # including representer into form
|
104
|
-
# module GenericRepresenter
|
105
|
-
# include Representable
|
106
|
-
|
107
|
-
# property :title
|
108
|
-
# property :manager do
|
109
|
-
# property :title
|
110
|
-
# end
|
111
|
-
# end
|
112
|
-
|
113
|
-
# class LabelForm < Reform::Form
|
114
|
-
# property :location
|
115
|
-
|
116
|
-
# include GenericRepresenter
|
117
|
-
# validates :title, :presence => true
|
118
|
-
# property :manager, :inherit => true do
|
119
|
-
# validates :title, :presence => true
|
120
|
-
# end
|
121
|
-
# end
|
122
|
-
# puts "......"+ LabelForm.representer_class.representable_attrs.get(:title).inspect
|
123
|
-
|
124
|
-
|
125
|
-
# it do
|
126
|
-
# form = LabelForm.new(OpenStruct.new(:manager => OpenStruct.new))
|
127
|
-
# form.validate({"manager" => {}, "title"=>""}) # it's important to pass both nested and scalar here!
|
128
|
-
# form.errors.messages.must_equal(:title=>["can't be blank"], :"manager.title"=>["can't be blank"], )
|
129
|
-
# end
|
130
102
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ParsePipelineTest < MiniTest::Spec
|
4
|
+
Album = Struct.new(:name)
|
5
|
+
|
6
|
+
class AlbumForm < Reform::Form
|
7
|
+
property :name, deserializer: { parse_pipeline: ->(input, options) { Representable::Pipeline[->(input, options) { options[:represented].name = input.inspect }] } }
|
8
|
+
end
|
9
|
+
|
10
|
+
it "allows passing :parse_pipeline directly" do
|
11
|
+
form = AlbumForm.new(Album.new)
|
12
|
+
form.validate("name" => "Greatest Hits")
|
13
|
+
form.name.must_equal "{\"name\"=>\"Greatest Hits\"}"
|
14
|
+
end
|
15
|
+
end
|
data/test/populate_test.rb
CHANGED
@@ -6,18 +6,19 @@ class PopulatorTest < MiniTest::Spec
|
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
8
|
class AlbumForm < Reform::Form
|
9
|
-
property :name
|
9
|
+
property :name, populator: ->(options) { self.name = options[:fragment].reverse }
|
10
10
|
validates :name, presence: true
|
11
11
|
|
12
12
|
collection :songs,
|
13
|
-
populator:
|
14
|
-
|
13
|
+
populator: ->(options) {
|
14
|
+
fragment, collection, index = options[:fragment], options[:model], options[:index]
|
15
|
+
|
15
16
|
(item = collection[index]) ? item : collection.insert(index, Song.new) } do
|
16
17
|
|
17
18
|
property :title
|
18
19
|
validates :title, presence: true
|
19
20
|
|
20
|
-
property :composer, populator:
|
21
|
+
property :composer, populator: ->(options) { options[:model] || self.composer= Artist.new } do
|
21
22
|
property :name
|
22
23
|
validates :name, presence: true
|
23
24
|
end
|
@@ -25,7 +26,7 @@ class PopulatorTest < MiniTest::Spec
|
|
25
26
|
|
26
27
|
# property :artist, populator: lambda { |fragment, options| (item = options.binding.get) ? item : Artist.new } do
|
27
28
|
# NOTE: we have to document that model here is the twin!
|
28
|
-
property :artist, populator:
|
29
|
+
property :artist, populator: ->(options) { options[:model] || self.artist = Artist.new } do
|
29
30
|
property :name
|
30
31
|
end
|
31
32
|
end
|
@@ -38,6 +39,14 @@ class PopulatorTest < MiniTest::Spec
|
|
38
39
|
|
39
40
|
let (:form) { AlbumForm.new(album) }
|
40
41
|
|
42
|
+
it "runs populator on scalar" do
|
43
|
+
form.validate(
|
44
|
+
"name" => "override me!"
|
45
|
+
)
|
46
|
+
|
47
|
+
form.name.must_equal "!em edirrevo"
|
48
|
+
end
|
49
|
+
|
41
50
|
# changing existing property :artist.
|
42
51
|
# TODO: check with artist==nil
|
43
52
|
it do
|
@@ -89,6 +98,26 @@ class PopulatorTest < MiniTest::Spec
|
|
89
98
|
end
|
90
99
|
end
|
91
100
|
|
101
|
+
class PopulateWithMethodTest < Minitest::Spec
|
102
|
+
class AlbumForm < Reform::Form
|
103
|
+
property :title, populator: :title!
|
104
|
+
|
105
|
+
def title!(options)
|
106
|
+
self.title = options[:fragment].reverse
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
let (:form) { AlbumForm.new(Album.new) }
|
111
|
+
|
112
|
+
it "runs populator method" do
|
113
|
+
form.validate(
|
114
|
+
"title" => "override me!"
|
115
|
+
)
|
116
|
+
|
117
|
+
form.title.must_equal "!em edirrevo"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
92
121
|
class PopulateIfEmptyTest < MiniTest::Spec
|
93
122
|
Song = Struct.new(:title, :album, :composer)
|
94
123
|
Album = Struct.new(:name, :songs, :artist)
|
@@ -122,7 +151,7 @@ class PopulateIfEmptyTest < MiniTest::Spec
|
|
122
151
|
end
|
123
152
|
end
|
124
153
|
|
125
|
-
property :artist, populate_if_empty: lambda {
|
154
|
+
property :artist, populate_if_empty: lambda { |args| create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
|
126
155
|
property :name
|
127
156
|
end
|
128
157
|
|
@@ -130,8 +159,8 @@ class PopulateIfEmptyTest < MiniTest::Spec
|
|
130
159
|
class Sting < Artist
|
131
160
|
attr_accessor :args
|
132
161
|
end
|
133
|
-
def create_artist(
|
134
|
-
Sting.new.tap { |artist| artist.args=(
|
162
|
+
def create_artist(input, user_options)
|
163
|
+
Sting.new.tap { |artist| artist.args=([input, user_options].to_s) }
|
135
164
|
end
|
136
165
|
end
|
137
166
|
|
@@ -171,7 +200,7 @@ class PopulateIfEmptyTest < MiniTest::Spec
|
|
171
200
|
end
|
172
201
|
|
173
202
|
# trigger artist populator. lambda calling form instance method.
|
174
|
-
it do
|
203
|
+
it "xxxx" do
|
175
204
|
form = AlbumForm.new(album = Album.new)
|
176
205
|
form.validate("artist" => {"name" => "From Autumn To Ashes"})
|
177
206
|
|
@@ -179,7 +208,7 @@ class PopulateIfEmptyTest < MiniTest::Spec
|
|
179
208
|
# test lambda was executed in form context.
|
180
209
|
form.artist.model.must_be_instance_of AlbumForm::Sting
|
181
210
|
# test lambda block arguments.
|
182
|
-
form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"},
|
211
|
+
form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
|
183
212
|
|
184
213
|
album.artist.must_equal nil
|
185
214
|
end
|
@@ -206,8 +235,8 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
|
|
206
235
|
validates :title, presence: true
|
207
236
|
end
|
208
237
|
|
209
|
-
def delete_song!(
|
210
|
-
songs.delete(songs[0]) and return true if fragment["title"] == "Broken, delete me!"
|
238
|
+
def delete_song!(options)
|
239
|
+
songs.delete(songs[0]) and return true if options[:fragment]["title"] == "Broken, delete me!"
|
211
240
|
false
|
212
241
|
end
|
213
242
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PopulatorSkipTest < MiniTest::Spec
|
4
|
+
Album = Struct.new(:songs)
|
5
|
+
Song = Struct.new(:title)
|
6
|
+
|
7
|
+
|
8
|
+
class AlbumForm < Reform::Form
|
9
|
+
collection :songs,
|
10
|
+
populator: ->(options) {
|
11
|
+
return skip! if options[:fragment][:title] == "Good"
|
12
|
+
songs[options[:index]]
|
13
|
+
} do
|
14
|
+
property :title
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it do
|
19
|
+
form = AlbumForm.new(Album.new([Song.new, Song.new]))
|
20
|
+
hash = {songs: [{title: "Good"}, {title: "Bad"}]}
|
21
|
+
|
22
|
+
form.validate(hash)
|
23
|
+
|
24
|
+
form.songs.size.must_equal 2
|
25
|
+
form.songs[0].title.must_equal nil
|
26
|
+
form.songs[1].title.must_equal "Bad"
|
27
|
+
end
|
28
|
+
end
|
data/test/reform_test.rb
CHANGED
data/test/skip_if_test.rb
CHANGED
@@ -5,7 +5,7 @@ class SkipIfTest < BaseTest
|
|
5
5
|
class AlbumForm < Reform::Form
|
6
6
|
property :title
|
7
7
|
|
8
|
-
property :hit, skip_if: lambda { |
|
8
|
+
property :hit, skip_if: lambda { |options| options[:fragment]["title"].blank? } do
|
9
9
|
property :title
|
10
10
|
validates :title, presence: true
|
11
11
|
end
|
@@ -14,8 +14,8 @@ class SkipIfTest < BaseTest
|
|
14
14
|
property :title
|
15
15
|
end
|
16
16
|
|
17
|
-
def skip_song?(
|
18
|
-
fragment["title"].nil?
|
17
|
+
def skip_song?(options)
|
18
|
+
options[:fragment]["title"].nil?
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -62,4 +62,11 @@ class SkipIfAllBlankTest < BaseTest
|
|
62
62
|
form.songs.size.must_equal 1
|
63
63
|
form.songs[0].title.must_equal "Apathy"
|
64
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
|
65
72
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "reform"
|
2
2
|
require 'minitest/autorun'
|
3
3
|
require "representable/debug"
|
4
|
+
require "declarative/testing"
|
4
5
|
require "pp"
|
5
6
|
|
6
7
|
class ReformSpec < MiniTest::Spec
|
@@ -62,6 +63,10 @@ MiniTest::Spec.class_eval do
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
def self.rails4_2?
|
67
|
+
::ActiveModel::VERSION::MAJOR == 4 and ::ActiveModel::VERSION::MINOR == 2
|
68
|
+
end
|
69
|
+
|
65
70
|
def self.rails4_0?
|
66
71
|
::ActiveModel::VERSION::MAJOR == 4 and ::ActiveModel::VERSION::MINOR == 0
|
67
72
|
end
|
@@ -73,7 +78,11 @@ end
|
|
73
78
|
|
74
79
|
require "reform/form/active_model/validations"
|
75
80
|
Reform::Contract.class_eval do
|
76
|
-
|
81
|
+
feature Reform::Form::ActiveModel::Validations
|
82
|
+
end
|
83
|
+
# FIXME!
|
84
|
+
Reform::Form.class_eval do
|
85
|
+
feature Reform::Form::ActiveModel::Validations
|
77
86
|
end
|
78
87
|
|
79
88
|
I18n.load_path << Dir['test/dummy/config/locales/*.yml']
|
data/test/unique_test.rb
CHANGED
@@ -19,7 +19,7 @@ class UniquenessValidatorOnCreateTest < MiniTest::Spec
|
|
19
19
|
|
20
20
|
form = SongForm.new(Song.new)
|
21
21
|
form.validate("title" => "How Many Tears").must_equal false
|
22
|
-
form.errors.to_s.must_equal "{:title=>[\"
|
22
|
+
form.errors.to_s.must_equal "{:title=>[\"has already been taken\"]}"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -61,4 +61,75 @@ class UniqueWithCompositionTest < MiniTest::Spec
|
|
61
61
|
form.validate("title" => "How Many Tears").must_equal true
|
62
62
|
form.save
|
63
63
|
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
class UniqueValidatorWithScopeTest < MiniTest::Spec
|
68
|
+
class SongForm < Reform::Form
|
69
|
+
include ActiveRecord
|
70
|
+
property :album_id
|
71
|
+
property :title
|
72
|
+
validates :title, unique: { scope: :album_id }
|
73
|
+
end
|
74
|
+
|
75
|
+
it do
|
76
|
+
Song.delete_all
|
77
|
+
|
78
|
+
album = Album.new
|
79
|
+
album.save
|
80
|
+
|
81
|
+
form = SongForm.new(Song.new)
|
82
|
+
form.validate(album_id: album.id, title: 'How Many Tears').must_equal true
|
83
|
+
form.save
|
84
|
+
|
85
|
+
form = SongForm.new(Song.new)
|
86
|
+
form.validate(album_id: album.id, title: 'How Many Tears').must_equal false
|
87
|
+
form.errors.to_s.must_equal "{:title=>[\"has already been taken\"]}"
|
88
|
+
|
89
|
+
album = Album.new
|
90
|
+
album.save
|
91
|
+
|
92
|
+
form = SongForm.new(Song.new)
|
93
|
+
form.validate(album_id: album.id, title: 'How Many Tears').must_equal true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class UniqueValidatorWithScopeArrayTest < MiniTest::Spec
|
98
|
+
class SongForm < Reform::Form
|
99
|
+
include ActiveRecord
|
100
|
+
property :album_id
|
101
|
+
property :artist_id
|
102
|
+
property :title
|
103
|
+
validates :title, unique: { scope: [:album_id, :artist_id] }
|
104
|
+
end
|
105
|
+
|
106
|
+
it do
|
107
|
+
Song.delete_all
|
108
|
+
|
109
|
+
album1 = Album.new
|
110
|
+
album1.save
|
111
|
+
|
112
|
+
artist1 = Artist.new
|
113
|
+
artist1.save
|
114
|
+
|
115
|
+
form = SongForm.new(Song.new)
|
116
|
+
form.validate(album_id: album1.id, artist_id: artist1.id, title: 'How Many Tears').must_equal true
|
117
|
+
form.save
|
118
|
+
|
119
|
+
form = SongForm.new(Song.new)
|
120
|
+
form.validate(album_id: album1.id, artist_id: artist1.id, title: 'How Many Tears').must_equal false
|
121
|
+
form.errors.to_s.must_equal "{:title=>[\"has already been taken\"]}"
|
122
|
+
|
123
|
+
album2 = Album.new
|
124
|
+
album2.save
|
125
|
+
|
126
|
+
form = SongForm.new(Song.new)
|
127
|
+
form.validate(album_id: album2.id, artist_id: artist1.id, title: 'How Many Tears').must_equal true
|
128
|
+
|
129
|
+
artist2 = Artist.new
|
130
|
+
artist2.save
|
131
|
+
|
132
|
+
form = SongForm.new(Song.new)
|
133
|
+
form.validate(album_id: album1.id, artist_id: artist2.id, title: 'How Many Tears').must_equal true
|
134
|
+
end
|
64
135
|
end
|