reform 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
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