reform 2.2.4 → 2.3.1

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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +11 -6
  6. data/Appraisals +8 -0
  7. data/CHANGES.md +54 -4
  8. data/CONTRIBUTING.md +31 -0
  9. data/Gemfile +2 -16
  10. data/ISSUE_TEMPLATE.md +25 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +5 -7
  13. data/Rakefile +18 -9
  14. data/gemfiles/0.13.0.gemfile +8 -0
  15. data/gemfiles/1.5.0.gemfile +9 -0
  16. data/lib/reform.rb +1 -0
  17. data/lib/reform/contract.rb +7 -17
  18. data/lib/reform/contract/custom_error.rb +41 -0
  19. data/lib/reform/contract/validate.rb +53 -23
  20. data/lib/reform/errors.rb +61 -0
  21. data/lib/reform/form.rb +36 -10
  22. data/lib/reform/form/call.rb +1 -1
  23. data/lib/reform/form/composition.rb +2 -2
  24. data/lib/reform/form/dry.rb +10 -58
  25. data/lib/reform/form/dry/input_hash.rb +37 -0
  26. data/lib/reform/form/dry/new_api.rb +46 -0
  27. data/lib/reform/form/dry/old_api.rb +61 -0
  28. data/lib/reform/form/populator.rb +11 -27
  29. data/lib/reform/form/prepopulate.rb +4 -3
  30. data/lib/reform/form/validate.rb +28 -13
  31. data/lib/reform/result.rb +90 -0
  32. data/lib/reform/validation.rb +19 -11
  33. data/lib/reform/validation/groups.rb +12 -27
  34. data/lib/reform/version.rb +1 -1
  35. data/reform.gemspec +15 -13
  36. data/test/benchmarking.rb +39 -6
  37. data/test/call_new_api.rb +23 -0
  38. data/test/{call_test.rb → call_old_api.rb} +4 -4
  39. data/test/changed_test.rb +8 -8
  40. data/test/coercion_test.rb +51 -19
  41. data/test/composition_new_api.rb +186 -0
  42. data/test/{composition_test.rb → composition_old_api.rb} +66 -31
  43. data/test/contract/custom_error_test.rb +55 -0
  44. data/test/contract_new_api.rb +77 -0
  45. data/test/{contract_test.rb → contract_old_api.rb} +13 -13
  46. data/test/default_test.rb +2 -2
  47. data/test/deserialize_test.rb +11 -14
  48. data/test/errors_new_api.rb +225 -0
  49. data/test/errors_old_api.rb +230 -0
  50. data/test/feature_test.rb +8 -10
  51. data/test/fixtures/dry_error_messages.yml +73 -23
  52. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  53. data/test/form_new_api.rb +57 -0
  54. data/test/{form_test.rb → form_old_api.rb} +5 -5
  55. data/test/form_option_new_api.rb +24 -0
  56. data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
  57. data/test/from_test.rb +9 -13
  58. data/test/inherit_new_api.rb +105 -0
  59. data/test/inherit_old_api.rb +105 -0
  60. data/test/{module_test.rb → module_new_api.rb} +20 -25
  61. data/test/module_old_api.rb +146 -0
  62. data/test/parse_option_test.rb +40 -0
  63. data/test/parse_pipeline_test.rb +3 -3
  64. data/test/populate_new_api.rb +304 -0
  65. data/test/{populate_test.rb → populate_old_api.rb} +83 -49
  66. data/test/populator_skip_test.rb +9 -9
  67. data/test/prepopulator_test.rb +8 -9
  68. data/test/read_only_test.rb +12 -1
  69. data/test/readable_test.rb +7 -7
  70. data/test/reform_new_api.rb +204 -0
  71. data/test/{reform_test.rb → reform_old_api.rb} +30 -51
  72. data/test/save_new_api.rb +101 -0
  73. data/test/{save_test.rb → save_old_api.rb} +32 -20
  74. data/test/setup_test.rb +8 -8
  75. data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
  76. data/test/skip_if_old_api.rb +92 -0
  77. data/test/skip_setter_and_getter_test.rb +3 -4
  78. data/test/test_helper.rb +25 -14
  79. data/test/validate_new_api.rb +408 -0
  80. data/test/{validate_test.rb → validate_old_api.rb} +59 -69
  81. data/test/validation/dry_validation_new_api.rb +836 -0
  82. data/test/validation/dry_validation_old_api.rb +772 -0
  83. data/test/validation/result_test.rb +77 -0
  84. data/test/validation_library_provided_test.rb +16 -0
  85. data/test/virtual_test.rb +47 -7
  86. data/test/writeable_test.rb +35 -6
  87. metadata +127 -56
  88. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  89. data/lib/reform/contract/errors.rb +0 -43
  90. data/lib/reform/form/mongoid.rb +0 -37
  91. data/lib/reform/form/orm.rb +0 -26
  92. data/lib/reform/mongoid.rb +0 -4
  93. data/test/deprecation_test.rb +0 -27
  94. data/test/errors_test.rb +0 -165
  95. data/test/inherit_test.rb +0 -119
  96. data/test/readonly_test.rb +0 -14
  97. data/test/validation/dry_test.rb +0 -60
  98. data/test/validation/dry_validation_test.rb +0 -352
  99. data/test/validation/errors.yml +0 -4
@@ -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
19
-
15
+ populator: ->(fragment:, model:, index:, **) {
16
+ (item = model[index]) ? item : model.insert(index, Song.new)
17
+ } do
20
18
  property :title
21
19
  validation do
22
- key(:title).required
20
+ required(:title).filled
23
21
  end
24
22
 
25
- property :composer, populator: ->(options) { options[:model] || self.composer= Artist.new } do
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
@@ -37,13 +35,13 @@ class PopulatorTest < MiniTest::Spec
37
35
  end
38
36
  end
39
37
 
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) }
38
+ let(:song) { Song.new("Broken") }
39
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
40
+ let(:composer) { Artist.new("Greg Graffin") }
41
+ let(:artist) { Artist.new("Bad Religion") }
42
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
45
43
 
46
- let (:form) { AlbumForm.new(album) }
44
+ let(:form) { AlbumForm.new(album) }
47
45
 
48
46
  it "runs populator on scalar" do
49
47
  form.validate(
@@ -93,7 +91,6 @@ class PopulatorTest < MiniTest::Spec
93
91
  form.songs.size.must_equal 4
94
92
  form.artist.name.must_equal "Bad Religion"
95
93
 
96
-
97
94
  # model has not changed, yet.
98
95
  album.name.must_equal "The Dissent Of Man"
99
96
  album.songs[0].title.must_equal "Broken"
@@ -107,7 +104,7 @@ end
107
104
  class PopulateWithMethodTest < Minitest::Spec
108
105
  Album = Struct.new(:title)
109
106
 
110
- class AlbumForm < Reform::Form
107
+ class AlbumForm < TestForm
111
108
  property :title, populator: :title!
112
109
 
113
110
  def title!(options)
@@ -115,12 +112,54 @@ class PopulateWithMethodTest < Minitest::Spec
115
112
  end
116
113
  end
117
114
 
118
- let (:form) { AlbumForm.new(Album.new) }
115
+ let(:form) { AlbumForm.new(Album.new) }
119
116
 
120
117
  it "runs populator method" do
121
- form.validate(
122
- "title" => "override me!"
123
- )
118
+ form.validate("title" => "override me!")
119
+
120
+ form.title.must_equal "!em edirrevo"
121
+ end
122
+ end
123
+
124
+ class PopulateWithCallableTest < Minitest::Spec
125
+ Album = Struct.new(:title)
126
+
127
+ class TitlePopulator
128
+ include Uber::Callable
129
+
130
+ def call(form, options)
131
+ form.title = options[:fragment].reverse
132
+ end
133
+ end
134
+
135
+ class AlbumForm < TestForm
136
+ property :title, populator: TitlePopulator.new
137
+ end
138
+
139
+ let(:form) { AlbumForm.new(Album.new) }
140
+
141
+ it "runs populator method" do
142
+ form.validate("title" => "override me!")
143
+
144
+ form.title.must_equal "!em edirrevo"
145
+ end
146
+ end
147
+
148
+ class PopulateWithProcTest < Minitest::Spec
149
+ Album = Struct.new(:title)
150
+
151
+ TitlePopulator = ->(options) do
152
+ options[:represented].title = options[:fragment].reverse
153
+ end
154
+
155
+ class AlbumForm < TestForm
156
+ property :title, populator: TitlePopulator
157
+ end
158
+
159
+ let(:form) { AlbumForm.new(Album.new) }
160
+
161
+ it "runs populator method" do
162
+ form.validate("title" => "override me!")
124
163
 
125
164
  form.title.must_equal "!em edirrevo"
126
165
  end
@@ -131,15 +170,13 @@ class PopulateIfEmptyTest < MiniTest::Spec
131
170
  Album = Struct.new(:name, :songs, :artist)
132
171
  Artist = Struct.new(:name)
133
172
 
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
-
173
+ let(:song) { Song.new("Broken") }
174
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
175
+ let(:composer) { Artist.new("Greg Graffin") }
176
+ let(:artist) { Artist.new("Bad Religion") }
177
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
141
178
 
142
- class AlbumForm < Reform::Form
179
+ class AlbumForm < TestForm
143
180
  property :name
144
181
 
145
182
  collection :songs,
@@ -147,42 +184,42 @@ class PopulateIfEmptyTest < MiniTest::Spec
147
184
 
148
185
  property :title
149
186
  validation do
150
- key(:title).required
187
+ required(:title).filled
151
188
  end
152
189
 
153
190
  property :composer, populate_if_empty: :populate_composer! do # lambda works, too. in form context.
154
191
  property :name
155
192
  validation do
156
- key(:name).required
193
+ required(:name).filled
157
194
  end
158
195
  end
159
196
 
160
197
  private
161
- def populate_composer!(fragment, options)
198
+ def populate_composer!(options)
162
199
  Artist.new
163
200
  end
164
201
  end
165
202
 
166
- property :artist, populate_if_empty: lambda { |args| create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
203
+ property :artist, populate_if_empty: ->(args) { create_artist(args[:fragment], args[:user_options]) } do # methods work, too.
167
204
  property :name
168
205
  end
169
206
 
170
- private
207
+ private
171
208
  class Sting < Artist
172
209
  attr_accessor :args
173
210
  end
174
211
  def create_artist(input, user_options)
175
- Sting.new.tap { |artist| artist.args=([input, user_options].to_s) }
212
+ Sting.new.tap { |artist| artist.args = ([input, user_options].to_s) }
176
213
  end
177
214
  end
178
215
 
179
- let (:form) { AlbumForm.new(album) }
216
+ let(:form) { AlbumForm.new(album) }
180
217
 
181
218
  it do
182
219
  form.songs.size.must_equal 2
183
220
 
184
221
  form.validate(
185
- "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
222
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"},
186
223
  {"title" => "Rime Of The Ancient Mariner"}, # new song.
187
224
  {"title" => "Re-Education", "composer" => {"name" => "Rise Against"}}], # new song with new composer.
188
225
  ).must_equal true
@@ -201,7 +238,6 @@ class PopulateIfEmptyTest < MiniTest::Spec
201
238
  form.songs.size.must_equal 4
202
239
  form.artist.name.must_equal "Bad Religion"
203
240
 
204
-
205
241
  # model has not changed, yet.
206
242
  album.name.must_equal "The Dissent Of Man"
207
243
  album.songs[0].title.must_equal "Broken"
@@ -222,22 +258,20 @@ class PopulateIfEmptyTest < MiniTest::Spec
222
258
  # test lambda block arguments.
223
259
  form.artist.model.args.to_s.must_equal "[{\"name\"=>\"From Autumn To Ashes\"}, nil]"
224
260
 
225
- album.artist.must_equal nil
261
+ assert_nil album.artist
226
262
  end
227
263
  end
228
264
 
229
-
230
265
  # delete songs while deserializing.
231
266
  class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
232
267
  Song = Struct.new(:title, :album, :composer)
233
268
  Album = Struct.new(:name, :songs, :artist)
234
269
 
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
-
270
+ let(:song) { Song.new("Broken") }
271
+ let(:song2) { Song.new("Resist Stance") }
272
+ let(:album) { Album.new("The Dissent Of Man", [song, song2]) }
239
273
 
240
- class AlbumForm < Reform::Form
274
+ class AlbumForm < TestForm
241
275
  property :name
242
276
 
243
277
  collection :songs,
@@ -245,7 +279,7 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
245
279
 
246
280
  property :title
247
281
  validation do
248
- key(:title).required
282
+ required(:title).filled
249
283
  end
250
284
  end
251
285
 
@@ -255,11 +289,11 @@ class PopulateIfEmptyWithDeletionTest < MiniTest::Spec
255
289
  end
256
290
  end
257
291
 
258
- let (:form) { AlbumForm.new(album) }
292
+ let(:form) { AlbumForm.new(album) }
259
293
 
260
294
  it do
261
295
  form.validate(
262
- "songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
296
+ "songs" => [{"title" => "Broken, delete me!"}, {"title" => "Roxanne"}]
263
297
  ).must_equal true
264
298
 
265
299
  form.errors.messages.inspect.must_equal "{}"
@@ -4,14 +4,14 @@ class PopulatorSkipTest < MiniTest::Spec
4
4
  Album = Struct.new(:songs)
5
5
  Song = Struct.new(:title)
6
6
 
7
+ class AlbumForm < TestForm
8
+ collection :songs, populator: :my_populator do
9
+ property :title
10
+ end
7
11
 
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
12
+ def my_populator(options)
13
+ return skip! if options[:fragment][:title] == "Good"
14
+ songs[options[:index]]
15
15
  end
16
16
  end
17
17
 
@@ -22,7 +22,7 @@ class PopulatorSkipTest < MiniTest::Spec
22
22
  form.validate(hash)
23
23
 
24
24
  form.songs.size.must_equal 2
25
- form.songs[0].title.must_equal nil
25
+ assert_nil form.songs[0].title
26
26
  form.songs[1].title.must_equal "Bad"
27
27
  end
28
- end
28
+ end
@@ -1,17 +1,17 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  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
8
- property :title, prepopulator: ->(*){ self.title = "Another Day At Work" } # normal assignment.
7
+ class AlbumForm < TestForm
8
+ property :title, prepopulator: ->(*) { self.title = "Another Day At Work" } # normal assignment.
9
9
  property :length
10
10
 
11
11
  property :hit, prepopulator: ->(options) { self.hit = Song.new(options[:title]) } do # use user options.
12
12
  property :title
13
13
 
14
- property :band, prepopulator: ->(options){ self.band = my_band(options[:title]) } do # invoke your own code.
14
+ property :band, prepopulator: ->(options) { self.band = my_band(options[:title]) } do # invoke your own code.
15
15
  property :name
16
16
  end
17
17
 
@@ -24,7 +24,7 @@ class PrepopulatorTest < MiniTest::Spec
24
24
  property :title
25
25
  end
26
26
 
27
- private
27
+ private
28
28
  def prepopulate_songs!(options)
29
29
  if songs == nil
30
30
  self.songs = [Song.new, Song.new]
@@ -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
@@ -78,12 +78,11 @@ class PrepopulateWithoutConfiguration < MiniTest::Spec
78
78
  it { subject.songs.size.must_equal 0 }
79
79
  end
80
80
 
81
-
82
81
  class ManualPrepopulatorOverridingTest < MiniTest::Spec
83
82
  Song = Struct.new(:title, :band, :length)
84
83
  Band = Struct.new(:name)
85
84
 
86
- class AlbumForm < Reform::Form
85
+ class AlbumForm < TestForm
87
86
  property :title
88
87
  property :length
89
88
 
@@ -109,4 +108,4 @@ class ManualPrepopulatorOverridingTest < MiniTest::Spec
109
108
  form.hit.model.must_equal Song.new("Potemkin City Limits")
110
109
  form.hit.title.must_equal "Potemkin City Limits"
111
110
  end
112
- end
111
+ end
@@ -1,3 +1,14 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
+ class ReadonlyTest < MiniTest::Spec
4
+ class SongForm < TestForm
5
+ property :artist
6
+ property :title, writeable: false
7
+ # TODO: what to do with virtual values?
8
+ end
3
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,17 +1,17 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
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
 
10
- let (:cred) { Credentials.new }
11
- let (:form) { PasswordForm.new(cred) }
10
+ let(:cred) { Credentials.new }
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
 
@@ -25,6 +25,6 @@ class ReadableTest < MiniTest::Spec
25
25
  hash = nested
26
26
  end
27
27
 
28
- hash.must_equal("password"=> "123")
28
+ hash.must_equal("password" => "123")
29
29
  }
30
- end
30
+ end
@@ -0,0 +1,204 @@
1
+ require "test_helper"
2
+
3
+ class ReformTest < Minitest::Spec
4
+ let(:comp) { OpenStruct.new(name: "Duran Duran", title: "Rio") }
5
+
6
+ let(:form) { SongForm.new(comp) }
7
+
8
+ class SongForm < TestForm
9
+ property :name
10
+ property :title
11
+
12
+ validation do
13
+ params { required(:name).filled }
14
+ end
15
+ end
16
+
17
+ describe "(new) form with empty models" do
18
+ let(:comp) { OpenStruct.new }
19
+
20
+ it "returns empty fields" do
21
+ assert_nil form.title
22
+ form.name.must_be_nil
23
+ end
24
+
25
+ describe "and submitted values" do
26
+ it "returns filled-out fields" do
27
+ form.validate("name" => "Duran Duran")
28
+
29
+ assert_nil form.title
30
+ form.name.must_equal "Duran Duran"
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "(edit) form with existing models" do
36
+ it "returns filled-out fields" do
37
+ form.name.must_equal "Duran Duran"
38
+ form.title.must_equal "Rio"
39
+ end
40
+ end
41
+
42
+ describe "#validate" do
43
+ let(:comp) { OpenStruct.new }
44
+
45
+ it "ignores unmapped fields in input" do
46
+ form.validate("name" => "Duran Duran", :genre => "80s")
47
+ assert_raises NoMethodError do
48
+ form.genre
49
+ end
50
+ end
51
+
52
+ it "returns true when valid" do
53
+ form.validate("name" => "Duran Duran").must_equal true
54
+ end
55
+
56
+ it "exposes input via property accessors" do
57
+ form.validate("name" => "Duran Duran")
58
+
59
+ form.name.must_equal "Duran Duran"
60
+ end
61
+
62
+ it "doesn't change model properties" do
63
+ form.validate("name" => "Duran Duran")
64
+
65
+ assert_nil comp.name # don't touch model, yet.
66
+ end
67
+
68
+ # TODO: test errors. test valid.
69
+ describe "invalid input" do
70
+ class ValidatingForm < TestForm
71
+ property :name
72
+ property :title
73
+
74
+ validation do
75
+ params do
76
+ required(:name).filled
77
+ required(:title).filled
78
+ end
79
+ end
80
+ end
81
+ let(:form) { ValidatingForm.new(comp) }
82
+
83
+ it "returns false when invalid" do
84
+ form.validate({}).must_equal false
85
+ end
86
+
87
+ it "populates errors" do
88
+ form.validate({})
89
+ form.errors.messages.must_equal(name: ["must be filled"], title: ["must be filled"])
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "#save" do
95
+ let(:comp) { OpenStruct.new }
96
+ let(:form) { SongForm.new(comp) }
97
+
98
+ before { form.validate("name" => "Diesel Boy") }
99
+
100
+ it "xxpushes data to models" do
101
+ form.save
102
+
103
+ comp.name.must_equal "Diesel Boy"
104
+ assert_nil comp.title
105
+ end
106
+
107
+ describe "#save with block" do
108
+ it do
109
+ hash = {}
110
+
111
+ form.save do |map|
112
+ hash = map
113
+ end
114
+
115
+ hash.must_equal("name" => "Diesel Boy", "title" => nil)
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "#model" do
121
+ it { form.model.must_equal comp }
122
+ end
123
+
124
+ describe "inheritance" do
125
+ class HitForm < SongForm
126
+ property :position
127
+ validation do
128
+ params { required(:position).filled }
129
+ end
130
+ end
131
+
132
+ let(:form) { HitForm.new(OpenStruct.new()) }
133
+ it do
134
+ form.validate("title" => "The Body")
135
+ form.title.must_equal "The Body"
136
+ assert_nil form.position
137
+ form.errors.messages.must_equal(name: ["must be filled"], position: ["must be filled"])
138
+ end
139
+ end
140
+ end
141
+
142
+ class OverridingAccessorsTest < BaseTest
143
+ class SongForm < TestForm
144
+ property :title
145
+
146
+ def title=(v) # used in #validate.
147
+ super v * 2
148
+ end
149
+
150
+ def title # used in #sync.
151
+ super.downcase
152
+ end
153
+ end
154
+
155
+ let(:song) { Song.new("Pray") }
156
+ subject { SongForm.new(song) }
157
+
158
+ # override reader for presentation.
159
+ it { subject.title.must_equal "pray" }
160
+
161
+ describe "#save" do
162
+ before { subject.validate("title" => "Hey Little World") }
163
+
164
+ # reader always used
165
+ it { subject.title.must_equal "hey little worldhey little world" }
166
+
167
+ # the reader is not used when saving/syncing.
168
+ it do
169
+ subject.save do |hash|
170
+ hash["title"].must_equal "Hey Little WorldHey Little World"
171
+ end
172
+ end
173
+
174
+ # no reader or writer used when saving/syncing.
175
+ it do
176
+ song.extend(Saveable)
177
+ subject.save
178
+ song.title.must_equal "Hey Little WorldHey Little World"
179
+ end
180
+ end
181
+ end
182
+
183
+ class MethodInFormTest < MiniTest::Spec
184
+ class AlbumForm < TestForm
185
+ property :title
186
+
187
+ def title
188
+ "The Suffer And The Witness"
189
+ end
190
+
191
+ property :hit do
192
+ property :title
193
+
194
+ def title
195
+ "Drones"
196
+ end
197
+ end
198
+ end
199
+
200
+ # methods can be used instead of created accessors.
201
+ subject { AlbumForm.new(OpenStruct.new(hit: OpenStruct.new)) }
202
+ it { subject.title.must_equal "The Suffer And The Witness" }
203
+ it { subject.hit.title.must_equal "Drones" }
204
+ end