reform 2.3.0.rc1 → 2.5.0

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 (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