reform 2.2.4 → 2.3.3

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 (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