reform 2.3.0.rc1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +7 -11
  4. data/CHANGES.md +43 -3
  5. data/Gemfile +2 -5
  6. data/ISSUE_TEMPLATE.md +1 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +7 -9
  9. data/Rakefile +6 -10
  10. data/lib/reform/contract.rb +7 -7
  11. data/lib/reform/contract/custom_error.rb +41 -0
  12. data/lib/reform/contract/validate.rb +10 -6
  13. data/lib/reform/errors.rb +27 -15
  14. data/lib/reform/form.rb +22 -11
  15. data/lib/reform/form/call.rb +1 -1
  16. data/lib/reform/form/composition.rb +2 -2
  17. data/lib/reform/form/dry.rb +22 -60
  18. data/lib/reform/form/dry/input_hash.rb +37 -0
  19. data/lib/reform/form/populator.rb +9 -11
  20. data/lib/reform/form/prepopulate.rb +3 -2
  21. data/lib/reform/form/validate.rb +19 -12
  22. data/lib/reform/result.rb +36 -9
  23. data/lib/reform/validation.rb +10 -8
  24. data/lib/reform/validation/groups.rb +2 -4
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +9 -9
  27. data/test/benchmarking.rb +10 -11
  28. data/test/call_test.rb +8 -8
  29. data/test/changed_test.rb +13 -13
  30. data/test/coercion_test.rb +56 -24
  31. data/test/composition_test.rb +49 -51
  32. data/test/contract/custom_error_test.rb +55 -0
  33. data/test/contract_test.rb +18 -18
  34. data/test/default_test.rb +3 -3
  35. data/test/deserialize_test.rb +14 -17
  36. data/test/docs/validation_test.rb +134 -0
  37. data/test/errors_test.rb +131 -86
  38. data/test/feature_test.rb +9 -11
  39. data/test/fixtures/dry_error_messages.yml +65 -52
  40. data/test/form_option_test.rb +3 -3
  41. data/test/form_test.rb +6 -6
  42. data/test/from_test.rb +17 -21
  43. data/test/inherit_test.rb +28 -35
  44. data/test/module_test.rb +23 -28
  45. data/test/parse_option_test.rb +12 -12
  46. data/test/parse_pipeline_test.rb +3 -3
  47. data/test/populate_test.rb +146 -93
  48. data/test/populator_skip_test.rb +3 -4
  49. data/test/prepopulator_test.rb +20 -21
  50. data/test/read_only_test.rb +12 -1
  51. data/test/readable_test.rb +7 -7
  52. data/test/reform_test.rb +38 -42
  53. data/test/save_test.rb +16 -19
  54. data/test/setup_test.rb +15 -15
  55. data/test/skip_if_test.rb +30 -19
  56. data/test/skip_setter_and_getter_test.rb +8 -9
  57. data/test/test_helper.rb +12 -5
  58. data/test/validate_test.rb +160 -140
  59. data/test/validation/dry_validation_test.rb +407 -236
  60. data/test/validation/result_test.rb +29 -31
  61. data/test/validation_library_provided_test.rb +3 -3
  62. data/test/virtual_test.rb +46 -6
  63. data/test/writeable_test.rb +13 -13
  64. metadata +32 -29
  65. data/test/readonly_test.rb +0 -14
data/test/feature_test.rb CHANGED
@@ -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,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
+ assert_equal form.date, "May 16"
49
+ assert_equal form.songs[0].date, "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,91 +1,104 @@
1
1
  en:
2
- errors:
3
- array?: "must be an array"
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}"
4
14
 
5
- empty?: "must be empty"
15
+ eql?: "must be equal to %{left}"
6
16
 
7
- excludes?: "must not include %{value}"
17
+ not_eql?: "must not be equal to %{left}"
8
18
 
9
- excluded_from?:
10
- arg:
11
- default: "must not be one of: %{list}"
12
- range: "must not be one of: %{list_left} - %{list_right}"
19
+ filled?: "must be filled"
13
20
 
14
- eql?: "must be equal to %{left}"
21
+ format?: "is in invalid format"
15
22
 
16
- not_eql?: "must not be equal to %{left}"
23
+ number?: "must be a number"
17
24
 
18
- filled?: "must be filled"
25
+ odd?: "must be odd"
19
26
 
20
- format?: "is in invalid format"
27
+ even?: "must be even"
21
28
 
22
- number?: "must be a number"
29
+ gt?: "must be greater than %{num}"
23
30
 
24
- odd?: "must be odd"
31
+ gteq?: "must be greater than or equal to %{num}"
25
32
 
26
- even?: "must be even"
33
+ hash?: "must be a hash"
27
34
 
28
- gt?: "must be greater than %{num}"
35
+ included_in?:
36
+ arg:
37
+ default: "must be one of: %{list}"
38
+ range: "must be one of: %{list_left} - %{list_right}"
29
39
 
30
- gteq?: "must be greater than or equal to %{num}"
40
+ includes?: "must include %{value}"
31
41
 
32
- hash?: "must be a hash"
42
+ bool?: "must be boolean"
33
43
 
34
- included_in?:
35
- arg:
36
- default: "must be one of: %{list}"
37
- range: "must be one of: %{list_left} - %{list_right}"
44
+ true?: "must be true"
38
45
 
39
- includes?: "must include %{value}"
46
+ false?: "must be false"
40
47
 
41
- bool?: "must be boolean"
48
+ int?: "must be an integer"
42
49
 
43
- true?: "must be true"
50
+ float?: "must be a float"
44
51
 
45
- false?: "must be false"
52
+ decimal?: "must be a decimal"
46
53
 
47
- int?: "must be an integer"
54
+ date?: "must be a date"
48
55
 
49
- float?: "must be a float"
56
+ date_time?: "must be a date time"
50
57
 
51
- decimal?: "must be a decimal"
58
+ time?: "must be a time"
52
59
 
53
- date?: "must be a date"
60
+ key?: "is missing"
54
61
 
55
- date_time?: "must be a date time"
62
+ attr?: "is missing"
56
63
 
57
- time?: "must be a time"
64
+ lt?: "must be less than %{num}"
58
65
 
59
- key?: "is missing"
66
+ lteq?: "must be less than or equal to %{num}"
60
67
 
61
- attr?: "is missing"
68
+ max_size?: "size cannot be greater than %{num}"
62
69
 
63
- lt?: "must be less than %{num}"
70
+ min_size?: "size cannot be less than %{num}"
64
71
 
65
- lteq?: "must be less than or equal to %{num}"
72
+ none?: "cannot be defined"
66
73
 
67
- max_size?: "size cannot be greater than %{num}"
74
+ str?: "must be a string"
68
75
 
69
- min_size?: "size cannot be less than %{num}"
76
+ type?: "must be %{type}"
70
77
 
71
- none?: "cannot be defined"
78
+ size?:
79
+ arg:
80
+ default: "size must be %{size}"
81
+ range: "size must be within %{size_left} - %{size_right}"
72
82
 
73
- str?: "must be a string"
83
+ value:
84
+ string:
85
+ arg:
86
+ default: "length must be %{size}"
87
+ range: "length must be within %{size_left} - %{size_right}"
74
88
 
75
- type?: "must be %{type}"
76
89
 
77
- size?:
78
- arg:
79
- default: "size must be %{size}"
80
- range: "size must be within %{size_left} - %{size_right}"
81
90
 
82
- value:
83
- string:
84
- arg:
85
- default: "length must be %{size}"
86
- range: "length must be within %{size_left} - %{size_right}"
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"
87
100
 
88
- good_musical_taste?: "you're a bad person"
89
101
  de:
102
+ dry_validation:
90
103
  errors:
91
- filled?: "muss abgefüllt sein"
104
+ filled?: "muss abgefüllt sein"
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class FormOptionTest < MiniTest::Spec
4
4
  Song = Struct.new(:title)
@@ -7,7 +7,7 @@ class FormOptionTest < MiniTest::Spec
7
7
  class SongForm < TestForm
8
8
  property :title
9
9
  validation do
10
- required(:title).filled
10
+ params { required(:title).filled }
11
11
  end
12
12
  end
13
13
 
@@ -17,7 +17,7 @@ class FormOptionTest < MiniTest::Spec
17
17
 
18
18
  it do
19
19
  form = AlbumForm.new(Album.new(Song.new("When It Comes To You")))
20
- form.song.title.must_equal "When It Comes To You"
20
+ assert_equal "When It Comes To You", form.song.title
21
21
 
22
22
  form.validate(song: {title: "Run For Cover"})
23
23
  end
data/test/form_test.rb CHANGED
@@ -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,11 +22,11 @@ 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
- it { cloned.wont_equal AlbumForm }
29
- it { AlbumForm.definitions.wont_equal cloned.definitions }
28
+ it { refute_equal cloned, AlbumForm }
29
+ it { refute_equal AlbumForm.definitions, cloned.definitions }
30
30
 
31
31
  it do
32
32
  # currently, forms need a name for validation, even without AM.
@@ -37,7 +37,7 @@ class FormTest < MiniTest::Spec
37
37
  end
38
38
 
39
39
  cloned.validation do
40
- required(:title).filled
40
+ params { required(:title).filled }
41
41
  end
42
42
 
43
43
  cloned.new(OpenStruct.new).validate({})
@@ -51,7 +51,7 @@ class FormTest < MiniTest::Spec
51
51
  end
52
52
 
53
53
  it "allows injecting :virtual options" do
54
- ArtistForm.new(Artist.new, current_user: Object).current_user.must_equal Object
54
+ assert_equal ArtistForm.new(Artist.new, current_user: Object).current_user, Object
55
55
  end
56
56
  end
57
57
  end
data/test/from_test.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class AsTest < BaseTest
4
4
  class AlbumForm < TestForm
@@ -19,46 +19,42 @@ class AsTest < BaseTest
19
19
  end
20
20
  end
21
21
 
22
- let (:song2) { Song.new("Roxanne") }
22
+ let(:song2) { Song.new("Roxanne") }
23
23
 
24
- let (:params) {
24
+ let(:params) do
25
25
  {
26
26
  "name" => "Best Of The Police",
27
- "single" => {"title" => "So Lonely"},
27
+ "single" => {"title" => "So Lonely"},
28
28
  "tracks" => [{"name" => "Message In A Bottle"}, {"name" => "Roxanne"}]
29
29
  }
30
- }
30
+ end
31
31
 
32
32
  subject { AlbumForm.new(Album.new("Best Of", hit, [Song.new("Fallout"), song2])) }
33
33
 
34
- it { subject.name.must_equal "Best Of" }
35
- it { subject.single.title.must_equal "Roxanne" }
36
- it { subject.tracks[0].name.must_equal "Fallout" }
37
- it { subject.tracks[1].name.must_equal "Roxanne" }
38
-
34
+ it { assert_equal subject.name, "Best Of" }
35
+ it { assert_equal subject.single.title, "Roxanne" }
36
+ it { assert_equal subject.tracks[0].name, "Fallout" }
37
+ it { assert_equal subject.tracks[1].name, "Roxanne" }
39
38
 
40
39
  describe "#validate" do
41
40
 
42
-
43
41
  before { subject.validate(params) }
44
42
 
45
- it { subject.name.must_equal "Best Of The Police" }
46
- it { subject.single.title.must_equal "So Lonely" }
47
- it { subject.tracks[0].name.must_equal "Message In A Bottle" }
48
- it { subject.tracks[1].name.must_equal "Roxanne" }
43
+ it { assert_equal subject.name, "Best Of The Police" }
44
+ it { assert_equal subject.single.title, "So Lonely" }
45
+ it { assert_equal subject.tracks[0].name, "Message In A Bottle" }
46
+ it { assert_equal subject.tracks[1].name, "Roxanne" }
49
47
  end
50
48
 
51
-
52
49
  describe "#sync" do
53
- before {
50
+ before do
54
51
  subject.tracks[1].name = "Livin' Ain't No Crime"
55
52
  subject.sync
56
- }
53
+ end
57
54
 
58
- it { song2.title.must_equal "Livin' Ain't No Crime" }
55
+ it { assert_equal song2.title, "Livin' Ain't No Crime" }
59
56
  end
60
57
 
61
-
62
58
  describe "#save (nested hash)" do
63
59
  before { subject.validate(params) }
64
60
 
@@ -69,7 +65,7 @@ class AsTest < BaseTest
69
65
  hash = nested_hash
70
66
  end
71
67
 
72
- hash.must_equal({"title"=>"Best Of The Police", "hit"=>{"title"=>"So Lonely"}, "songs"=>[{"title"=>"Message In A Bottle"}, {"title"=>"Roxanne"}], "band"=> nil})
68
+ assert_equal hash, "title" => "Best Of The Police", "hit" => {"title" => "So Lonely"}, "songs" => [{"title" => "Message In A Bottle"}, {"title" => "Roxanne"}], "band" => nil
73
69
  end
74
70
  end
75
71
  end
data/test/inherit_test.rb CHANGED
@@ -1,12 +1,12 @@
1
- require 'test_helper'
2
- require 'representable/json'
1
+ require "test_helper"
2
+ require "representable/json"
3
3
 
4
4
  class InheritTest < BaseTest
5
5
  Populator = Reform::Form::Populator
6
6
 
7
7
  class SkipParse
8
8
  include Uber::Callable
9
- def call(*args)
9
+ def call(*_args)
10
10
  false
11
11
  end
12
12
  end
@@ -14,19 +14,18 @@ class InheritTest < BaseTest
14
14
  class AlbumForm < TestForm
15
15
  property :title, deserializer: {instance: "Instance"}, skip_if: "skip_if in AlbumForm" # allow direct configuration of :deserializer.
16
16
 
17
- property :hit, populate_if_empty: -> (*) { Song.new } do
17
+ property :hit, populate_if_empty: ->(*) { Song.new } do
18
18
  property :title
19
19
  validation do
20
- required(:title).filled
20
+ params { required(:title).filled }
21
21
  end
22
22
  end
23
23
 
24
- collection :songs, populate_if_empty: lambda {}, skip_if: :all_blank do
24
+ collection :songs, populate_if_empty: -> {}, skip_if: :all_blank do
25
25
  property :title
26
26
  end
27
27
 
28
- property :band, populate_if_empty: lambda {} do
29
-
28
+ property :band, populate_if_empty: -> {} do
30
29
  def band_id
31
30
  1
32
31
  end
@@ -35,10 +34,10 @@ class InheritTest < BaseTest
35
34
 
36
35
  class CompilationForm < AlbumForm
37
36
  property :title, inherit: true, skip_if: "skip_if from CompilationForm"
38
- property :hit, :inherit => true, populate_if_empty: -> (*) { Song.new }, skip_if: SkipParse.new do
37
+ property :hit, inherit: true, populate_if_empty: ->(*) { Song.new }, skip_if: SkipParse.new do
39
38
  property :rating
40
39
  validation do
41
- required(:rating).filled
40
+ params { required(:rating).filled }
42
41
  end
43
42
  end
44
43
 
@@ -48,59 +47,53 @@ class InheritTest < BaseTest
48
47
  end
49
48
  end
50
49
 
51
- let (:album) { Album.new(nil, Song.new, [], Band.new) }
50
+ let(:album) { Album.new(nil, Song.new, [], Band.new) }
52
51
  subject { CompilationForm.new(album) }
53
52
 
54
53
  it do
55
- subject.validate({"hit" => {"title" => "LA Drone", "rating" => 10}})
56
- subject.hit.title.must_equal "LA Drone"
57
- subject.hit.rating.must_equal 10
58
- subject.errors.messages.must_equal({})
54
+ subject.validate("hit" => {"title" => "LA Drone", "rating" => 10})
55
+ assert_equal subject.hit.title, "LA Drone"
56
+ assert_equal subject.hit.rating, 10
57
+ assert_equal subject.errors.messages, {}
59
58
  end
60
59
 
61
60
  it do
62
61
  subject.validate({})
63
62
  assert_nil subject.model.hit.title
64
63
  assert_nil subject.model.hit.rating
65
- subject.errors.messages.must_equal({:"hit.title"=>["must be filled"], :"hit.rating"=>["must be filled"]})
64
+ assert_equal subject.errors.messages, "hit.title": ["must be filled"], "hit.rating": ["must be filled"]
66
65
  end
67
66
 
68
67
  it "xxx" do
69
68
  # sub hashes like :deserializer must be properly cloned when inheriting.
70
- AlbumForm.options_for(:title)[:deserializer].object_id.wont_equal CompilationForm.options_for(:title)[:deserializer].object_id
69
+ refute_equal AlbumForm.options_for(:title)[:deserializer].object_id, CompilationForm.options_for(:title)[:deserializer].object_id
71
70
 
72
71
  # don't overwrite direct deserializer: {} configuration.
73
- AlbumForm.options_for(:title)[:internal_populator].must_be_instance_of Reform::Form::Populator::Sync
74
- AlbumForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if in AlbumForm"
72
+ assert AlbumForm.options_for(:title)[:internal_populator].is_a? Reform::Form::Populator::Sync
73
+ assert_equal AlbumForm.options_for(:title)[:deserializer][:skip_parse], "skip_if in AlbumForm"
75
74
 
76
75
  # AlbumForm.options_for(:hit)[:internal_populator].inspect.must_match /Reform::Form::Populator:.+ @user_proc="Populator"/
77
76
  # AlbumForm.options_for(:hit)[:deserializer][:instance].inspect.must_be_instance_with Reform::Form::Populator, user_proc: "Populator"
78
77
 
78
+ assert AlbumForm.options_for(:songs)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
79
+ assert AlbumForm.options_for(:songs)[:deserializer][:skip_parse].is_a? Reform::Form::Validate::Skip::AllBlank
79
80
 
80
- AlbumForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
81
- AlbumForm.options_for(:songs)[:deserializer][:skip_parse].must_be_instance_of Reform::Form::Validate::Skip::AllBlank
82
-
83
- AlbumForm.options_for(:band)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
84
-
85
-
81
+ assert AlbumForm.options_for(:band)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
86
82
 
87
- CompilationForm.options_for(:title)[:deserializer][:skip_parse].must_equal "skip_if from CompilationForm"
83
+ assert_equal CompilationForm.options_for(:title)[:deserializer][:skip_parse], "skip_if from CompilationForm"
88
84
  # pp CompilationForm.options_for(:songs)
89
- CompilationForm.options_for(:songs)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
85
+ assert CompilationForm.options_for(:songs)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
90
86
 
91
-
92
- CompilationForm.options_for(:band)[:internal_populator].must_be_instance_of Reform::Form::Populator::IfEmpty
87
+ assert CompilationForm.options_for(:band)[:internal_populator].is_a? Reform::Form::Populator::IfEmpty
93
88
 
94
89
  # completely overwrite inherited.
95
- CompilationForm.options_for(:hit)[:deserializer][:skip_parse].must_be_instance_of SkipParse
96
-
90
+ assert CompilationForm.options_for(:hit)[:deserializer][:skip_parse].is_a? SkipParse
97
91
 
98
92
  # inherit: true with block will still inherit the original class.
99
- AlbumForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id.must_equal 1
100
- CompilationForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id.must_equal 1
93
+ assert_equal AlbumForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id, 1
94
+ assert_equal CompilationForm.new(OpenStruct.new(band: OpenStruct.new)).band.band_id, 1
101
95
  end
102
96
 
103
-
104
97
  class CDForm < AlbumForm
105
98
  # override :band's original populate_if_empty but with :inherit.
106
99
  property :band, inherit: true, populator: "CD Populator" do
@@ -108,5 +101,5 @@ class InheritTest < BaseTest
108
101
  end
109
102
  end
110
103
 
111
- it { CDForm.options_for(:band)[:internal_populator].instance_variable_get(:@user_proc).must_equal "CD Populator" }
104
+ it { assert_equal CDForm.options_for(:band)[:internal_populator].instance_variable_get(:@user_proc), "CD Populator" }
112
105
  end