reform 2.2.4 → 2.3.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -7
  3. data/CHANGES.md +26 -4
  4. data/CONTRIBUTING.md +31 -0
  5. data/Gemfile +1 -12
  6. data/ISSUE_TEMPLATE.md +25 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +3 -3
  9. data/lib/reform.rb +1 -0
  10. data/lib/reform/contract.rb +1 -11
  11. data/lib/reform/contract/validate.rb +49 -23
  12. data/lib/reform/errors.rb +49 -0
  13. data/lib/reform/form.rb +20 -5
  14. data/lib/reform/form/dry.rb +57 -29
  15. data/lib/reform/form/populator.rb +2 -16
  16. data/lib/reform/form/prepopulate.rb +1 -1
  17. data/lib/reform/form/validate.rb +10 -2
  18. data/lib/reform/result.rb +63 -0
  19. data/lib/reform/validation.rb +19 -13
  20. data/lib/reform/validation/groups.rb +11 -25
  21. data/lib/reform/version.rb +1 -1
  22. data/reform.gemspec +7 -6
  23. data/test/benchmarking.rb +39 -5
  24. data/test/call_test.rb +1 -1
  25. data/test/changed_test.rb +1 -1
  26. data/test/coercion_test.rb +2 -2
  27. data/test/composition_test.rb +47 -9
  28. data/test/contract_test.rb +5 -5
  29. data/test/default_test.rb +1 -1
  30. data/test/deserialize_test.rb +3 -3
  31. data/test/errors_test.rb +36 -21
  32. data/test/feature_test.rb +1 -1
  33. data/test/fixtures/dry_error_messages.yml +70 -23
  34. data/test/form_option_test.rb +3 -3
  35. data/test/form_test.rb +3 -3
  36. data/test/from_test.rb +2 -2
  37. data/test/inherit_test.rb +44 -51
  38. data/test/module_test.rb +12 -12
  39. data/test/parse_option_test.rb +40 -0
  40. data/test/parse_pipeline_test.rb +2 -2
  41. data/test/populate_test.rb +59 -19
  42. data/test/populator_skip_test.rb +9 -8
  43. data/test/prepopulator_test.rb +3 -3
  44. data/test/readable_test.rb +2 -2
  45. data/test/readonly_test.rb +1 -1
  46. data/test/reform_test.rb +16 -31
  47. data/test/save_test.rb +23 -8
  48. data/test/setup_test.rb +2 -2
  49. data/test/skip_if_test.rb +4 -4
  50. data/test/skip_setter_and_getter_test.rb +1 -1
  51. data/test/test_helper.rb +13 -10
  52. data/test/validate_test.rb +18 -18
  53. data/test/validation/dry_validation_test.rb +430 -117
  54. data/test/validation/result_test.rb +79 -0
  55. data/test/validation_library_provided_test.rb +16 -0
  56. data/test/virtual_test.rb +1 -1
  57. data/test/writeable_test.rb +31 -2
  58. metadata +42 -23
  59. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  60. data/lib/reform/contract/errors.rb +0 -43
  61. data/lib/reform/form/mongoid.rb +0 -37
  62. data/lib/reform/form/orm.rb +0 -26
  63. data/lib/reform/mongoid.rb +0 -4
  64. data/test/deprecation_test.rb +0 -27
  65. data/test/validation/dry_test.rb +0 -60
  66. data/test/validation/errors.yml +0 -4
@@ -9,7 +9,7 @@ class ModuleInclusionTest < MiniTest::Spec
9
9
  property :title
10
10
 
11
11
  validation do
12
- key(:title).required
12
+ required(:title).filled
13
13
  end
14
14
 
15
15
  def id # gets mixed into Form, too.
@@ -22,7 +22,7 @@ class ModuleInclusionTest < MiniTest::Spec
22
22
  end
23
23
 
24
24
  validation do
25
- key(:band).required
25
+ required(:band).filled
26
26
  end
27
27
 
28
28
  include Dry::Types.module # allows using Types::* in module.
@@ -36,22 +36,22 @@ class ModuleInclusionTest < MiniTest::Spec
36
36
  collection :airplays do
37
37
  property :station
38
38
  validation do
39
- key(:station).required
39
+ required(:station).filled
40
40
  end
41
41
  end
42
42
  validation do
43
- key(:airplays).required
43
+ required(:airplays).filled
44
44
  end
45
45
  end
46
46
 
47
47
 
48
48
  # test:
49
49
  # by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
50
- class HitForm < Reform::Form
50
+ class HitForm < TestForm
51
51
  include BandPropertyForm
52
52
  end
53
53
 
54
- class SongForm < Reform::Form
54
+ class SongForm < TestForm
55
55
  include Coercion
56
56
  property :title
57
57
 
@@ -72,7 +72,7 @@ class ModuleInclusionTest < MiniTest::Spec
72
72
  it do
73
73
  form = SongForm.new(OpenStruct.new)
74
74
  form.validate({})
75
- form.errors.messages.must_equal({:band=>["is missing"]})
75
+ form.errors.messages.must_equal({:band=>["must be filled"]})
76
76
  end
77
77
 
78
78
  # coercion works
@@ -90,18 +90,18 @@ class ModuleInclusionTest < MiniTest::Spec
90
90
 
91
91
  property :name
92
92
  validation do
93
- key(:name).required
93
+ required(:name).filled
94
94
  end
95
95
  end
96
96
 
97
- class AlbumForm < Reform::Form
97
+ class AlbumForm < TestForm
98
98
  include AlbumFormModule
99
99
 
100
100
  # pp heritage
101
101
  property :band, :inherit => true do
102
102
  property :label
103
103
  validation do
104
- key(:label).required
104
+ required(:label).filled
105
105
  end
106
106
  end
107
107
  end
@@ -109,7 +109,7 @@ class ModuleInclusionTest < MiniTest::Spec
109
109
  it do
110
110
  form = AlbumForm.new(OpenStruct.new(:band => OpenStruct.new))
111
111
  form.validate({"band" => {}})
112
- form.errors.messages.must_equal({:band=>["must be filled"], :"band.title"=>["is missing"], :"band.label"=>["is missing"], :name=>["is missing"]})
112
+ form.errors.messages.must_equal({:"band.title"=>["must be filled"], :"band.label"=>["must be filled"], :name=>["must be filled"]})
113
113
  end
114
114
 
115
115
 
@@ -127,7 +127,7 @@ class ModuleInclusionTest < MiniTest::Spec
127
127
  end
128
128
  end
129
129
 
130
- class IncludingSongForm < Reform::Form
130
+ class IncludingSongForm < TestForm
131
131
  include SongModule
132
132
  end
133
133
 
@@ -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
@@ -3,8 +3,8 @@ require "test_helper"
3
3
  class ParsePipelineTest < MiniTest::Spec
4
4
  Album = Struct.new(:name)
5
5
 
6
- class AlbumForm < Reform::Form
7
- property :name, deserializer: { parse_pipeline: ->(input, options) { Representable::Pipeline[->(input, options) { options[:represented].name = input.inspect }] } }
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
@@ -5,27 +5,25 @@ class PopulatorTest < MiniTest::Spec
5
5
  Album = Struct.new(:name, :songs, :artist)
6
6
  Artist = Struct.new(:name)
7
7
 
8
- class AlbumForm < Reform::Form
8
+ class AlbumForm < TestForm
9
9
  property :name, populator: ->(options) { self.name = options[:fragment].reverse }
10
10
  validation do
11
- key(:name).required
11
+ required(:name).filled
12
12
  end
13
13
 
14
14
  collection :songs,
15
- populator: ->(options) {
16
- fragment, collection, index = options[:fragment], options[:model], options[:index]
17
-
18
- (item = collection[index]) ? item : collection.insert(index, Song.new) } do
15
+ populator: ->(fragment:, model:, index:, **) {
16
+ (item = model[index]) ? item : model.insert(index, Song.new) } do
19
17
 
20
18
  property :title
21
19
  validation do
22
- key(:title).required
20
+ required(:title).filled
23
21
  end
24
22
 
25
23
  property :composer, populator: ->(options) { options[:model] || self.composer= Artist.new } do
26
24
  property :name
27
25
  validation do
28
- key(:name).required
26
+ required(:name).filled
29
27
  end
30
28
  end
31
29
  end
@@ -107,7 +105,7 @@ end
107
105
  class PopulateWithMethodTest < Minitest::Spec
108
106
  Album = Struct.new(:title)
109
107
 
110
- class AlbumForm < Reform::Form
108
+ class AlbumForm < TestForm
111
109
  property :title, populator: :title!
112
110
 
113
111
  def title!(options)
@@ -118,9 +116,51 @@ class PopulateWithMethodTest < Minitest::Spec
118
116
  let (:form) { AlbumForm.new(Album.new) }
119
117
 
120
118
  it "runs populator method" do
121
- form.validate(
122
- "title" => "override me!"
123
- )
119
+ form.validate("title" => "override me!")
120
+
121
+ form.title.must_equal "!em edirrevo"
122
+ end
123
+ end
124
+
125
+ class PopulateWithCallableTest < Minitest::Spec
126
+ Album = Struct.new(:title)
127
+
128
+ class TitlePopulator
129
+ include Uber::Callable
130
+
131
+ def call(form, options)
132
+ form.title = options[:fragment].reverse
133
+ end
134
+ end
135
+
136
+ class AlbumForm < TestForm
137
+ property :title, populator: TitlePopulator.new
138
+ end
139
+
140
+ let (:form) { AlbumForm.new(Album.new) }
141
+
142
+ it "runs populator method" do
143
+ form.validate("title" => "override me!")
144
+
145
+ form.title.must_equal "!em edirrevo"
146
+ end
147
+ end
148
+
149
+ class PopulateWithProcTest < Minitest::Spec
150
+ Album = Struct.new(:title)
151
+
152
+ TitlePopulator = ->(options) do
153
+ options[:represented].title = options[:fragment].reverse
154
+ end
155
+
156
+ class AlbumForm < TestForm
157
+ property :title, populator: TitlePopulator
158
+ end
159
+
160
+ let (:form) { AlbumForm.new(Album.new) }
161
+
162
+ it "runs populator method" do
163
+ form.validate("title" => "override me!")
124
164
 
125
165
  form.title.must_equal "!em edirrevo"
126
166
  end
@@ -139,7 +179,7 @@ class PopulateIfEmptyTest < MiniTest::Spec
139
179
 
140
180
 
141
181
 
142
- class AlbumForm < Reform::Form
182
+ class AlbumForm < TestForm
143
183
  property :name
144
184
 
145
185
  collection :songs,
@@ -147,18 +187,18 @@ class PopulateIfEmptyTest < MiniTest::Spec
147
187
 
148
188
  property :title
149
189
  validation do
150
- key(:title).required
190
+ required(:title).filled
151
191
  end
152
192
 
153
193
  property :composer, populate_if_empty: :populate_composer! do # lambda works, too. in form context.
154
194
  property :name
155
195
  validation do
156
- key(:name).required
196
+ required(:name).filled
157
197
  end
158
198
  end
159
199
 
160
200
  private
161
- def populate_composer!(fragment, options)
201
+ def populate_composer!(options)
162
202
  Artist.new
163
203
  end
164
204
  end
@@ -222,7 +262,7 @@ class PopulateIfEmptyTest < MiniTest::Spec
222
262
  # test lambda block arguments.
223
263
  form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
224
264
 
225
- album.artist.must_equal nil
265
+ assert_nil album.artist
226
266
  end
227
267
  end
228
268
 
@@ -237,7 +277,7 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
237
277
  let (:album) { Album.new("The Dissent Of Man", [song, song2]) }
238
278
 
239
279
 
240
- class AlbumForm < Reform::Form
280
+ class AlbumForm < TestForm
241
281
  property :name
242
282
 
243
283
  collection :songs,
@@ -245,7 +285,7 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
245
285
 
246
286
  property :title
247
287
  validation do
248
- key(:title).required
288
+ required(:title).filled
249
289
  end
250
290
  end
251
291
 
@@ -5,13 +5,14 @@ class PopulatorSkipTest < MiniTest::Spec
5
5
  Song = Struct.new(:title)
6
6
 
7
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
8
+ class AlbumForm < TestForm
9
+ collection :songs, populator: :my_populator do
10
+ property :title
11
+ end
12
+
13
+ def my_populator(options)
14
+ return skip! if options[:fragment][:title] == "Good"
15
+ songs[options[:index]]
15
16
  end
16
17
  end
17
18
 
@@ -22,7 +23,7 @@ class PopulatorSkipTest < MiniTest::Spec
22
23
  form.validate(hash)
23
24
 
24
25
  form.songs.size.must_equal 2
25
- form.songs[0].title.must_equal nil
26
+ assert_nil form.songs[0].title
26
27
  form.songs[1].title.must_equal "Bad"
27
28
  end
28
29
  end
@@ -4,7 +4,7 @@ class PrepopulatorTest < MiniTest::Spec
4
4
  Song = Struct.new(:title, :band, :length)
5
5
  Band = Struct.new(:name)
6
6
 
7
- class AlbumForm < Reform::Form
7
+ class AlbumForm < TestForm
8
8
  property :title, prepopulator: ->(*){ self.title = "Another Day At Work" } # normal assignment.
9
9
  property :length
10
10
 
@@ -63,7 +63,7 @@ end
63
63
  class PrepopulateWithoutConfiguration < MiniTest::Spec
64
64
  Song = Struct.new(:title)
65
65
 
66
- class AlbumForm < Reform::Form
66
+ class AlbumForm < TestForm
67
67
  collection :songs do
68
68
  property :title
69
69
  end
@@ -83,7 +83,7 @@ class ManualPrepopulatorOverridingTest < MiniTest::Spec
83
83
  Song = Struct.new(:title, :band, :length)
84
84
  Band = Struct.new(:name)
85
85
 
86
- class AlbumForm < Reform::Form
86
+ class AlbumForm < TestForm
87
87
  property :title
88
88
  property :length
89
89
 
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  class ReadableTest < MiniTest::Spec
4
4
  Credentials = Struct.new(:password)
5
5
 
6
- class PasswordForm < Reform::Form
6
+ class PasswordForm < TestForm
7
7
  property :password, readable: false
8
8
  end
9
9
 
@@ -11,7 +11,7 @@ class ReadableTest < MiniTest::Spec
11
11
  let (:form) { PasswordForm.new(cred) }
12
12
 
13
13
  it {
14
- form.password.must_equal nil # password not read.
14
+ assert_nil form.password # password not read.
15
15
 
16
16
  form.validate("password" => "123")
17
17
 
@@ -1,7 +1,7 @@
1
1
  require "test_helper"
2
2
 
3
3
  class ReadonlyTest < MiniTest::Spec
4
- class SongForm < Reform::Form
4
+ class SongForm < TestForm
5
5
  property :artist
6
6
  property :title, writeable: false
7
7
  # TODO: what to do with virtual values?
@@ -6,12 +6,12 @@ class ReformTest < Minitest::Spec
6
6
 
7
7
  let (:form) { SongForm.new(comp) }
8
8
 
9
- class SongForm < Reform::Form
9
+ class SongForm < TestForm
10
10
  property :name
11
11
  property :title
12
12
 
13
13
  validation do
14
- key(:name).required
14
+ required(:name).filled
15
15
  end
16
16
  end
17
17
 
@@ -19,7 +19,7 @@ class ReformTest < Minitest::Spec
19
19
  let (:comp) { OpenStruct.new }
20
20
 
21
21
  it "returns empty fields" do
22
- form.title.must_equal nil
22
+ assert_nil form.title
23
23
  form.name.must_equal nil
24
24
  end
25
25
 
@@ -27,7 +27,7 @@ class ReformTest < Minitest::Spec
27
27
  it "returns filled-out fields" do
28
28
  form.validate("name" => "Duran Duran")
29
29
 
30
- form.title.must_equal nil
30
+ assert_nil form.title
31
31
  form.name.must_equal "Duran Duran"
32
32
  end
33
33
  end
@@ -63,18 +63,18 @@ class ReformTest < Minitest::Spec
63
63
  it "doesn't change model properties" do
64
64
  form.validate("name" => "Duran Duran")
65
65
 
66
- comp.name.must_equal nil # don't touch model, yet.
66
+ assert_nil comp.name # don't touch model, yet.
67
67
  end
68
68
 
69
69
  # TODO: test errors. test valid.
70
70
  describe "invalid input" do
71
- class ValidatingForm < Reform::Form
71
+ class ValidatingForm < TestForm
72
72
  property :name
73
73
  property :title
74
74
 
75
75
  validation do
76
- key(:name).required
77
- key(:title).required
76
+ required(:name).filled
77
+ required(:title).filled
78
78
  end
79
79
  end
80
80
  let (:form) { ValidatingForm.new(comp) }
@@ -85,26 +85,11 @@ class ReformTest < Minitest::Spec
85
85
 
86
86
  it "populates errors" do
87
87
  form.validate({})
88
- form.errors.messages.must_equal({:name=>["is missing"], :title=>["is missing"]})
88
+ form.errors.messages.must_equal({:name=>["must be filled"], :title=>["must be filled"]})
89
89
  end
90
90
  end
91
91
  end
92
92
 
93
- # FIXME: add this test to reform-rails.
94
- describe "#errors" do
95
- before { form.validate({})}
96
-
97
- it { form.errors.must_be_kind_of Reform::Contract::Errors }
98
-
99
- it { form.errors.messages.must_equal({}) }
100
-
101
- it do
102
- form.validate({"name"=>""})
103
- form.errors.messages.must_equal({:name=>["must be filled"]})
104
- end
105
- end
106
-
107
-
108
93
  describe "#save" do
109
94
  let (:comp) { OpenStruct.new }
110
95
  let (:form) { SongForm.new(comp) }
@@ -115,7 +100,7 @@ class ReformTest < Minitest::Spec
115
100
  form.save
116
101
 
117
102
  comp.name.must_equal "Diesel Boy"
118
- comp.title.must_equal nil
103
+ assert_nil comp.title
119
104
  end
120
105
 
121
106
  describe "#save with block" do
@@ -126,7 +111,7 @@ class ReformTest < Minitest::Spec
126
111
  hash = map
127
112
  end
128
113
 
129
- hash.must_equal({"name"=>"Diesel Boy"})
114
+ hash.must_equal({"name"=>"Diesel Boy", "title" => nil})
130
115
  end
131
116
  end
132
117
  end
@@ -141,7 +126,7 @@ class ReformTest < Minitest::Spec
141
126
  class HitForm < SongForm
142
127
  property :position
143
128
  validation do
144
- key(:position).required
129
+ required(:position).filled
145
130
  end
146
131
  end
147
132
 
@@ -149,15 +134,15 @@ class ReformTest < Minitest::Spec
149
134
  it do
150
135
  form.validate({"title" => "The Body"})
151
136
  form.title.must_equal "The Body"
152
- form.position.must_equal nil
153
- form.errors.messages.must_equal({:name=>["is missing"], :position=>["is missing"]})
137
+ assert_nil form.position
138
+ form.errors.messages.must_equal({:name=>["must be filled"], :position=>["must be filled"]})
154
139
  end
155
140
  end
156
141
  end
157
142
 
158
143
 
159
144
  class OverridingAccessorsTest < BaseTest
160
- class SongForm < Reform::Form
145
+ class SongForm < TestForm
161
146
  property :title
162
147
 
163
148
  def title=(v) # used in #validate.
@@ -200,7 +185,7 @@ end
200
185
 
201
186
 
202
187
  class MethodInFormTest < MiniTest::Spec
203
- class AlbumForm < Reform::Form
188
+ class AlbumForm < TestForm
204
189
  property :title
205
190
 
206
191
  def title