reform 2.2.4

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +11 -0
  4. data/CHANGES.md +415 -0
  5. data/Gemfile +19 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +339 -0
  8. data/Rakefile +15 -0
  9. data/TODO.md +45 -0
  10. data/gemfiles/Gemfile.disposable-0.3 +6 -0
  11. data/lib/reform.rb +8 -0
  12. data/lib/reform/contract.rb +77 -0
  13. data/lib/reform/contract/errors.rb +43 -0
  14. data/lib/reform/contract/validate.rb +33 -0
  15. data/lib/reform/form.rb +94 -0
  16. data/lib/reform/form/call.rb +23 -0
  17. data/lib/reform/form/coercion.rb +3 -0
  18. data/lib/reform/form/composition.rb +34 -0
  19. data/lib/reform/form/dry.rb +67 -0
  20. data/lib/reform/form/module.rb +27 -0
  21. data/lib/reform/form/mongoid.rb +37 -0
  22. data/lib/reform/form/orm.rb +26 -0
  23. data/lib/reform/form/populator.rb +123 -0
  24. data/lib/reform/form/prepopulate.rb +24 -0
  25. data/lib/reform/form/validate.rb +60 -0
  26. data/lib/reform/mongoid.rb +4 -0
  27. data/lib/reform/validation.rb +40 -0
  28. data/lib/reform/validation/groups.rb +73 -0
  29. data/lib/reform/version.rb +3 -0
  30. data/reform.gemspec +29 -0
  31. data/test/benchmarking.rb +26 -0
  32. data/test/call_test.rb +23 -0
  33. data/test/changed_test.rb +41 -0
  34. data/test/coercion_test.rb +66 -0
  35. data/test/composition_test.rb +149 -0
  36. data/test/contract_test.rb +77 -0
  37. data/test/default_test.rb +22 -0
  38. data/test/deprecation_test.rb +27 -0
  39. data/test/deserialize_test.rb +104 -0
  40. data/test/errors_test.rb +165 -0
  41. data/test/feature_test.rb +65 -0
  42. data/test/fixtures/dry_error_messages.yml +44 -0
  43. data/test/form_option_test.rb +24 -0
  44. data/test/form_test.rb +57 -0
  45. data/test/from_test.rb +75 -0
  46. data/test/inherit_test.rb +119 -0
  47. data/test/module_test.rb +142 -0
  48. data/test/parse_pipeline_test.rb +15 -0
  49. data/test/populate_test.rb +270 -0
  50. data/test/populator_skip_test.rb +28 -0
  51. data/test/prepopulator_test.rb +112 -0
  52. data/test/read_only_test.rb +3 -0
  53. data/test/readable_test.rb +30 -0
  54. data/test/readonly_test.rb +14 -0
  55. data/test/reform_test.rb +223 -0
  56. data/test/save_test.rb +89 -0
  57. data/test/setup_test.rb +48 -0
  58. data/test/skip_if_test.rb +74 -0
  59. data/test/skip_setter_and_getter_test.rb +54 -0
  60. data/test/test_helper.rb +49 -0
  61. data/test/validate_test.rb +420 -0
  62. data/test/validation/dry_test.rb +60 -0
  63. data/test/validation/dry_validation_test.rb +352 -0
  64. data/test/validation/errors.yml +4 -0
  65. data/test/virtual_test.rb +24 -0
  66. data/test/writeable_test.rb +29 -0
  67. metadata +265 -0
@@ -0,0 +1,112 @@
1
+ require 'test_helper'
2
+
3
+ class PrepopulatorTest < MiniTest::Spec
4
+ Song = Struct.new(:title, :band, :length)
5
+ Band = Struct.new(:name)
6
+
7
+ class AlbumForm < Reform::Form
8
+ property :title, prepopulator: ->(*){ self.title = "Another Day At Work" } # normal assignment.
9
+ property :length
10
+
11
+ property :hit, prepopulator: ->(options) { self.hit = Song.new(options[:title]) } do # use user options.
12
+ property :title
13
+
14
+ property :band, prepopulator: ->(options){ self.band = my_band(options[:title]) } do # invoke your own code.
15
+ property :name
16
+ end
17
+
18
+ def my_band(name)
19
+ Band.new(title)
20
+ end
21
+ end
22
+
23
+ collection :songs, prepopulator: :prepopulate_songs! do
24
+ property :title
25
+ end
26
+
27
+ private
28
+ def prepopulate_songs!(options)
29
+ if songs == nil
30
+ self.songs = [Song.new, Song.new]
31
+ else
32
+ songs << Song.new # full Twin::Collection API available.
33
+ end
34
+ end
35
+ end
36
+
37
+ it do
38
+ form = AlbumForm.new(OpenStruct.new(length: 1)).prepopulate!(title: "Potemkin City Limits")
39
+
40
+ form.length.must_equal 1
41
+ form.title.must_equal "Another Day At Work"
42
+ form.hit.model.must_equal Song.new("Potemkin City Limits")
43
+ form.songs.size.must_equal 2
44
+ form.songs[0].model.must_equal Song.new
45
+ form.songs[1].model.must_equal Song.new
46
+ form.songs[1].model.must_equal Song.new
47
+ # prepopulate works more than 1 level, recursive.
48
+ # it also passes options properly down there.
49
+ form.hit.band.model.must_equal Band.new("Potemkin City Limits")
50
+ end
51
+
52
+ # add to existing collection.
53
+ it do
54
+ form = AlbumForm.new(OpenStruct.new(songs: [Song.new])).prepopulate!
55
+
56
+ form.songs.size.must_equal 2
57
+ form.songs[0].model.must_equal Song.new
58
+ form.songs[1].model.must_equal Song.new
59
+ end
60
+ end
61
+
62
+ # calling form.prepopulate! shouldn't crash.
63
+ class PrepopulateWithoutConfiguration < MiniTest::Spec
64
+ Song = Struct.new(:title)
65
+
66
+ class AlbumForm < Reform::Form
67
+ collection :songs do
68
+ property :title
69
+ end
70
+
71
+ property :hit do
72
+ property :title
73
+ end
74
+ end
75
+
76
+ subject { AlbumForm.new(OpenStruct.new(songs: [], hit: nil)).prepopulate! }
77
+
78
+ it { subject.songs.size.must_equal 0 }
79
+ end
80
+
81
+
82
+ class ManualPrepopulatorOverridingTest < MiniTest::Spec
83
+ Song = Struct.new(:title, :band, :length)
84
+ Band = Struct.new(:name)
85
+
86
+ class AlbumForm < Reform::Form
87
+ property :title
88
+ property :length
89
+
90
+ property :hit do
91
+ property :title
92
+
93
+ property :band do
94
+ property :name
95
+ end
96
+ end
97
+
98
+ def prepopulate!(options)
99
+ self.hit = Song.new(options[:title])
100
+ super
101
+ end
102
+ end
103
+
104
+ # you can simply override Form#prepopulate!
105
+ it do
106
+ form = AlbumForm.new(OpenStruct.new(length: 1)).prepopulate!(title: "Potemkin City Limits")
107
+
108
+ form.length.must_equal 1
109
+ form.hit.model.must_equal Song.new("Potemkin City Limits")
110
+ form.hit.title.must_equal "Potemkin City Limits"
111
+ end
112
+ end
@@ -0,0 +1,3 @@
1
+ require 'test_helper'
2
+
3
+
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ class ReadableTest < MiniTest::Spec
4
+ Credentials = Struct.new(:password)
5
+
6
+ class PasswordForm < Reform::Form
7
+ property :password, readable: false
8
+ end
9
+
10
+ let (:cred) { Credentials.new }
11
+ let (:form) { PasswordForm.new(cred) }
12
+
13
+ it {
14
+ form.password.must_equal nil # password not read.
15
+
16
+ form.validate("password" => "123")
17
+
18
+ form.password.must_equal "123"
19
+
20
+ form.sync
21
+ cred.password.must_equal "123" # password written.
22
+
23
+ hash = {}
24
+ form.save do |nested|
25
+ hash = nested
26
+ end
27
+
28
+ hash.must_equal("password"=> "123")
29
+ }
30
+ end
@@ -0,0 +1,14 @@
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
@@ -0,0 +1,223 @@
1
+ require 'test_helper'
2
+
3
+ # TODO: this test should be removed.
4
+ class ReformTest < Minitest::Spec
5
+ let (:comp) { OpenStruct.new(:name => "Duran Duran", :title => "Rio") }
6
+
7
+ let (:form) { SongForm.new(comp) }
8
+
9
+ class SongForm < Reform::Form
10
+ property :name
11
+ property :title
12
+
13
+ validation do
14
+ key(:name).required
15
+ end
16
+ end
17
+
18
+ describe "(new) form with empty models" do
19
+ let (:comp) { OpenStruct.new }
20
+
21
+ it "returns empty fields" do
22
+ form.title.must_equal nil
23
+ form.name.must_equal nil
24
+ end
25
+
26
+ describe "and submitted values" do
27
+ it "returns filled-out fields" do
28
+ form.validate("name" => "Duran Duran")
29
+
30
+ form.title.must_equal nil
31
+ form.name.must_equal "Duran Duran"
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "(edit) form with existing models" do
37
+ it "returns filled-out fields" do
38
+ form.name.must_equal "Duran Duran"
39
+ form.title.must_equal "Rio"
40
+ end
41
+ end
42
+
43
+ describe "#validate" do
44
+ let (:comp) { OpenStruct.new }
45
+
46
+ it "ignores unmapped fields in input" do
47
+ form.validate("name" => "Duran Duran", :genre => "80s")
48
+ assert_raises NoMethodError do
49
+ form.genre
50
+ end
51
+ end
52
+
53
+ it "returns true when valid" do
54
+ form.validate("name" => "Duran Duran").must_equal true
55
+ end
56
+
57
+ it "exposes input via property accessors" do
58
+ form.validate("name" => "Duran Duran")
59
+
60
+ form.name.must_equal "Duran Duran"
61
+ end
62
+
63
+ it "doesn't change model properties" do
64
+ form.validate("name" => "Duran Duran")
65
+
66
+ comp.name.must_equal nil # don't touch model, yet.
67
+ end
68
+
69
+ # TODO: test errors. test valid.
70
+ describe "invalid input" do
71
+ class ValidatingForm < Reform::Form
72
+ property :name
73
+ property :title
74
+
75
+ validation do
76
+ key(:name).required
77
+ key(:title).required
78
+ end
79
+ end
80
+ let (:form) { ValidatingForm.new(comp) }
81
+
82
+ it "returns false when invalid" do
83
+ form.validate({}).must_equal false
84
+ end
85
+
86
+ it "populates errors" do
87
+ form.validate({})
88
+ form.errors.messages.must_equal({:name=>["is missing"], :title=>["is missing"]})
89
+ end
90
+ end
91
+ end
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
+ describe "#save" do
109
+ let (:comp) { OpenStruct.new }
110
+ let (:form) { SongForm.new(comp) }
111
+
112
+ before { form.validate("name" => "Diesel Boy") }
113
+
114
+ it "xxpushes data to models" do
115
+ form.save
116
+
117
+ comp.name.must_equal "Diesel Boy"
118
+ comp.title.must_equal nil
119
+ end
120
+
121
+ describe "#save with block" do
122
+ it do
123
+ hash = {}
124
+
125
+ form.save do |map|
126
+ hash = map
127
+ end
128
+
129
+ hash.must_equal({"name"=>"Diesel Boy"})
130
+ end
131
+ end
132
+ end
133
+
134
+
135
+ describe "#model" do
136
+ it { form.model.must_equal comp }
137
+ end
138
+
139
+
140
+ describe "inheritance" do
141
+ class HitForm < SongForm
142
+ property :position
143
+ validation do
144
+ key(:position).required
145
+ end
146
+ end
147
+
148
+ let (:form) { HitForm.new(OpenStruct.new()) }
149
+ it do
150
+ form.validate({"title" => "The Body"})
151
+ form.title.must_equal "The Body"
152
+ form.position.must_equal nil
153
+ form.errors.messages.must_equal({:name=>["is missing"], :position=>["is missing"]})
154
+ end
155
+ end
156
+ end
157
+
158
+
159
+ class OverridingAccessorsTest < BaseTest
160
+ class SongForm < Reform::Form
161
+ property :title
162
+
163
+ def title=(v) # used in #validate.
164
+ super v*2
165
+ end
166
+
167
+ def title # used in #sync.
168
+ super.downcase
169
+ end
170
+ end
171
+
172
+ let (:song) { Song.new("Pray") }
173
+ subject { SongForm.new(song) }
174
+
175
+ # override reader for presentation.
176
+ it { subject.title.must_equal "pray" }
177
+
178
+
179
+ describe "#save" do
180
+ before { subject.validate("title" => "Hey Little World") }
181
+
182
+ # reader always used
183
+ it { subject.title.must_equal "hey little worldhey little world" }
184
+
185
+ # the reader is not used when saving/syncing.
186
+ it do
187
+ subject.save do |hash|
188
+ hash["title"].must_equal "Hey Little WorldHey Little World"
189
+ end
190
+ end
191
+
192
+ # no reader or writer used when saving/syncing.
193
+ it do
194
+ song.extend(Saveable)
195
+ subject.save
196
+ song.title.must_equal "Hey Little WorldHey Little World"
197
+ end
198
+ end
199
+ end
200
+
201
+
202
+ class MethodInFormTest < MiniTest::Spec
203
+ class AlbumForm < Reform::Form
204
+ property :title
205
+
206
+ def title
207
+ "The Suffer And The Witness"
208
+ end
209
+
210
+ property :hit do
211
+ property :title
212
+
213
+ def title
214
+ "Drones"
215
+ end
216
+ end
217
+ end
218
+
219
+ # methods can be used instead of created accessors.
220
+ subject { AlbumForm.new(OpenStruct.new(:hit => OpenStruct.new)) }
221
+ it { subject.title.must_equal "The Suffer And The Witness" }
222
+ it { subject.hit.title.must_equal "Drones" }
223
+ end
@@ -0,0 +1,89 @@
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