reform 2.3.0.rc1 → 2.3.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +26 -11
  6. data/CHANGES.md +25 -2
  7. data/Gemfile +6 -3
  8. data/ISSUE_TEMPLATE.md +1 -1
  9. data/README.md +2 -4
  10. data/Rakefile +18 -9
  11. data/lib/reform/contract.rb +7 -7
  12. data/lib/reform/contract/custom_error.rb +41 -0
  13. data/lib/reform/contract/validate.rb +9 -5
  14. data/lib/reform/errors.rb +27 -15
  15. data/lib/reform/form.rb +22 -11
  16. data/lib/reform/form/call.rb +1 -1
  17. data/lib/reform/form/composition.rb +2 -2
  18. data/lib/reform/form/dry.rb +10 -86
  19. data/lib/reform/form/dry/input_hash.rb +37 -0
  20. data/lib/reform/form/dry/new_api.rb +58 -0
  21. data/lib/reform/form/dry/old_api.rb +61 -0
  22. data/lib/reform/form/populator.rb +9 -11
  23. data/lib/reform/form/prepopulate.rb +3 -2
  24. data/lib/reform/form/validate.rb +19 -12
  25. data/lib/reform/result.rb +36 -9
  26. data/lib/reform/validation.rb +10 -8
  27. data/lib/reform/validation/groups.rb +2 -3
  28. data/lib/reform/version.rb +1 -1
  29. data/reform.gemspec +10 -9
  30. data/test/benchmarking.rb +10 -11
  31. data/test/call_new_api.rb +23 -0
  32. data/test/{call_test.rb → call_old_api.rb} +3 -3
  33. data/test/changed_test.rb +7 -7
  34. data/test/coercion_test.rb +50 -18
  35. data/test/composition_new_api.rb +186 -0
  36. data/test/{composition_test.rb → composition_old_api.rb} +23 -26
  37. data/test/contract/custom_error_test.rb +55 -0
  38. data/test/contract_new_api.rb +77 -0
  39. data/test/{contract_test.rb → contract_old_api.rb} +8 -8
  40. data/test/default_test.rb +1 -1
  41. data/test/deserialize_test.rb +8 -11
  42. data/test/errors_new_api.rb +225 -0
  43. data/test/errors_old_api.rb +230 -0
  44. data/test/feature_test.rb +7 -9
  45. data/test/fixtures/dry_error_messages.yml +5 -2
  46. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  47. data/test/form_new_api.rb +57 -0
  48. data/test/{form_test.rb → form_old_api.rb} +2 -2
  49. data/test/form_option_new_api.rb +24 -0
  50. data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
  51. data/test/from_test.rb +8 -12
  52. data/test/inherit_new_api.rb +105 -0
  53. data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
  54. data/test/module_new_api.rb +137 -0
  55. data/test/{module_test.rb → module_old_api.rb} +19 -15
  56. data/test/parse_option_test.rb +5 -5
  57. data/test/parse_pipeline_test.rb +2 -2
  58. data/test/populate_new_api.rb +304 -0
  59. data/test/{populate_test.rb → populate_old_api.rb} +28 -34
  60. data/test/populator_skip_test.rb +1 -2
  61. data/test/prepopulator_test.rb +5 -6
  62. data/test/read_only_test.rb +12 -1
  63. data/test/readable_test.rb +5 -5
  64. data/test/reform_new_api.rb +204 -0
  65. data/test/{reform_test.rb → reform_old_api.rb} +17 -23
  66. data/test/save_new_api.rb +101 -0
  67. data/test/{save_test.rb → save_old_api.rb} +10 -13
  68. data/test/setup_test.rb +6 -6
  69. data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
  70. data/test/skip_if_old_api.rb +92 -0
  71. data/test/skip_setter_and_getter_test.rb +2 -3
  72. data/test/test_helper.rb +13 -5
  73. data/test/validate_new_api.rb +408 -0
  74. data/test/{validate_test.rb → validate_old_api.rb} +43 -53
  75. data/test/validation/dry_validation_new_api.rb +826 -0
  76. data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
  77. data/test/validation/result_test.rb +20 -22
  78. data/test/validation_library_provided_test.rb +3 -3
  79. data/test/virtual_test.rb +46 -6
  80. data/test/writeable_test.rb +7 -7
  81. metadata +101 -51
  82. data/test/errors_test.rb +0 -180
  83. data/test/readonly_test.rb +0 -14
@@ -0,0 +1,230 @@
1
+ require "test_helper"
2
+
3
+ class ErrorsTest < MiniTest::Spec
4
+ class AlbumForm < TestForm
5
+ property :title
6
+ validation do
7
+ required(:title).filled
8
+ end
9
+
10
+ property :artists, default: []
11
+ property :producer do
12
+ property :name
13
+ end
14
+
15
+ property :hit do
16
+ property :title
17
+ validation do
18
+ required(:title).filled
19
+ end
20
+ end
21
+
22
+ collection :songs do
23
+ property :title
24
+ validation do
25
+ required(:title).filled
26
+ end
27
+ end
28
+
29
+ property :band do # yepp, people do crazy stuff like that.
30
+ property :name
31
+ property :label do
32
+ property :name
33
+ validation do
34
+ required(:name).filled
35
+ end
36
+ end
37
+ # TODO: make band a required object.
38
+
39
+ validation do
40
+ configure do
41
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
42
+
43
+ def good_musical_taste?(value)
44
+ value != "Nickelback"
45
+ end
46
+ end
47
+
48
+ required(:name).filled(:good_musical_taste?)
49
+ end
50
+ end
51
+
52
+ validation do
53
+ required(:title).filled
54
+ required(:artists).each(:str?)
55
+ required(:producer).schema do
56
+ required(:name).filled
57
+ end
58
+ end
59
+ end
60
+
61
+ let(:album_title) { "Blackhawks Over Los Angeles" }
62
+ let(:album) do
63
+ OpenStruct.new(
64
+ title: album_title,
65
+ hit: song,
66
+ songs: songs, # TODO: document this requirement,
67
+ band: Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
68
+ producer: Struct.new(:name).new("Sun Records")
69
+ )
70
+ end
71
+ let(:song) { OpenStruct.new(title: "Downtown") }
72
+ let(:songs) { [song = OpenStruct.new(title: "Calling"), song] }
73
+ let(:form) { AlbumForm.new(album) }
74
+
75
+ describe "#validate with invalid array property" do
76
+ it do
77
+ form.validate(
78
+ title: "Swimming Pool - EP",
79
+ band: {
80
+ name: "Marie Madeleine",
81
+ label: {name: "Ekler'o'shocK"}
82
+ },
83
+ artists: [42, "Good Charlotte", 43]
84
+ ).must_equal false
85
+ form.errors.messages.must_equal(artists: {0 => ["must be a string"], 2 => ["must be a string"]})
86
+ form.errors.size.must_equal(1)
87
+ end
88
+ end
89
+
90
+ describe "#errors without #validate" do
91
+ it do
92
+ form.errors.size.must_equal 0
93
+ end
94
+ end
95
+
96
+ describe "blank everywhere" do
97
+ before do
98
+ form.validate(
99
+ "hit" => {"title" => ""},
100
+ "title" => "",
101
+ "songs" => [{"title" => ""}, {"title" => ""}],
102
+ "producer" => {"name" => ""}
103
+ )
104
+ end
105
+
106
+ it do
107
+ form.errors.messages.must_equal(
108
+ title: ["must be filled"],
109
+ "hit.title": ["must be filled"],
110
+ "songs.title": ["must be filled"],
111
+ "band.label.name": ["must be filled"],
112
+ "producer.name": ["must be filled"]
113
+ )
114
+ end
115
+
116
+ # it do
117
+ # form.errors.must_equal({:title => ["must be filled"]})
118
+ # TODO: this should only contain local errors?
119
+ # end
120
+
121
+ # nested forms keep their own Errors:
122
+ it { form.producer.errors.messages.must_equal(name: ["must be filled"]) }
123
+ it { form.hit.errors.messages.must_equal(title: ["must be filled"]) }
124
+ it { form.songs[0].errors.messages.must_equal(title: ["must be filled"]) }
125
+
126
+ it do
127
+ form.errors.messages.must_equal(
128
+ title: ["must be filled"],
129
+ "hit.title": ["must be filled"],
130
+ "songs.title": ["must be filled"],
131
+ "band.label.name": ["must be filled"],
132
+ "producer.name": ["must be filled"]
133
+ )
134
+ form.errors.size.must_equal(5)
135
+ end
136
+ end
137
+
138
+ describe "#validate with main form invalid" do
139
+ it do
140
+ form.validate("title" => "", "band" => {"label" => {name: "Fat Wreck"}}, "producer" => nil).must_equal false
141
+ form.errors.messages.must_equal(title: ["must be filled"], producer: ["must be a hash"])
142
+ form.errors.size.must_equal(2)
143
+ end
144
+ end
145
+
146
+ describe "#validate with middle nested form invalid" do
147
+ before { @result = form.validate("hit" => {"title" => ""}, "band" => {"label" => {name: "Fat Wreck"}}) }
148
+
149
+ it { @result.must_equal false }
150
+ it { form.errors.messages.must_equal("hit.title": ["must be filled"]) }
151
+ it { form.errors.size.must_equal(1) }
152
+ end
153
+
154
+ describe "#validate with collection form invalid" do
155
+ before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {name: "Fat Wreck"}}) }
156
+
157
+ it { @result.must_equal false }
158
+ it { form.errors.messages.must_equal("songs.title": ["must be filled"]) }
159
+ it { form.errors.size.must_equal(1) }
160
+ end
161
+
162
+ describe "#validate with collection and 2-level-nested invalid" do
163
+ before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
164
+
165
+ it { @result.must_equal false }
166
+ it { form.errors.messages.must_equal("songs.title": ["must be filled"], "band.label.name": ["must be filled"]) }
167
+ it { form.errors.size.must_equal(2) }
168
+ end
169
+
170
+ describe "#validate with nested form using :base invalid" do
171
+ it do
172
+ result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
173
+ result.must_equal false
174
+ form.errors.messages.must_equal("band.name": ["you're a bad person"])
175
+ form.errors.size.must_equal(1)
176
+ end
177
+ end
178
+
179
+ describe "#add" do
180
+ let(:album_title) { nil }
181
+ it do
182
+ form.errors.add(:before, "validate")
183
+ form.errors.add(:before, "validate 2")
184
+ form.errors.add(:title, "before validate")
185
+ result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
186
+ result.must_equal false
187
+ form.errors.messages.must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"])
188
+ # add a new custom error
189
+ form.errors.add(:policy, "error_text")
190
+ form.errors.messages.must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"])
191
+ # does not duplicate errors
192
+ form.errors.add(:title, "must be filled")
193
+ form.errors.messages.must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"])
194
+ # merge existing errors
195
+ form.errors.add(:policy, "another error")
196
+ form.errors.messages.must_equal(before: ["validate", "validate 2"], title: ["before validate", "must be filled"], "band.name": ["you're a bad person"], policy: ["error_text", "another error"])
197
+ end
198
+ end
199
+
200
+ describe "correct #validate" do
201
+ before do
202
+ @result = form.validate(
203
+ "hit" => {"title" => "Sacrifice"},
204
+ "title" => "Second Heat",
205
+ "songs" => [{"title" => "Heart Of A Lion"}],
206
+ "band" => {"label" => {name: "Fat Wreck"}}
207
+ )
208
+ end
209
+
210
+ it { @result.must_equal true }
211
+ it { form.hit.title.must_equal "Sacrifice" }
212
+ it { form.title.must_equal "Second Heat" }
213
+ it { form.songs.first.title.must_equal "Heart Of A Lion" }
214
+ it do
215
+ skip "WE DON'T NEED COUNT AND EMPTY? ON THE CORE ERRORS OBJECT"
216
+ form.errors.size.must_equal(0)
217
+ form.errors.empty?.must_equal(true)
218
+ end
219
+ end
220
+
221
+ describe "Errors#to_s" do
222
+ before { form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
223
+
224
+ # to_s is aliased to messages
225
+ it {
226
+ skip "why do we need Errors#to_s ?"
227
+ form.errors.to_s.must_equal "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}"
228
+ }
229
+ end
230
+ end
@@ -1,5 +1,3 @@
1
- require 'test_helper'
2
-
3
1
  class FeatureInheritanceTest < BaseTest
4
2
  Song = Struct.new(:title, :album, :composer)
5
3
  Album = Struct.new(:name, :songs, :artist)
@@ -38,13 +36,13 @@ class FeatureInheritanceTest < BaseTest
38
36
  end
39
37
  end
40
38
 
41
- let (:song) { Song.new("Broken") }
42
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
43
- let (:composer) { Artist.new("Greg Graffin") }
44
- let (:artist) { Artist.new("Bad Religion") }
45
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
39
+ let(:song) { Song.new("Broken") }
40
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
41
+ let(:composer) { Artist.new("Greg Graffin") }
42
+ let(:artist) { Artist.new("Bad Religion") }
43
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
46
44
 
47
- let (:form) { AlbumForm.new(album) }
45
+ let(:form) { AlbumForm.new(album) }
48
46
 
49
47
  it do
50
48
  form.date.must_equal "May 16"
@@ -62,4 +60,4 @@ class FeatureInheritanceTest < BaseTest
62
60
  # it { subject.band.label.is_a?(Reform::Form::ActiveModel) }
63
61
  # it { subject.band.label.is_a?(Reform::Form::Coercion) }
64
62
  # it { subject.band.label.is_a?(Reform::Form::MultiParameterAttributes) }
65
- end
63
+ end
@@ -86,6 +86,9 @@ en:
86
86
  range: "length must be within %{size_left} - %{size_right}"
87
87
 
88
88
  good_musical_taste?: "you're a bad person"
89
+
90
+ a_song?: "must have at least one enabled song"
91
+ with_last_name?: "must have last name"
89
92
  de:
90
- errors:
91
- filled?: "muss abgefüllt sein"
93
+ errors:
94
+ filled?: "muss abgefüllt sein"
@@ -0,0 +1,104 @@
1
+ en:
2
+ dry_validation:
3
+ errors:
4
+ array?: "must be an array"
5
+
6
+ empty?: "must be empty"
7
+
8
+ excludes?: "must not include %{value}"
9
+
10
+ excluded_from?:
11
+ arg:
12
+ default: "must not be one of: %{list}"
13
+ range: "must not be one of: %{list_left} - %{list_right}"
14
+
15
+ eql?: "must be equal to %{left}"
16
+
17
+ not_eql?: "must not be equal to %{left}"
18
+
19
+ filled?: "must be filled"
20
+
21
+ format?: "is in invalid format"
22
+
23
+ number?: "must be a number"
24
+
25
+ odd?: "must be odd"
26
+
27
+ even?: "must be even"
28
+
29
+ gt?: "must be greater than %{num}"
30
+
31
+ gteq?: "must be greater than or equal to %{num}"
32
+
33
+ hash?: "must be a hash"
34
+
35
+ included_in?:
36
+ arg:
37
+ default: "must be one of: %{list}"
38
+ range: "must be one of: %{list_left} - %{list_right}"
39
+
40
+ includes?: "must include %{value}"
41
+
42
+ bool?: "must be boolean"
43
+
44
+ true?: "must be true"
45
+
46
+ false?: "must be false"
47
+
48
+ int?: "must be an integer"
49
+
50
+ float?: "must be a float"
51
+
52
+ decimal?: "must be a decimal"
53
+
54
+ date?: "must be a date"
55
+
56
+ date_time?: "must be a date time"
57
+
58
+ time?: "must be a time"
59
+
60
+ key?: "is missing"
61
+
62
+ attr?: "is missing"
63
+
64
+ lt?: "must be less than %{num}"
65
+
66
+ lteq?: "must be less than or equal to %{num}"
67
+
68
+ max_size?: "size cannot be greater than %{num}"
69
+
70
+ min_size?: "size cannot be less than %{num}"
71
+
72
+ none?: "cannot be defined"
73
+
74
+ str?: "must be a string"
75
+
76
+ type?: "must be %{type}"
77
+
78
+ size?:
79
+ arg:
80
+ default: "size must be %{size}"
81
+ range: "size must be within %{size_left} - %{size_right}"
82
+
83
+ value:
84
+ string:
85
+ arg:
86
+ default: "length must be %{size}"
87
+ range: "length must be within %{size_left} - %{size_right}"
88
+
89
+
90
+
91
+ rules:
92
+ name:
93
+ good_musical_taste?: "you're a bad person"
94
+ title:
95
+ good_musical_taste?: "you're a bad person"
96
+ songs:
97
+ a_song?: "must have at least one enabled song"
98
+ artist:
99
+ with_last_name?: "must have last name"
100
+
101
+ de:
102
+ dry_validation:
103
+ errors:
104
+ filled?: "muss abgefüllt sein"
@@ -0,0 +1,57 @@
1
+ require "test_helper"
2
+
3
+ class FormTest < MiniTest::Spec
4
+ Artist = Struct.new(:name)
5
+
6
+ class AlbumForm < TestForm
7
+ property :title
8
+
9
+ property :hit do
10
+ property :title
11
+ end
12
+
13
+ collection :songs do
14
+ property :title
15
+ end
16
+
17
+ property :band do # yepp, people do crazy stuff like that.
18
+ property :label do
19
+ property :name
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "::dup" do
25
+ let(:cloned) { AlbumForm.clone }
26
+
27
+ # #dup is called in Op.inheritable_attr(:contract_class), it must be subclass of the original one.
28
+ it { cloned.wont_equal AlbumForm }
29
+ it { AlbumForm.definitions.wont_equal cloned.definitions }
30
+
31
+ it do
32
+ # currently, forms need a name for validation, even without AM.
33
+ cloned.singleton_class.class_eval do
34
+ def name
35
+ "Album"
36
+ end
37
+ end
38
+
39
+ cloned.validation do
40
+ params { required(:title).filled }
41
+ end
42
+
43
+ cloned.new(OpenStruct.new).validate({})
44
+ end
45
+ end
46
+
47
+ describe "#initialize" do
48
+ class ArtistForm < TestForm
49
+ property :name
50
+ property :current_user, virtual: true
51
+ end
52
+
53
+ it "allows injecting :virtual options" do
54
+ ArtistForm.new(Artist.new, current_user: Object).current_user.must_equal Object
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class FormTest < MiniTest::Spec
4
4
  Artist = Struct.new(:name)
@@ -22,7 +22,7 @@ class FormTest < MiniTest::Spec
22
22
  end
23
23
 
24
24
  describe "::dup" do
25
- let (:cloned) { AlbumForm.clone }
25
+ let(:cloned) { AlbumForm.clone }
26
26
 
27
27
  # #dup is called in Op.inheritable_attr(:contract_class), it must be subclass of the original one.
28
28
  it { cloned.wont_equal AlbumForm }