reform 2.2.4 → 2.3.3

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 (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,101 @@
1
+ require "test_helper"
2
+
3
+ class SaveTest < BaseTest
4
+ Song = Struct.new(:title, :album, :composer)
5
+ Album = Struct.new(:name, :songs, :artist)
6
+ Artist = Struct.new(:name)
7
+
8
+ class AlbumForm < TestForm
9
+ property :name
10
+ validation do
11
+ params { required(:name).filled }
12
+ end
13
+
14
+ collection :songs do
15
+ property :title
16
+ validation do
17
+ params { required(:title).filled }
18
+ end
19
+
20
+ property :composer do
21
+ property :name
22
+ validation do
23
+ params { required(:name).filled }
24
+ end
25
+ end
26
+ end
27
+
28
+ property :artist, save: false do
29
+ property :name
30
+ end
31
+ end
32
+
33
+ module Saveable
34
+ def save
35
+ @saved = true
36
+ end
37
+
38
+ def saved?
39
+ defined?(@saved) && @saved
40
+ end
41
+ end
42
+
43
+ let(:song) { Song.new("Broken").extend(Saveable) }
44
+ # let(:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
45
+ let(:composer) { Artist.new("Greg Graffin").extend(Saveable) }
46
+ let(:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
47
+ let(:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
48
+
49
+ let(:form) { AlbumForm.new(album) }
50
+
51
+ it do
52
+ form.validate("songs" => [{"title" => "Fixed"}])
53
+
54
+ form.save
55
+
56
+ _(album.saved?).must_equal true
57
+ _(album.songs[0].title).must_equal "Fixed"
58
+ _(album.songs[0].saved?).must_equal true
59
+ assert_nil album.artist.saved?
60
+ end
61
+
62
+ describe "#sync with block" do
63
+ it do
64
+ form = AlbumForm.new(Album.new("Greatest Hits"))
65
+
66
+ form.validate(name: nil) # nil-out the title.
67
+
68
+ nested_hash = nil
69
+ form.sync do |hash|
70
+ nested_hash = hash
71
+ end
72
+
73
+ _(nested_hash).must_equal({"name" => nil, "artist" => nil})
74
+ end
75
+ end
76
+ end
77
+
78
+ # class SaveWithDynamicOptionsTest < MiniTest::Spec
79
+ # Song = Struct.new(:id, :title, :length) do
80
+ # include Saveable
81
+ # end
82
+
83
+ # class SongForm < TestForm
84
+ # property :title#, save: false
85
+ # property :length, virtual: true
86
+ # end
87
+
88
+ # let(:song) { Song.new }
89
+ # let(:form) { SongForm.new(song) }
90
+
91
+ # # we have access to original input value and outside parameters.
92
+ # it "xxx" do
93
+ # form.validate("title" => "A Poor Man's Memory", "length" => 10)
94
+ # length_seconds = 120
95
+ # form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
96
+
97
+ # song.title.must_equal "A Poor Man's Memory"
98
+ # assert_nil song.length
99
+ # song.id.must_equal "10: 120"
100
+ # end
101
+ # end
@@ -0,0 +1,101 @@
1
+ require "test_helper"
2
+
3
+ class SaveTest < BaseTest
4
+ Song = Struct.new(:title, :album, :composer)
5
+ Album = Struct.new(:name, :songs, :artist)
6
+ Artist = Struct.new(:name)
7
+
8
+ class AlbumForm < TestForm
9
+ property :name
10
+ validation do
11
+ required(:name).filled
12
+ end
13
+
14
+ collection :songs do
15
+ property :title
16
+ validation do
17
+ required(:title).filled
18
+ end
19
+
20
+ property :composer do
21
+ property :name
22
+ validation do
23
+ required(:name).filled
24
+ end
25
+ end
26
+ end
27
+
28
+ property :artist, save: false do
29
+ property :name
30
+ end
31
+ end
32
+
33
+ module Saveable
34
+ def save
35
+ @saved = true
36
+ end
37
+
38
+ def saved?
39
+ defined?(@saved) && @saved
40
+ end
41
+ end
42
+
43
+ let(:song) { Song.new("Broken").extend(Saveable) }
44
+ # let(:song_with_composer) { Song.new("Resist Stance", nil, composer).extend(Saveable) }
45
+ let(:composer) { Artist.new("Greg Graffin").extend(Saveable) }
46
+ let(:artist) { Artist.new("Bad Religion").extend(Saveable).extend(Saveable) }
47
+ let(:album) { Album.new("The Dissent Of Man", [song], artist).extend(Saveable) }
48
+
49
+ let(:form) { AlbumForm.new(album) }
50
+
51
+ it do
52
+ form.validate("songs" => [{"title" => "Fixed"}])
53
+
54
+ form.save
55
+
56
+ _(album.saved?).must_equal true
57
+ _(album.songs[0].title).must_equal "Fixed"
58
+ _(album.songs[0].saved?).must_equal true
59
+ assert_nil album.artist.saved?
60
+ end
61
+
62
+ describe "#sync with block" do
63
+ it do
64
+ form = AlbumForm.new(Album.new("Greatest Hits"))
65
+
66
+ form.validate(name: nil) # nil-out the title.
67
+
68
+ nested_hash = nil
69
+ form.sync do |hash|
70
+ nested_hash = hash
71
+ end
72
+
73
+ _(nested_hash).must_equal({"name" => nil, "artist" => nil})
74
+ end
75
+ end
76
+ end
77
+
78
+ # class SaveWithDynamicOptionsTest < MiniTest::Spec
79
+ # Song = Struct.new(:id, :title, :length) do
80
+ # include Saveable
81
+ # end
82
+
83
+ # class SongForm < TestForm
84
+ # property :title#, save: false
85
+ # property :length, virtual: true
86
+ # end
87
+
88
+ # let(:song) { Song.new }
89
+ # let(:form) { SongForm.new(song) }
90
+
91
+ # # we have access to original input value and outside parameters.
92
+ # it "xxx" do
93
+ # form.validate("title" => "A Poor Man's Memory", "length" => 10)
94
+ # length_seconds = 120
95
+ # form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
96
+
97
+ # song.title.must_equal "A Poor Man's Memory"
98
+ # assert_nil song.length
99
+ # song.id.must_equal "10: 120"
100
+ # end
101
+ # end
@@ -5,7 +5,7 @@ class SetupTest < 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
10
10
  collection :songs do
11
11
  property :title
@@ -20,29 +20,29 @@ class SetupTest < MiniTest::Spec
20
20
  end
21
21
  end
22
22
 
23
- let (:song) { Song.new("Broken") }
24
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
25
- let (:composer) { Artist.new("Greg Graffin") }
26
- let (:artist) { Artist.new("Bad Religion") }
23
+ let(:song) { Song.new("Broken") }
24
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
25
+ let(:composer) { Artist.new("Greg Graffin") }
26
+ let(:artist) { Artist.new("Bad Religion") }
27
27
 
28
28
  describe "with nested objects" do
29
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
29
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
30
30
 
31
31
  it do
32
32
  form = AlbumForm.new(album)
33
33
 
34
- form.name.must_equal "The Dissent Of Man"
35
- form.songs[0].title.must_equal "Broken"
36
- form.songs[0].composer.must_equal nil
37
- form.songs[1].title.must_equal "Resist Stance"
38
- form.songs[1].composer.name.must_equal "Greg Graffin"
39
- form.artist.name.must_equal "Bad Religion"
34
+ _(form.name).must_equal "The Dissent Of Man"
35
+ _(form.songs[0].title).must_equal "Broken"
36
+ assert_nil form.songs[0].composer
37
+ _(form.songs[1].title).must_equal "Resist Stance"
38
+ _(form.songs[1].composer.name).must_equal "Greg Graffin"
39
+ _(form.artist.name).must_equal "Bad Religion"
40
40
 
41
41
  # make sure all is wrapped in forms.
42
- form.songs[0].must_be_kind_of Reform::Form
43
- form.songs[1].must_be_kind_of Reform::Form
44
- form.songs[1].composer.must_be_kind_of Reform::Form
45
- form.artist.must_be_kind_of Reform::Form
42
+ _(form.songs[0]).must_be_kind_of Reform::Form
43
+ _(form.songs[1]).must_be_kind_of Reform::Form
44
+ _(form.songs[1].composer).must_be_kind_of Reform::Form
45
+ _(form.artist).must_be_kind_of Reform::Form
46
46
  end
47
47
  end
48
- end
48
+ end
@@ -0,0 +1,85 @@
1
+ require "test_helper"
2
+
3
+ class SkipIfTest < BaseTest
4
+ let(:hit) { Song.new }
5
+ let(:album) { Album.new(nil, hit, [], nil) }
6
+
7
+ class AlbumForm < TestForm
8
+ property :title
9
+
10
+ property :hit, skip_if: ->(options) { options[:fragment]["title"] == "" } do
11
+ property :title
12
+ validation do
13
+ params { required(:title).filled }
14
+ end
15
+ end
16
+
17
+ collection :songs, skip_if: :skip_song?, populate_if_empty: BaseTest::Song do
18
+ property :title
19
+ end
20
+
21
+ def skip_song?(options)
22
+ options[:fragment]["title"].nil?
23
+ end
24
+ end
25
+
26
+ # deserializes when present.
27
+ it do
28
+ form = AlbumForm.new(album)
29
+ _(form.validate("hit" => {"title" => "Altar Of Sacrifice"})).must_equal true
30
+ _(form.hit.title).must_equal "Altar Of Sacrifice"
31
+ end
32
+
33
+ # skips deserialization when not present.
34
+ it do
35
+ form = AlbumForm.new(Album.new)
36
+ _(form.validate("hit" => {"title" => ""})).must_equal true
37
+ assert_nil form.hit # hit hasn't been deserialised.
38
+ end
39
+
40
+ # skips deserialization when not present.
41
+ it do
42
+ form = AlbumForm.new(Album.new(nil, nil, []))
43
+ _(form.validate("songs" => [{"title" => "Waste Of Breath"}, {"title" => nil}])).must_equal true
44
+ _(form.songs.size).must_equal 1
45
+ _(form.songs[0].title).must_equal "Waste Of Breath"
46
+ end
47
+ end
48
+
49
+ class SkipIfAllBlankTest < BaseTest
50
+ # skip_if: :all_blank"
51
+ class AlbumForm < TestForm
52
+ collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
53
+ property :title
54
+ property :length
55
+ end
56
+ end
57
+
58
+ # create only one object.
59
+ it do
60
+ form = AlbumForm.new(OpenStruct.new(songs: []))
61
+ _(form.validate("songs" => [{"title" => "Apathy"}, {"title" => "", "length" => ""}])).must_equal true
62
+ _(form.songs.size).must_equal 1
63
+ _(form.songs[0].title).must_equal "Apathy"
64
+ end
65
+
66
+ it do
67
+ form = AlbumForm.new(OpenStruct.new(songs: []))
68
+ _(form.validate("songs" => [{"title" => "", "length" => ""}, {"title" => "Apathy"}])).must_equal true
69
+ _(form.songs.size).must_equal 1
70
+ _(form.songs[0].title).must_equal "Apathy"
71
+ end
72
+ end
73
+
74
+ class InvalidOptionsCombinationTest < BaseTest
75
+ it do
76
+ assert_raises(Reform::Form::InvalidOptionsCombinationError) do
77
+ class AlbumForm < TestForm
78
+ collection :songs, skip_if: :all_blank, populator: -> {} do
79
+ property :title
80
+ property :length
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,92 @@
1
+ require "test_helper"
2
+
3
+ class SkipIfTest < BaseTest
4
+ let(:hit) { Song.new }
5
+ let(:album) { Album.new(nil, hit, [], nil) }
6
+
7
+ class AlbumForm < TestForm
8
+ property :title
9
+
10
+ property :hit, skip_if: ->(options) { options[:fragment]["title"] == "" } do
11
+ property :title
12
+ validation do
13
+ required(:title).filled
14
+ end
15
+ end
16
+
17
+ collection :songs, skip_if: :skip_song?, populate_if_empty: BaseTest::Song do
18
+ property :title
19
+ end
20
+
21
+ def skip_song?(options)
22
+ options[:fragment]["title"].nil?
23
+ end
24
+ end
25
+
26
+ # deserializes when present.
27
+ it do
28
+ form = AlbumForm.new(album)
29
+ _(form.validate("hit" => {"title" => "Altar Of Sacrifice"})).must_equal true
30
+ _(form.hit.title).must_equal "Altar Of Sacrifice"
31
+ end
32
+
33
+ # skips deserialization when not present.
34
+ it do
35
+ form = AlbumForm.new(Album.new)
36
+ _(form.validate("hit" => {"title" => ""})).must_equal true
37
+ assert_nil form.hit # hit hasn't been deserialised.
38
+ end
39
+
40
+ # skips deserialization when not present.
41
+ it do
42
+ form = AlbumForm.new(Album.new(nil, nil, []))
43
+ _(form.validate("songs" => [{"title" => "Waste Of Breath"}, {"title" => nil}])).must_equal true
44
+ _(form.songs.size).must_equal 1
45
+ _(form.songs[0].title).must_equal "Waste Of Breath"
46
+ end
47
+ end
48
+
49
+ class SkipIfAllBlankTest < BaseTest
50
+ # skip_if: :all_blank"
51
+ class AlbumForm < TestForm
52
+ collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
53
+ property :title
54
+ property :length
55
+ end
56
+ end
57
+
58
+ # create only one object.
59
+ it do
60
+ form = AlbumForm.new(OpenStruct.new(songs: []))
61
+ _(form.validate("songs" => [{"title" => "Apathy"}, {"title" => "", "length" => ""}])).must_equal true
62
+ _(form.songs.size).must_equal 1
63
+ _(form.songs[0].title).must_equal "Apathy"
64
+ end
65
+
66
+ it do
67
+ form = AlbumForm.new(OpenStruct.new(songs: []))
68
+ _(form.validate("songs" => [{"title" => "", "length" => ""}, {"title" => "Apathy"}])).must_equal true
69
+ _(form.songs.size).must_equal 1
70
+ _(form.songs[0].title).must_equal "Apathy"
71
+ end
72
+
73
+ it do
74
+ form = AlbumForm.new(OpenStruct.new(songs: []))
75
+ _(form.validate(:songs => [{:title=>"", :length => ""}, {:title=>"Apathy"}])).must_equal true
76
+ _(form.songs.size).must_equal 1
77
+ _(form.songs[0].title).must_equal "Apathy"
78
+ end
79
+ end
80
+
81
+ class InvalidOptionsCombinationTest < BaseTest
82
+ it do
83
+ assert_raises(Reform::Form::InvalidOptionsCombinationError) do
84
+ class AlbumForm < TestForm
85
+ collection :songs, skip_if: :all_blank, populator: -> {} do
86
+ property :title
87
+ property :length
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -7,7 +7,7 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
7
7
  Album = Struct.new(:title, :artist)
8
8
  Artist = Struct.new(:name)
9
9
 
10
- class AlbumForm < Reform::Form
10
+ class AlbumForm < TestForm
11
11
  property :title
12
12
 
13
13
  def title
@@ -31,24 +31,23 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
31
31
  end
32
32
  end
33
33
 
34
- let (:artist) { Artist.new("Bad Religion") }
35
-
34
+ let(:artist) { Artist.new("Bad Religion") }
36
35
 
37
36
  it do
38
37
  album = Album.new("Greatest Hits", artist)
39
38
  form = AlbumForm.new(album)
40
39
 
41
- form.title.must_equal "GREATEST HITS"
42
- form.artist.name.must_equal "bad religion"
40
+ _(form.title).must_equal "GREATEST HITS"
41
+ _(form.artist.name).must_equal "bad religion"
43
42
 
44
43
  form.validate("title" => "Resiststance", "artist" => {"name" => "Greg Graffin"})
45
44
 
46
- form.title.must_equal "ECNATSTSISER" # first, setter called, then getter.
47
- form.artist.name.must_equal "greg graffi"
45
+ _(form.title).must_equal "ECNATSTSISER" # first, setter called, then getter.
46
+ _(form.artist.name).must_equal "greg graffi"
48
47
 
49
48
  form.sync
50
49
 
51
- album.title.must_equal "ecnatstsiseR" # setter called, but not getter.
52
- album.artist.name.must_equal "Greg Graffi"
50
+ _(album.title).must_equal "ecnatstsiseR" # setter called, but not getter.
51
+ _(album.artist.name).must_equal "Greg Graffi"
53
52
  end
54
- end
53
+ end