reform 2.2.4 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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)
@@ -21,7 +19,7 @@ class FeatureInheritanceTest < BaseTest
21
19
  # end
22
20
  # end
23
21
 
24
- class AlbumForm < Reform::Form
22
+ class AlbumForm < TestForm
25
23
  feature Date # feature.
26
24
  property :name
27
25
 
@@ -38,17 +36,17 @@ 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
- form.date.must_equal "May 16"
51
- form.songs[0].date.must_equal "May 16"
48
+ _(form.date).must_equal "May 16"
49
+ _(form.songs[0].date).must_equal "May 16"
52
50
  end
53
51
 
54
52
  # it { subject.class.include?(Reform::Form::ActiveModel) }
@@ -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
@@ -1,44 +1,94 @@
1
- array?: "%{name} must be an array"
1
+ en:
2
+ errors:
3
+ array?: "must be an array"
2
4
 
3
- empty?: "%{name} cannot be empty"
5
+ empty?: "must be empty"
4
6
 
5
- exclusion?: "%{name} must not be one of: %{list}"
7
+ excludes?: "must not include %{value}"
6
8
 
7
- eql?: "%{name} must be equal to %{eql_value}"
9
+ excluded_from?:
10
+ arg:
11
+ default: "must not be one of: %{list}"
12
+ range: "must not be one of: %{list_left} - %{list_right}"
8
13
 
9
- filled?: "%{name} must be filled"
14
+ eql?: "must be equal to %{left}"
10
15
 
11
- format?: "%{name} is in invalid format"
16
+ not_eql?: "must not be equal to %{left}"
12
17
 
13
- gt?: "%{name} must be greater than %{num} (%{value} was given)"
18
+ filled?: "must be filled"
14
19
 
15
- gteq?: "%{name} must be greater than or equal to %{num}"
20
+ format?: "is in invalid format"
16
21
 
17
- hash?: "%{name} must be a hash"
22
+ number?: "must be a number"
18
23
 
19
- inclusion?: "%{name} must be one of: %{list}"
24
+ odd?: "must be odd"
20
25
 
21
- int?: "%{name} must be an integer"
26
+ even?: "must be even"
22
27
 
23
- key?: "%{name} is missing"
28
+ gt?: "must be greater than %{num}"
24
29
 
25
- lt?: "%{name} must be less than %{num} (%{value} was given)"
30
+ gteq?: "must be greater than or equal to %{num}"
26
31
 
27
- lteq?: "%{name} must be less than or equal to %{num}"
32
+ hash?: "must be a hash"
28
33
 
29
- max_size?: "%{name} size cannot be greater than %{num}"
34
+ included_in?:
35
+ arg:
36
+ default: "must be one of: %{list}"
37
+ range: "must be one of: %{list_left} - %{list_right}"
30
38
 
31
- min_size?: "%{name} size cannot be less than %{num}"
39
+ includes?: "must include %{value}"
32
40
 
33
- nil?: "%{name} cannot be nil"
41
+ bool?: "must be boolean"
34
42
 
35
- size?:
36
- range: "%{name} size must be within %{left} - %{right}"
37
- default: "%{name} size must be %{num}"
43
+ true?: "must be true"
38
44
 
39
- str?: "%{name} must be a string"
45
+ false?: "must be false"
40
46
 
41
- good_musical_taste?: "you're a bad person"
47
+ int?: "must be an integer"
42
48
 
43
- form_access_validation?: "this doesn't look like a Reform form dude!!"
49
+ float?: "must be a float"
44
50
 
51
+ decimal?: "must be a decimal"
52
+
53
+ date?: "must be a date"
54
+
55
+ date_time?: "must be a date time"
56
+
57
+ time?: "must be a time"
58
+
59
+ key?: "is missing"
60
+
61
+ attr?: "is missing"
62
+
63
+ lt?: "must be less than %{num}"
64
+
65
+ lteq?: "must be less than or equal to %{num}"
66
+
67
+ max_size?: "size cannot be greater than %{num}"
68
+
69
+ min_size?: "size cannot be less than %{num}"
70
+
71
+ none?: "cannot be defined"
72
+
73
+ str?: "must be a string"
74
+
75
+ type?: "must be %{type}"
76
+
77
+ size?:
78
+ arg:
79
+ default: "size must be %{size}"
80
+ range: "size must be within %{size_left} - %{size_right}"
81
+
82
+ value:
83
+ string:
84
+ arg:
85
+ default: "length must be %{size}"
86
+ range: "length must be within %{size_left} - %{size_right}"
87
+
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"
92
+ de:
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"