reform 2.2.4 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +11 -6
  4. data/Appraisals +8 -0
  5. data/CHANGES.md +57 -4
  6. data/CONTRIBUTING.md +31 -0
  7. data/Gemfile +2 -16
  8. data/ISSUE_TEMPLATE.md +25 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +5 -7
  11. data/Rakefile +16 -9
  12. data/gemfiles/0.13.0.gemfile +8 -0
  13. data/gemfiles/1.5.0.gemfile +9 -0
  14. data/lib/reform.rb +1 -0
  15. data/lib/reform/contract.rb +7 -17
  16. data/lib/reform/contract/custom_error.rb +41 -0
  17. data/lib/reform/contract/validate.rb +53 -23
  18. data/lib/reform/errors.rb +61 -0
  19. data/lib/reform/form.rb +36 -10
  20. data/lib/reform/form/call.rb +1 -1
  21. data/lib/reform/form/composition.rb +2 -2
  22. data/lib/reform/form/dry.rb +10 -58
  23. data/lib/reform/form/dry/input_hash.rb +37 -0
  24. data/lib/reform/form/dry/new_api.rb +45 -0
  25. data/lib/reform/form/dry/old_api.rb +61 -0
  26. data/lib/reform/form/populator.rb +11 -27
  27. data/lib/reform/form/prepopulate.rb +4 -3
  28. data/lib/reform/form/validate.rb +28 -13
  29. data/lib/reform/result.rb +90 -0
  30. data/lib/reform/validation.rb +19 -11
  31. data/lib/reform/validation/groups.rb +12 -27
  32. data/lib/reform/version.rb +1 -1
  33. data/reform.gemspec +14 -13
  34. data/test/benchmarking.rb +39 -6
  35. data/test/call_new_api.rb +23 -0
  36. data/test/call_old_api.rb +23 -0
  37. data/test/changed_test.rb +14 -14
  38. data/test/coercion_test.rb +57 -25
  39. data/test/composition_new_api.rb +186 -0
  40. data/test/composition_old_api.rb +184 -0
  41. data/test/contract/custom_error_test.rb +55 -0
  42. data/test/contract_new_api.rb +77 -0
  43. data/test/contract_old_api.rb +77 -0
  44. data/test/default_test.rb +4 -4
  45. data/test/deserialize_test.rb +17 -20
  46. data/test/errors_new_api.rb +225 -0
  47. data/test/errors_old_api.rb +230 -0
  48. data/test/feature_test.rb +10 -12
  49. data/test/fixtures/dry_error_messages.yml +73 -23
  50. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  51. data/test/form_new_api.rb +57 -0
  52. data/test/{form_test.rb → form_old_api.rb} +8 -8
  53. data/test/form_option_new_api.rb +24 -0
  54. data/test/{form_option_test.rb → form_option_old_api.rb} +5 -5
  55. data/test/from_test.rb +18 -22
  56. data/test/inherit_new_api.rb +105 -0
  57. data/test/inherit_old_api.rb +105 -0
  58. data/test/{module_test.rb → module_new_api.rb} +26 -31
  59. data/test/module_old_api.rb +146 -0
  60. data/test/parse_option_test.rb +40 -0
  61. data/test/parse_pipeline_test.rb +4 -4
  62. data/test/populate_new_api.rb +304 -0
  63. data/test/populate_old_api.rb +304 -0
  64. data/test/populator_skip_test.rb +11 -11
  65. data/test/prepopulator_test.rb +23 -24
  66. data/test/read_only_test.rb +12 -1
  67. data/test/readable_test.rb +9 -9
  68. data/test/reform_new_api.rb +204 -0
  69. data/test/{reform_test.rb → reform_old_api.rb} +44 -65
  70. data/test/save_new_api.rb +101 -0
  71. data/test/save_old_api.rb +101 -0
  72. data/test/setup_test.rb +17 -17
  73. data/test/skip_if_new_api.rb +85 -0
  74. data/test/skip_if_old_api.rb +92 -0
  75. data/test/skip_setter_and_getter_test.rb +9 -10
  76. data/test/test_helper.rb +25 -14
  77. data/test/validate_new_api.rb +453 -0
  78. data/test/{validate_test.rb → validate_old_api.rb} +121 -131
  79. data/test/validation/dry_validation_new_api.rb +835 -0
  80. data/test/validation/dry_validation_old_api.rb +772 -0
  81. data/test/validation/result_test.rb +77 -0
  82. data/test/validation_library_provided_test.rb +16 -0
  83. data/test/virtual_test.rb +47 -7
  84. data/test/writeable_test.rb +38 -9
  85. metadata +111 -56
  86. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  87. data/lib/reform/contract/errors.rb +0 -43
  88. data/lib/reform/form/mongoid.rb +0 -37
  89. data/lib/reform/form/orm.rb +0 -26
  90. data/lib/reform/mongoid.rb +0 -4
  91. data/test/call_test.rb +0 -23
  92. data/test/composition_test.rb +0 -149
  93. data/test/contract_test.rb +0 -77
  94. data/test/deprecation_test.rb +0 -27
  95. data/test/errors_test.rb +0 -165
  96. data/test/inherit_test.rb +0 -119
  97. data/test/populate_test.rb +0 -270
  98. data/test/readonly_test.rb +0 -14
  99. data/test/save_test.rb +0 -89
  100. data/test/skip_if_test.rb +0 -74
  101. data/test/validation/dry_test.rb +0 -60
  102. data/test/validation/dry_validation_test.rb +0 -352
  103. data/test/validation/errors.yml +0 -4
@@ -1,119 +0,0 @@
1
- require 'test_helper'
2
- require 'representable/json'
3
-
4
- class InheritTest < BaseTest
5
- Populator = Reform::Form::Populator
6
-
7
- class AlbumForm < Reform::Form
8
- property :title, deserializer: {instance: "Instance"}, skip_if: "skip_if in AlbumForm" # allow direct configuration of :deserializer.
9
-
10
- property :hit, populator: "Populator" do
11
- property :title
12
- end
13
-
14
- collection :songs, populate_if_empty: lambda {}, skip_if: :all_blank do
15
- property :title
16
- end
17
-
18
- property :artist, populate_if_empty: lambda {} do
19
-
20
- def artist_id
21
- 1
22
- end
23
- end
24
- end
25
-
26
- puts
27
- puts "inherit"
28
-
29
- class CompilationForm < AlbumForm
30
- property :title, inherit: true, skip_if: "skip_if from CompilationForm"
31
- puts "[#{options_for(:title)[:deserializer].object_id}] COM@@@@@ #{options_for(:title)[:deserializer].inspect}"
32
- # property :hit, :inherit => true do
33
- # property :rating
34
- # validates :title, :rating, :presence => true
35
- # end
36
-
37
- # puts representer_class.representable_attrs.
38
- # get(:hit)[:extend].evaluate(nil).new(OpenStruct.new).rating
39
-
40
- # NO collection here, this is entirely inherited.
41
- # collection :songs, ..
42
-
43
- property :artist, inherit: true do # inherit everything, but explicitely.
44
- end
45
-
46
- # completely override.
47
- property :hit, skip_if: "SkipParse" do
48
- end
49
-
50
- # override partly.
51
- end
52
-
53
- let (:album) { Album.new(nil, OpenStruct.new(:hit => OpenStruct.new()) ) }
54
- subject { CompilationForm.new(album) }
55
-
56
-
57
- # valid.
58
- # it {
59
- # subject.validate("hit" => {"title" => "LA Drone", "rating" => 10})
60
- # subject.hit.title.must_equal "LA Drone"
61
- # subject.hit.rating.must_equal 10
62
- # subject.errors.messages.must_equal({})
63
- # }
64
-
65
- # it do
66
- # subject.validate({})
67
- # subject.hit.title.must_equal nil
68
- # subject.hit.rating.must_equal nil
69
- # subject.errors.messages.must_equal({:"hit.title"=>["can't be blank"], :"hit.rating"=>["can't be blank"]})
70
- # end
71
-
72
- require "pp"
73
-
74
- it "xxx" do
75
- # sub hashes like :deserializer must be properly cloned when inheriting.
76
- AlbumForm.options_for(:title)[:deserializer].object_id.wont_equal CompilationForm.options_for(:title)[:deserializer].object_id
77
-
78
- # don't overwrite direct deserializer: {} configuration.
79
- AlbumForm.options_for(:title)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync
80
- AlbumForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if in AlbumForm"
81
-
82
- AlbumForm.options_for(:hit)[:internal_populator].inspect.must_match /Reform::Form::Populator:.+ @user_proc="Populator"/
83
- # AlbumForm.options_for(:hit)[:deserializer][:instance].inspect.must_be_instance_with Reform::Form::Populator, user_proc: "Populator"
84
-
85
-
86
- AlbumForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
87
- AlbumForm.options_for(:songs)[:deserializer][:skip_parse].must_be_instance_of Reform::Form::Validate::Skip::AllBlank
88
-
89
- AlbumForm.options_for(:artist)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
90
-
91
-
92
-
93
- CompilationForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if from CompilationForm"
94
- # pp CompilationForm.options_for(:songs)
95
- CompilationForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
96
-
97
-
98
- CompilationForm.options_for(:artist)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
99
-
100
- # completely overwrite inherited.
101
- CompilationForm.options_for(:hit)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync # reset to default.
102
- CompilationForm.options_for(:hit)[:deserializer][:skip_parse].must_equal "SkipParse"
103
-
104
-
105
- # inherit: true with block will still inherit the original class.
106
- AlbumForm.new(OpenStruct.new(artist: OpenStruct.new)).artist.artist_id.must_equal 1
107
- CompilationForm.new(OpenStruct.new(artist: OpenStruct.new)).artist.artist_id.must_equal 1
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" }
119
- end
@@ -1,270 +0,0 @@
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 < Reform::Form
9
- property :name, populator: ->(options) { self.name = options[:fragment].reverse }
10
- validation do
11
- key(:name).required
12
- end
13
-
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
19
-
20
- property :title
21
- validation do
22
- key(:title).required
23
- end
24
-
25
- property :composer, populator: ->(options) { options[:model] || self.composer= Artist.new } do
26
- property :name
27
- validation do
28
- key(:name).required
29
- end
30
- end
31
- end
32
-
33
- # property :artist, populator: lambda { |fragment, options| (item = options.binding.get) ? item : Artist.new } do
34
- # NOTE: we have to document that model here is the twin!
35
- property :artist, populator: ->(options) { options[:model] || self.artist = Artist.new } do
36
- property :name
37
- end
38
- end
39
-
40
- let (:song) { Song.new("Broken") }
41
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
42
- let (:composer) { Artist.new("Greg Graffin") }
43
- let (:artist) { Artist.new("Bad Religion") }
44
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
45
-
46
- let (:form) { AlbumForm.new(album) }
47
-
48
- it "runs populator on scalar" do
49
- form.validate(
50
- "name" => "override me!"
51
- )
52
-
53
- form.name.must_equal "!em edirrevo"
54
- end
55
-
56
- # changing existing property :artist.
57
- # TODO: check with artist==nil
58
- it do
59
- old_id = artist.object_id
60
-
61
- form.validate(
62
- "artist" => {"name" => "Marcus Miller"}
63
- )
64
-
65
- form.artist.model.object_id.must_equal old_id
66
- end
67
-
68
- # use populator for default value on scalars?
69
-
70
- # adding to collection via :populator.
71
- # valid.
72
- it "yyy" do
73
- form.validate(
74
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
75
- {"title" => "Rime Of The Ancient Mariner"}, # new song.
76
- {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
77
- ).must_equal true
78
-
79
- form.errors.messages.inspect.must_equal "{}"
80
-
81
- # form has updated.
82
- form.name.must_equal "The Dissent Of Man"
83
- form.songs[0].title.must_equal "Fallout"
84
- form.songs[1].title.must_equal "Roxanne"
85
- form.songs[1].composer.name.must_equal "Greg Graffin"
86
-
87
- form.songs[1].composer.model.must_be_instance_of Artist
88
-
89
- form.songs[1].title.must_equal "Roxanne"
90
- form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
91
- form.songs[3].title.must_equal "Re-Education"
92
- form.songs[3].composer.name.must_equal "Rise Against"
93
- form.songs.size.must_equal 4
94
- form.artist.name.must_equal "Bad Religion"
95
-
96
-
97
- # model has not changed, yet.
98
- album.name.must_equal "The Dissent Of Man"
99
- album.songs[0].title.must_equal "Broken"
100
- album.songs[1].title.must_equal "Resist Stance"
101
- album.songs[1].composer.name.must_equal "Greg Graffin"
102
- album.songs.size.must_equal 2
103
- album.artist.name.must_equal "Bad Religion"
104
- end
105
- end
106
-
107
- class PopulateWithMethodTest < Minitest::Spec
108
- Album = Struct.new(:title)
109
-
110
- class AlbumForm < Reform::Form
111
- property :title, populator: :title!
112
-
113
- def title!(options)
114
- self.title = options[:fragment].reverse
115
- end
116
- end
117
-
118
- let (:form) { AlbumForm.new(Album.new) }
119
-
120
- it "runs populator method" do
121
- form.validate(
122
- "title" => "override me!"
123
- )
124
-
125
- form.title.must_equal "!em edirrevo"
126
- end
127
- end
128
-
129
- class PopulateIfEmptyTest < MiniTest::Spec
130
- Song = Struct.new(:title, :album, :composer)
131
- Album = Struct.new(:name, :songs, :artist)
132
- Artist = Struct.new(:name)
133
-
134
- let (:song) { Song.new("Broken") }
135
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
136
- let (:composer) { Artist.new("Greg Graffin") }
137
- let (:artist) { Artist.new("Bad Religion") }
138
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
139
-
140
-
141
-
142
- class AlbumForm < Reform::Form
143
- property :name
144
-
145
- collection :songs,
146
- populate_if_empty: Song do # class name works.
147
-
148
- property :title
149
- validation do
150
- key(:title).required
151
- end
152
-
153
- property :composer, populate_if_empty: :populate_composer! do # lambda works, too. in form context.
154
- property :name
155
- validation do
156
- key(:name).required
157
- end
158
- end
159
-
160
- private
161
- def populate_composer!(fragment, options)
162
- Artist.new
163
- end
164
- end
165
-
166
- property :artist, populate_if_empty: lambda { |args| create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
167
- property :name
168
- end
169
-
170
- private
171
- class Sting < Artist
172
- attr_accessor :args
173
- end
174
- def create_artist(input, user_options)
175
- Sting.new.tap { |artist| artist.args=([input, user_options].to_s) }
176
- end
177
- end
178
-
179
- let (:form) { AlbumForm.new(album) }
180
-
181
- it do
182
- form.songs.size.must_equal 2
183
-
184
- form.validate(
185
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
186
- {"title" => "Rime Of The Ancient Mariner"}, # new song.
187
- {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
188
- ).must_equal true
189
-
190
- form.errors.messages.inspect.must_equal "{}"
191
-
192
- # form has updated.
193
- form.name.must_equal "The Dissent Of Man"
194
- form.songs[0].title.must_equal "Fallout"
195
- form.songs[1].title.must_equal "Roxanne"
196
- form.songs[1].composer.name.must_equal "Greg Graffin"
197
- form.songs[1].title.must_equal "Roxanne"
198
- form.songs[2].title.must_equal "Rime Of The Ancient Mariner" # new song added.
199
- form.songs[3].title.must_equal "Re-Education"
200
- form.songs[3].composer.name.must_equal "Rise Against"
201
- form.songs.size.must_equal 4
202
- form.artist.name.must_equal "Bad Religion"
203
-
204
-
205
- # model has not changed, yet.
206
- album.name.must_equal "The Dissent Of Man"
207
- album.songs[0].title.must_equal "Broken"
208
- album.songs[1].title.must_equal "Resist Stance"
209
- album.songs[1].composer.name.must_equal "Greg Graffin"
210
- album.songs.size.must_equal 2
211
- album.artist.name.must_equal "Bad Religion"
212
- end
213
-
214
- # trigger artist populator. lambda calling form instance method.
215
- it "xxxx" do
216
- form = AlbumForm.new(album = Album.new)
217
- form.validate("artist" => {"name" => "From Autumn To Ashes"})
218
-
219
- form.artist.name.must_equal "From Autumn To Ashes"
220
- # test lambda was executed in form context.
221
- form.artist.model.must_be_instance_of AlbumForm::Sting
222
- # test lambda block arguments.
223
- form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
224
-
225
- album.artist.must_equal nil
226
- end
227
- end
228
-
229
-
230
- # delete songs while deserializing.
231
- class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
232
- Song = Struct.new(:title, :album, :composer)
233
- Album = Struct.new(:name, :songs, :artist)
234
-
235
- let (:song) { Song.new("Broken") }
236
- let (:song2) { Song.new("Resist Stance") }
237
- let (:album) { Album.new("The Dissent Of Man", [song, song2]) }
238
-
239
-
240
- class AlbumForm < Reform::Form
241
- property :name
242
-
243
- collection :songs,
244
- populate_if_empty: Song, skip_if: :delete_song! do
245
-
246
- property :title
247
- validation do
248
- key(:title).required
249
- end
250
- end
251
-
252
- def delete_song!(options)
253
- songs.delete(songs[0]) and return true if options[:fragment]["title"] == "Broken, delete me!"
254
- false
255
- end
256
- end
257
-
258
- let (:form) { AlbumForm.new(album) }
259
-
260
- it do
261
- form.validate(
262
- "songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
263
- ).must_equal true
264
-
265
- form.errors.messages.inspect.must_equal "{}"
266
-
267
- form.songs.size.must_equal 1
268
- form.songs[0].title.must_equal "Roxanne"
269
- end
270
- end
@@ -1,14 +0,0 @@
1
- require "test_helper"
2
-
3
- class ReadonlyTest < MiniTest::Spec
4
- class SongForm < Reform::Form
5
- property :artist
6
- property :title, writeable: false
7
- # TODO: what to do with virtual values?
8
- end
9
-
10
- let (:form) { SongForm.new(OpenStruct.new) }
11
-
12
- it { form.readonly?(:artist).must_equal false }
13
- it { form.readonly?(:title).must_equal true }
14
- end
@@ -1,89 +0,0 @@
1
- require 'test_helper'
2
-
3
- class SaveTest < BaseTest
4
- Song = Struct.new(:title, :album, :composer)
5
- Album = Struct.new(:name, :songs, :artist)
6
- Artist = Struct.new(:name)
7
-
8
- class AlbumForm < Reform::Form
9
- property :name
10
- validation do
11
- key(:name).required
12
- end
13
-
14
- collection :songs do
15
- property :title
16
- validation do
17
- key(:title).required
18
- end
19
-
20
- property :composer do
21
- property :name
22
- validation do
23
- key(:name).required
24
- end
25
- end
26
- end
27
-
28
- property :artist, save: false do
29
- property :name
30
- end
31
- end
32
-
33
- module Saveable
34
- def save
35
- @saved = true
36
- end
37
-
38
- def saved?
39
- @saved
40
- end
41
- end
42
-
43
-
44
- let (:song) { Song.new("Broken").extend(Saveable) }
45
- # let (:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
46
- let (:composer) { Artist.new("Greg Graffin").extend(Saveable) }
47
- let (:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
48
- let (:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
49
-
50
- let (:form) { AlbumForm.new(album) }
51
-
52
-
53
- it do
54
- form.validate("songs" => [{"title" => "Fixed"}])
55
-
56
- form.save
57
-
58
- album.saved?.must_equal true
59
- album.songs[0].title.must_equal "Fixed"
60
- album.songs[0].saved?.must_equal true
61
- album.artist.saved?.must_equal nil
62
- end
63
- end
64
-
65
-
66
- # class SaveWithDynamicOptionsTest < MiniTest::Spec
67
- # Song = Struct.new(:id, :title, :length) do
68
- # include Saveable
69
- # end
70
-
71
- # class SongForm < Reform::Form
72
- # property :title#, save: false
73
- # property :length, virtual: true
74
- # end
75
-
76
- # let (:song) { Song.new }
77
- # let (:form) { SongForm.new(song) }
78
-
79
- # # we have access to original input value and outside parameters.
80
- # it "xxx" do
81
- # form.validate("title" => "A Poor Man's Memory", "length" => 10)
82
- # length_seconds = 120
83
- # form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
84
-
85
- # song.title.must_equal "A Poor Man's Memory"
86
- # song.length.must_equal nil
87
- # song.id.must_equal "10: 120"
88
- # end
89
- # end