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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGES.md +12 -0
  4. data/Gemfile +12 -2
  5. data/README.md +9 -14
  6. data/Rakefile +1 -1
  7. data/database.sqlite3 +0 -0
  8. data/lib/reform.rb +1 -0
  9. data/lib/reform/contract.rb +13 -20
  10. data/lib/reform/contract/validate.rb +9 -7
  11. data/lib/reform/form.rb +45 -31
  12. data/lib/reform/form/active_model.rb +10 -10
  13. data/lib/reform/form/active_model/form_builder_methods.rb +5 -4
  14. data/lib/reform/form/active_model/model_reflections.rb +2 -2
  15. data/lib/reform/form/active_model/model_validations.rb +3 -3
  16. data/lib/reform/form/active_model/validations.rb +49 -32
  17. data/lib/reform/form/dry.rb +55 -0
  18. data/lib/reform/form/lotus.rb +4 -1
  19. data/lib/reform/form/module.rb +3 -17
  20. data/lib/reform/form/multi_parameter_attributes.rb +0 -9
  21. data/lib/reform/form/populator.rb +72 -30
  22. data/lib/reform/form/validate.rb +19 -43
  23. data/lib/reform/form/validation/unique_validator.rb +39 -6
  24. data/lib/reform/validation.rb +40 -0
  25. data/lib/reform/validation/groups.rb +73 -0
  26. data/lib/reform/version.rb +1 -1
  27. data/reform.gemspec +3 -1
  28. data/test/active_record_test.rb +2 -0
  29. data/test/contract_test.rb +2 -2
  30. data/test/deprecation_test.rb +27 -0
  31. data/test/deserialize_test.rb +29 -8
  32. data/test/dummy/config/locales/en.yml +4 -1
  33. data/test/errors_test.rb +4 -4
  34. data/test/feature_test.rb +2 -2
  35. data/test/fixtures/dry_error_messages.yml +43 -0
  36. data/test/form_builder_test.rb +10 -8
  37. data/test/form_test.rb +1 -36
  38. data/test/inherit_test.rb +20 -8
  39. data/test/module_test.rb +2 -30
  40. data/test/parse_pipeline_test.rb +15 -0
  41. data/test/populate_test.rb +41 -12
  42. data/test/populator_skip_test.rb +28 -0
  43. data/test/reform_test.rb +1 -1
  44. data/test/skip_if_test.rb +10 -3
  45. data/test/test_helper.rb +11 -2
  46. data/test/unique_test.rb +72 -1
  47. data/test/validate_test.rb +6 -7
  48. data/test/validation/activemodel_validation_test.rb +252 -0
  49. data/test/validation/dry_validation_test.rb +330 -0
  50. metadata +63 -10
  51. data/lib/reform/schema.rb +0 -13
@@ -143,7 +143,7 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
143
143
  end
144
144
  end
145
145
 
146
- class ValidateWithDeserializerOptionTest < MiniTest::Spec
146
+ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
147
147
  Song = Struct.new(:title, :album, :composer)
148
148
  Album = Struct.new(:name, :songs, :artist)
149
149
  Artist = Struct.new(:name)
@@ -153,21 +153,20 @@ class ValidateWithDeserializerOptionTest < MiniTest::Spec
153
153
  validates :name, presence: true
154
154
 
155
155
  collection :songs,
156
- deserializer: {instance: lambda { |fragment, index, options|
157
- collection = options.binding.get
158
- (item = collection[index]) ? item : collection.insert(index, Song.new) },
159
- setter: nil} do
156
+ internal_populator: lambda { |input, options|
157
+ collection = options[:represented].songs
158
+ (item = collection[options[:index]]) ? item : collection.insert(options[:index], Song.new) } do
160
159
 
161
160
  property :title
162
161
  validates :title, presence: true
163
162
 
164
- property :composer, deserializer: { instance: lambda { |fragment, options| (item = options.binding.get) ? item : Artist.new } } do
163
+ property :composer, internal_populator: lambda { |input, options| (item = options[:represented].composer) ? item : Artist.new } do
165
164
  property :name
166
165
  validates :name, presence: true
167
166
  end
168
167
  end
169
168
 
170
- property :artist, deserializer: { instance: lambda { |fragment, options| (item = options.binding.get) ? item : Artist.new } } do
169
+ property :artist, internal_populator: lambda { |input, options| (item = options[:represented].artist) ? item : Artist.new } do
171
170
  property :name
172
171
  validates :name, presence: true
173
172
  end
@@ -0,0 +1,252 @@
1
+ require "test_helper"
2
+ require "reform/form/lotus"
3
+
4
+ class ActiveModelValidationTest < MiniTest::Spec
5
+ Session = Struct.new(:username, :email, :password, :confirm_password)
6
+ Album = Struct.new(:name, :songs, :artist)
7
+ Artist = Struct.new(:name)
8
+
9
+ class SessionForm < Reform::Form
10
+ include Reform::Form::ActiveModel::Validations
11
+
12
+
13
+ property :username
14
+ property :email
15
+ property :password
16
+ property :confirm_password
17
+
18
+ validation :default do
19
+ validates :username, presence: true
20
+ validates :email, presence: true
21
+ end
22
+
23
+ validation :email, if: :default do
24
+ # validate :email_ok? # FIXME: implement that.
25
+ validates :email, length: {is: 3}
26
+ end
27
+
28
+ validation :nested, if: :default do
29
+ validates :password, presence: true, length: {is: 1}
30
+ end
31
+
32
+ validation :confirm, if: :default, after: :email do
33
+ validates :confirm_password, length: {is: 2}
34
+ end
35
+ end
36
+
37
+ let (:form) { SessionForm.new(Session.new) }
38
+
39
+ # valid.
40
+ it do
41
+ form.validate({username: "Helloween", email: "yep", password: "9", confirm_password:"dd"}).must_equal true
42
+ form.errors.messages.inspect.must_equal "{}"
43
+ end
44
+
45
+ # invalid.
46
+ it do
47
+ form.validate({}).must_equal false
48
+ form.errors.messages.inspect.must_equal "{:username=>[\"can't be blank\"], :email=>[\"can't be blank\"]}"
49
+ end
50
+
51
+ # partially invalid.
52
+ # 2nd group fails.
53
+ let (:character) { self.class.rails4_2? ? :character : :characters}
54
+ it do
55
+ form.validate(username: "Helloween", email: "yo").must_equal false
56
+ form.errors.messages.inspect.must_equal "{:email=>[\"is the wrong length (should be 3 characters)\"], :confirm_password=>[\"is the wrong length (should be 2 characters)\"], :password=>[\"can't be blank\", \"is the wrong length (should be 1 #{character})\"]}"
57
+ end
58
+ # 3rd group fails.
59
+ it do
60
+ form.validate(username: "Helloween", email: "yo!").must_equal false
61
+ form.errors.messages.inspect.must_equal"{:confirm_password=>[\"is the wrong length (should be 2 characters)\"], :password=>[\"can't be blank\", \"is the wrong length (should be 1 #{character})\"]}"
62
+ end
63
+ # 4th group with after: fails.
64
+ it do
65
+ form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9").must_equal false
66
+ form.errors.messages.inspect.must_equal "{:confirm_password=>[\"is the wrong length (should be 2 characters)\"]}"
67
+ end
68
+
69
+
70
+ describe "implicit :default group" do
71
+ # implicit :default group.
72
+ class LoginForm < Reform::Form
73
+ include Reform::Form::ActiveModel::Validations
74
+
75
+
76
+ property :username
77
+ property :email
78
+ property :password
79
+ property :confirm_password
80
+
81
+ validates :username, presence: true
82
+ validates :email, presence: true
83
+ validates :password, presence: true
84
+
85
+ validation :after_default, if: :default do
86
+ validates :confirm_password, presence: true
87
+ end
88
+ end
89
+
90
+ let (:form) { LoginForm.new(Session.new) }
91
+
92
+ # valid.
93
+ it do
94
+ form.validate({username: "Helloween", email: "yep", password: "9", confirm_password: 9}).must_equal true
95
+ form.errors.messages.inspect.must_equal "{}"
96
+ end
97
+
98
+ # invalid.
99
+ it do
100
+ form.validate({password: 9}).must_equal false
101
+ form.errors.messages.inspect.must_equal "{:username=>[\"can't be blank\"], :email=>[\"can't be blank\"]}"
102
+ end
103
+
104
+ # partially invalid.
105
+ # 2nd group fails.
106
+ it do
107
+ form.validate(password: 9).must_equal false
108
+ form.errors.messages.inspect.must_equal "{:username=>[\"can't be blank\"], :email=>[\"can't be blank\"]}"
109
+ end
110
+ end
111
+
112
+
113
+ # describe "overwriting a group" do
114
+ # class OverwritingForm < Reform::Form
115
+ # include Reform::Form::ActiveModel::Validations
116
+
117
+ # property :username
118
+ # property :email
119
+
120
+ # validation :email do
121
+ # validates :email, presence: true # is not considered, but overwritten.
122
+ # end
123
+
124
+ # validation :email do # overwrites the above.
125
+ # validates :username, presence: true
126
+ # end
127
+ # end
128
+
129
+ # let (:form) { OverwritingForm.new(Session.new) }
130
+
131
+ # # valid.
132
+ # it do
133
+ # form.validate({username: "Helloween"}).must_equal true
134
+ # end
135
+
136
+ # # invalid.
137
+ # it do
138
+ # form.validate({}).must_equal false
139
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username can't be blank\"]}"
140
+ # end
141
+ # end
142
+
143
+
144
+ describe "inherit: true in same group" do
145
+ class InheritSameGroupForm < Reform::Form
146
+ include Reform::Form::ActiveModel::Validations
147
+
148
+ property :username
149
+ property :email
150
+
151
+ validation :email do
152
+ validates :email, presence: true
153
+ end
154
+
155
+ validation :email, inherit: true do # extends the above.
156
+ validates :username, presence: true
157
+ end
158
+ end
159
+
160
+ let (:form) { InheritSameGroupForm.new(Session.new) }
161
+
162
+ # valid.
163
+ it do
164
+ form.validate({username: "Helloween", email: 9}).must_equal true
165
+ end
166
+
167
+ # invalid.
168
+ it do
169
+ form.validate({}).must_equal false
170
+ form.errors.messages.inspect.must_equal "{:email=>[\"can't be blank\"], :username=>[\"can't be blank\"]}"
171
+ end
172
+ end
173
+
174
+
175
+ describe "if: with lambda" do
176
+ class IfWithLambdaForm < Reform::Form
177
+ include Reform::Form::ActiveModel::Validations
178
+
179
+ property :username
180
+ property :email
181
+ property :password
182
+
183
+ validation :email do
184
+ validates :email, presence: true
185
+ end
186
+
187
+ # run this is :email group is true.
188
+ validation :after_email, if: lambda { |results| results[:email]==true } do # extends the above.
189
+ validates :username, presence: true
190
+ end
191
+
192
+ # block gets evaled in form instance context.
193
+ validation :password, if: lambda { |results| email == "john@trb.org" } do
194
+ validates :password, presence: true
195
+ end
196
+ end
197
+
198
+ let (:form) { IfWithLambdaForm.new(Session.new) }
199
+
200
+ # valid.
201
+ it do
202
+ form.validate({username: "Strung Out", email: 9}).must_equal true
203
+ end
204
+
205
+ # invalid.
206
+ it do
207
+ form.validate({email: 9}).must_equal false
208
+ form.errors.messages.inspect.must_equal "{:username=>[\"can't be blank\"]}"
209
+ end
210
+ end
211
+
212
+
213
+ # TODO: describe "multiple errors for property" do
214
+
215
+ describe "::validate" do
216
+ class ValidateForm < Reform::Form
217
+ include Reform::Form::ActiveModel::Validations
218
+
219
+ property :username
220
+ validates :username, presence: true
221
+ validate :username_ok?#, context: :entity
222
+
223
+ def username_ok?#(value)
224
+ errors.add(:username, "not ok") if username == "yo"
225
+ end
226
+ end
227
+
228
+ let (:form) { ValidateForm.new(Session.new) }
229
+
230
+ # invalid.
231
+ it do
232
+ form.validate({username: "yo"}).must_equal false
233
+ form.errors.messages.inspect.must_equal "{:username=>[\"not ok\"]}"
234
+ end
235
+
236
+ # valid.
237
+ it do
238
+ form.validate({username: "not yo"}).must_equal true
239
+ form.errors.empty?.must_equal true
240
+ end
241
+ end
242
+
243
+
244
+ describe "validates: :acceptance" do
245
+ class AcceptanceForm < Reform::Form
246
+ property :accept, virtual: true, validates: { acceptance: true }
247
+ end
248
+
249
+ it { AcceptanceForm.new(nil).validate(accept: "0").must_equal false }
250
+ it { AcceptanceForm.new(nil).validate(accept: "1").must_equal true }
251
+ end
252
+ end
@@ -0,0 +1,330 @@
1
+ require "test_helper"
2
+ require "reform/form/dry"
3
+
4
+ class ValidationGroupsTest < MiniTest::Spec
5
+
6
+ describe "basic validations" do
7
+ Session = Struct.new(:username, :email, :password, :confirm_password)
8
+
9
+ class SessionForm < Reform::Form
10
+ include Reform::Form::Dry::Validations
11
+
12
+ property :username
13
+ property :email
14
+ property :password
15
+ property :confirm_password
16
+
17
+ validation :default do
18
+ key(:username, &:filled?)
19
+ key(:email, &:filled?)
20
+ end
21
+
22
+ validation :email, if: :default do
23
+ key(:email) do |email|
24
+ email.min_size?(3)
25
+ end
26
+ end
27
+
28
+ validation :nested, if: :default do
29
+ key(:password) do |password|
30
+ password.filled? & password.min_size?(2)
31
+ end
32
+ end
33
+
34
+ validation :confirm, if: :default, after: :email do
35
+ key(:confirm_password) do |confirm_password|
36
+ confirm_password.filled? & confirm_password.min_size?(2)
37
+ end
38
+ end
39
+ end
40
+
41
+ let (:form) { SessionForm.new(Session.new) }
42
+
43
+ # valid.
44
+ it do
45
+ form.validate({ username: "Helloween",
46
+ email: "yep",
47
+ password: "99",
48
+ confirm_password: "99" }).must_equal true
49
+ form.errors.messages.inspect.must_equal "{}"
50
+ end
51
+
52
+ # invalid.
53
+ it do
54
+ form.validate({}).must_equal false
55
+ form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\"], :email=>[\"email must be filled\"]}"
56
+ end
57
+
58
+ # partially invalid.
59
+ # 2nd group fails.
60
+ it do
61
+ form.validate(username: "Helloween", email: "yo", confirm_password:"9").must_equal false
62
+ form.errors.messages.inspect.must_equal "{:email=>[\"email size cannot be less than 3\"], :confirm_password=>[\"confirm_password size cannot be less than 2\"], :password=>[\"password must be filled\"]}"
63
+ end
64
+ # 3rd group fails.
65
+ it do
66
+ form.validate(username: "Helloween", email: "yo!", confirm_password:"9").must_equal false
67
+ form.errors.messages.inspect
68
+ .must_equal "{:confirm_password=>[\"confirm_password size cannot be less than 2\"], :password=>[\"password must be filled\"]}"
69
+ end
70
+ # 4th group with after: fails.
71
+ it do
72
+ form.validate(username: "Helloween", email: "yo!", password: "", confirm_password: "9").must_equal false
73
+ form.errors.messages.inspect.must_equal "{:confirm_password=>[\"confirm_password size cannot be less than 2\"], :password=>[\"password must be filled\"]}"
74
+ end
75
+ end
76
+
77
+
78
+ describe "Nested validations" do
79
+ class AlbumForm < Reform::Form
80
+ include Reform::Form::Dry::Validations
81
+
82
+ property :title
83
+
84
+ property :hit do
85
+ property :title
86
+
87
+ # FIX ME: this doesn't work now, @apotonick said he knows why
88
+ # The error is that this validation block act as an AM:V instead of the Dry one.
89
+ # validation :default do
90
+ # key(:title, &:filled?)
91
+ # end
92
+ end
93
+
94
+ collection :songs do
95
+ property :title
96
+ end
97
+
98
+ property :band do
99
+ property :name
100
+ property :label do
101
+ property :name
102
+ end
103
+ end
104
+
105
+ validation :default do
106
+ configure { |config|
107
+ config.messages_file = 'test/fixtures/dry_error_messages.yml'
108
+ }
109
+
110
+ key(:title) do |title|
111
+ title.filled? & title.good_musical_taste?
112
+ end
113
+
114
+ key(:title, &:form_access_validation?)
115
+
116
+ # message need to be defined on fixtures/dry_error_messages
117
+ # d-v expects you to define your custome messages on the .yml file
118
+ def good_musical_taste?(value)
119
+ value != 'Nickelback'
120
+ end
121
+
122
+ def form_access_validation?(value)
123
+ form.title == 'Reform'
124
+ end
125
+ end
126
+ end
127
+
128
+ let (:album) do
129
+ OpenStruct.new(
130
+ :title => "Blackhawks Over Los Angeles",
131
+ :hit => song,
132
+ :songs => songs,
133
+ :band => Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
134
+ )
135
+ end
136
+ let (:song) { OpenStruct.new(:title => "Downtown") }
137
+ let (:songs) { [ song = OpenStruct.new(:title => "Calling"), song ] }
138
+ let (:form) { AlbumForm.new(album) }
139
+
140
+ # correct #validate.
141
+ it do
142
+ result = form.validate(
143
+ "title" => "Reform",
144
+ "songs" => [
145
+ {"title" => "Fallout"},
146
+ {"title" => "Roxanne", "composer" => {"name" => "Sting"}}
147
+ ],
148
+ "band" => {"label" => {"name" => "Epitaph"}},
149
+ )
150
+
151
+ result.must_equal true
152
+ form.errors.messages.inspect.must_equal "{}"
153
+ end
154
+ end
155
+
156
+
157
+ describe "fails with :validate, :validates and :validates_with" do
158
+
159
+ it "throws a goddamn error" do
160
+ e = proc do
161
+ class FailingForm < Reform::Form
162
+ include Reform::Form::Dry::Validations
163
+
164
+ property :username
165
+
166
+ validation :email do
167
+ validates(:email, &:filled?)
168
+ end
169
+ end
170
+ end.must_raise
171
+ # e.message.must_equal 'validates() is not supported by Dry Validation backend.'
172
+
173
+ e = proc do
174
+ class FailingForm < Reform::Form
175
+ include Reform::Form::Dry::Validations
176
+
177
+ property :username
178
+
179
+ validation :email do
180
+ validate(:email, &:filled?)
181
+ end
182
+ end
183
+ end.must_raise(NoMethodError)
184
+ # e.message.must_equal 'validate() is not supported by Dry Validation backend.'
185
+
186
+ e = proc do
187
+ class FailingForm < Reform::Form
188
+ include Reform::Form::Dry::Validations
189
+
190
+ property :username
191
+
192
+ validation :email do
193
+ validates_with(:email, &:filled?)
194
+ end
195
+ end
196
+ end.must_raise
197
+ # e.message.must_equal 'validates_with() is not supported by Dry Validation backend.'
198
+ end
199
+ end
200
+
201
+
202
+ # describe "same-named group" do
203
+ # class OverwritingForm < Reform::Form
204
+ # include Reform::Form::Dry::Validations
205
+
206
+ # property :username
207
+ # property :email
208
+
209
+ # validation :email do # FIX ME: is this working for other validator or just bugging here?
210
+ # key(:email, &:filled?) # it's not considered, overitten
211
+ # end
212
+
213
+ # validation :email do # just another group.
214
+ # key(:username, &:filled?)
215
+ # end
216
+ # end
217
+
218
+ # let (:form) { OverwritingForm.new(Session.new) }
219
+
220
+ # # valid.
221
+ # it do
222
+ # form.validate({username: "Helloween"}).must_equal true
223
+ # end
224
+
225
+ # # invalid.
226
+ # it "whoo" do
227
+ # form.validate({}).must_equal false
228
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username can't be blank\"]}"
229
+ # end
230
+ # end
231
+
232
+
233
+ describe "inherit: true in same group" do
234
+ class InheritSameGroupForm < Reform::Form
235
+ include Reform::Form::Dry::Validations
236
+
237
+ property :username
238
+ property :email
239
+
240
+ validation :email do
241
+ key(:email, &:filled?)
242
+ end
243
+
244
+ validation :email, inherit: true do # extends the above.
245
+ key(:username, &:filled?)
246
+ end
247
+ end
248
+
249
+ let (:form) { InheritSameGroupForm.new(Session.new) }
250
+
251
+ # valid.
252
+ it do
253
+ form.validate({username: "Helloween", email: 9}).must_equal true
254
+ end
255
+
256
+ # invalid.
257
+ it do
258
+ form.validate({}).must_equal false
259
+ form.errors.messages.inspect.must_equal "{:email=>[\"email must be filled\"], :username=>[\"username must be filled\"]}"
260
+ end
261
+ end
262
+
263
+
264
+ describe "if: with lambda" do
265
+ class IfWithLambdaForm < Reform::Form
266
+ include Reform::Form::Dry::Validations # ::build_errors.
267
+
268
+ property :username
269
+ property :email
270
+ property :password
271
+
272
+ validation :email do
273
+ # validates :email, presence: true
274
+ key(:email, &:filled?)
275
+ end
276
+
277
+ # run this is :email group is true.
278
+ validation :after_email, if: lambda { |results| results[:email]==true } do # extends the above.
279
+ # validates :username, presence: true
280
+ key(:username, &:filled?)
281
+ end
282
+
283
+ # block gets evaled in form instance context.
284
+ validation :password, if: lambda { |results| email == "john@trb.org" } do
285
+ # validates :password, presence: true
286
+ key(:password, &:filled?)
287
+ end
288
+ end
289
+
290
+ let (:form) { IfWithLambdaForm.new(Session.new) }
291
+
292
+ # valid.
293
+ it do
294
+ form.validate({username: "Strung Out", email: 9}).must_equal true
295
+ end
296
+
297
+ # invalid.
298
+ it do
299
+ form.validate({email: 9}).must_equal false
300
+ form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\"]}"
301
+ end
302
+ end
303
+
304
+
305
+ # Currenty dry-v don't support that option, it doesn't make sense
306
+ # I've talked to @solnic and he plans to add a "hint" feature to show
307
+ # more errors messages than only those that have failed.
308
+ #
309
+ # describe "multiple errors for property" do
310
+ # class MultipleErrorsForPropertyForm < Reform::Form
311
+ # include Reform::Form::Dry::Validations
312
+
313
+ # property :username
314
+
315
+ # validation :default do
316
+ # key(:username) do |username|
317
+ # username.filled? | (username.min_size?(2) & username.max_size?(3))
318
+ # end
319
+ # end
320
+ # end
321
+
322
+ # let (:form) { MultipleErrorsForPropertyForm.new(Session.new) }
323
+
324
+ # # valid.
325
+ # it do
326
+ # form.validate({username: ""}).must_equal false
327
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\", \"username is not proper size\"]}"
328
+ # end
329
+ # end
330
+ end