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.
- checksums.yaml +5 -5
- data/.gitignore +5 -1
- data/.travis.yml +7 -11
- data/CHANGES.md +43 -3
- data/Gemfile +2 -5
- data/ISSUE_TEMPLATE.md +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +7 -9
- data/Rakefile +6 -10
- data/lib/reform/contract.rb +7 -7
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +10 -6
- data/lib/reform/errors.rb +27 -15
- data/lib/reform/form.rb +22 -11
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +22 -60
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/populator.rb +9 -11
- data/lib/reform/form/prepopulate.rb +3 -2
- data/lib/reform/form/validate.rb +19 -12
- data/lib/reform/result.rb +36 -9
- data/lib/reform/validation.rb +10 -8
- data/lib/reform/validation/groups.rb +2 -4
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +9 -9
- data/test/benchmarking.rb +10 -11
- data/test/call_test.rb +8 -8
- data/test/changed_test.rb +13 -13
- data/test/coercion_test.rb +56 -24
- data/test/composition_test.rb +49 -51
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_test.rb +18 -18
- data/test/default_test.rb +3 -3
- data/test/deserialize_test.rb +14 -17
- data/test/docs/validation_test.rb +134 -0
- data/test/errors_test.rb +131 -86
- data/test/feature_test.rb +9 -11
- data/test/fixtures/dry_error_messages.yml +65 -52
- data/test/form_option_test.rb +3 -3
- data/test/form_test.rb +6 -6
- data/test/from_test.rb +17 -21
- data/test/inherit_test.rb +28 -35
- data/test/module_test.rb +23 -28
- data/test/parse_option_test.rb +12 -12
- data/test/parse_pipeline_test.rb +3 -3
- data/test/populate_test.rb +146 -93
- data/test/populator_skip_test.rb +3 -4
- data/test/prepopulator_test.rb +20 -21
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +7 -7
- data/test/reform_test.rb +38 -42
- data/test/save_test.rb +16 -19
- data/test/setup_test.rb +15 -15
- data/test/skip_if_test.rb +30 -19
- data/test/skip_setter_and_getter_test.rb +8 -9
- data/test/test_helper.rb +12 -5
- data/test/validate_test.rb +160 -140
- data/test/validation/dry_validation_test.rb +407 -236
- data/test/validation/result_test.rb +29 -31
- data/test/validation_library_provided_test.rb +3 -3
- data/test/virtual_test.rb +46 -6
- data/test/writeable_test.rb +13 -13
- metadata +32 -29
- data/test/readonly_test.rb +0 -14
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            require "test_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class CustomerErrorTest < MiniTest::Spec
         | 
| 4 | 
            +
              let(:key)            { :name }
         | 
| 5 | 
            +
              let(:error_text)     { "text2" }
         | 
| 6 | 
            +
              let(:starting_error) { [OpenStruct.new(errors: {title: ["text1"]})] }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              let(:custom_error) { Reform::Contract::CustomError.new(key, error_text, @results) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              before { @results = starting_error }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              it "base class structure" do
         | 
| 13 | 
            +
                assert_equal custom_error.success?, false
         | 
| 14 | 
            +
                assert_equal custom_error.failure?, true
         | 
| 15 | 
            +
                assert_equal custom_error.errors, key => [error_text]
         | 
| 16 | 
            +
                assert_equal custom_error.messages, key => [error_text]
         | 
| 17 | 
            +
                assert_equal custom_error.hint, {}
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              describe "updates @results accordingly" do
         | 
| 21 | 
            +
                it "add new key" do
         | 
| 22 | 
            +
                  custom_error
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  assert_equal @results.size, 2
         | 
| 25 | 
            +
                  errors = @results.map(&:errors)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  assert_equal errors[0], starting_error.first.errors
         | 
| 28 | 
            +
                  assert_equal errors[1], custom_error.errors
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                describe "when key error already exists in @results" do
         | 
| 32 | 
            +
                  let(:key) { :title }
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  it "merge errors text" do
         | 
| 35 | 
            +
                    custom_error
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    assert_equal @results.size, 1
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    assert_equal @results.first.errors.values, [%w[text1 text2]]
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  describe "add error text is already" do
         | 
| 43 | 
            +
                    let(:error_text) { "text1" }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    it 'does not create duplicates' do
         | 
| 46 | 
            +
                      custom_error
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      assert_equal @results.size, 1
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      assert_equal @results.first.errors.values, [%w[text1]]
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
    
        data/test/contract_test.rb
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "test_helper"
         | 
| 2 2 |  | 
| 3 3 | 
             
            class ContractTest < MiniTest::Spec
         | 
| 4 4 | 
             
              Song  = Struct.new(:title, :album, :composer)
         | 
| @@ -16,19 +16,19 @@ class ContractTest < MiniTest::Spec | |
| 16 16 | 
             
                properties :year, :style, readable: false
         | 
| 17 17 |  | 
| 18 18 | 
             
                validation do
         | 
| 19 | 
            -
                  required(:name).filled
         | 
| 19 | 
            +
                  params { required(:name).filled }
         | 
| 20 20 | 
             
                end
         | 
| 21 21 |  | 
| 22 22 | 
             
                collection :songs do
         | 
| 23 23 | 
             
                  property :title
         | 
| 24 24 | 
             
                  validation do
         | 
| 25 | 
            -
                    required(:title).filled
         | 
| 25 | 
            +
                    params { required(:title).filled }
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 28 | 
             
                  property :composer do
         | 
| 29 29 | 
             
                    property :name
         | 
| 30 30 | 
             
                    validation do
         | 
| 31 | 
            -
                      required(:name).filled
         | 
| 31 | 
            +
                      params { required(:name).filled }
         | 
| 32 32 | 
             
                    end
         | 
| 33 33 | 
             
                  end
         | 
| 34 34 | 
             
                end
         | 
| @@ -36,42 +36,42 @@ class ContractTest < MiniTest::Spec | |
| 36 36 | 
             
                property :artist, form: ArtistForm
         | 
| 37 37 | 
             
              end
         | 
| 38 38 |  | 
| 39 | 
            -
              let | 
| 40 | 
            -
              let | 
| 41 | 
            -
              let | 
| 42 | 
            -
              let | 
| 43 | 
            -
              let | 
| 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", 123, [song, song_with_composer], artist) }
         | 
| 44 44 |  | 
| 45 | 
            -
              let | 
| 45 | 
            +
              let(:form) { AlbumForm.new(album) }
         | 
| 46 46 |  | 
| 47 47 | 
             
              # accept `property form: SongForm`.
         | 
| 48 48 | 
             
              it do
         | 
| 49 | 
            -
                form.artist. | 
| 49 | 
            +
                assert form.artist.is_a? ArtistForm
         | 
| 50 50 | 
             
              end
         | 
| 51 51 |  | 
| 52 52 | 
             
              describe ".properties" do
         | 
| 53 53 | 
             
                it "defines a property when called with one argument" do
         | 
| 54 | 
            -
                  form | 
| 54 | 
            +
                  assert_respond_to form, :duration
         | 
| 55 55 | 
             
                end
         | 
| 56 56 |  | 
| 57 57 | 
             
                it "defines several properties when called with multiple arguments" do
         | 
| 58 | 
            -
                  form | 
| 59 | 
            -
                  form | 
| 58 | 
            +
                  assert_respond_to form, :year
         | 
| 59 | 
            +
                  assert_respond_to form, :style
         | 
| 60 60 | 
             
                end
         | 
| 61 61 |  | 
| 62 62 | 
             
                it "passes options to each property when options are provided" do
         | 
| 63 63 | 
             
                  readable = AlbumForm.new(album).options_for(:style)[:readable]
         | 
| 64 | 
            -
                  readable | 
| 64 | 
            +
                  assert_equal readable, false
         | 
| 65 65 | 
             
                end
         | 
| 66 66 |  | 
| 67 67 | 
             
                it "returns the list of defined properties" do
         | 
| 68 68 | 
             
                  returned_value = AlbumForm.properties(:hello, :world, virtual: true)
         | 
| 69 | 
            -
                  returned_value | 
| 69 | 
            +
                  assert_equal returned_value, %i[hello world]
         | 
| 70 70 | 
             
                end
         | 
| 71 71 | 
             
              end
         | 
| 72 72 |  | 
| 73 73 | 
             
              describe "#options_for" do
         | 
| 74 | 
            -
                it { AlbumForm.options_for(:name).extend(Declarative::Inspect).inspect | 
| 75 | 
            -
                it { AlbumForm.new(album).options_for(:name).extend(Declarative::Inspect).inspect | 
| 74 | 
            +
                it { assert_equal AlbumForm.options_for(:name).extend(Declarative::Inspect).inspect, "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
         | 
| 75 | 
            +
                it { assert_equal AlbumForm.new(album).options_for(:name).extend(Declarative::Inspect).inspect, "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
         | 
| 76 76 | 
             
              end
         | 
| 77 77 | 
             
            end
         | 
    
        data/test/default_test.rb
    CHANGED
    
    | @@ -16,7 +16,7 @@ class DefaultTest < Minitest::Spec | |
| 16 16 | 
             
              it do
         | 
| 17 17 | 
             
                form = AlbumForm.new(Album.new(nil, [Song.new]))
         | 
| 18 18 |  | 
| 19 | 
            -
                form.name | 
| 20 | 
            -
                form.songs[0].title | 
| 19 | 
            +
                assert_equal form.name, "Wrong"
         | 
| 20 | 
            +
                assert_equal form.songs[0].title, "It's Catching Up"
         | 
| 21 21 | 
             
              end
         | 
| 22 | 
            -
            end
         | 
| 22 | 
            +
            end
         | 
    
        data/test/deserialize_test.rb
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "test_helper"
         | 
| 2 2 | 
             
            require "representable/json"
         | 
| 3 3 |  | 
| 4 4 | 
             
            class DeserializeTest < MiniTest::Spec
         | 
| @@ -10,7 +10,7 @@ class DeserializeTest < MiniTest::Spec | |
| 10 10 | 
             
                module Json
         | 
| 11 11 | 
             
                  def deserialize(params)
         | 
| 12 12 | 
             
                    deserializer.new(self).
         | 
| 13 | 
            -
             | 
| 13 | 
            +
                      # extend(Representable::Debug).
         | 
| 14 14 | 
             
                      from_json(params)
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| @@ -18,7 +18,7 @@ class DeserializeTest < MiniTest::Spec | |
| 18 18 | 
             
                    Disposable::Rescheme.from(self.class,
         | 
| 19 19 | 
             
                      include:          [Representable::JSON],
         | 
| 20 20 | 
             
                      superclass:       Representable::Decorator,
         | 
| 21 | 
            -
                      definitions_from:  | 
| 21 | 
            +
                      definitions_from: ->(inline) { inline.definitions },
         | 
| 22 22 | 
             
                      options_from:     :deserializer,
         | 
| 23 23 | 
             
                      exclude_options:  [:populator]
         | 
| 24 24 | 
             
                    )
         | 
| @@ -26,15 +26,13 @@ class DeserializeTest < MiniTest::Spec | |
| 26 26 | 
             
                end
         | 
| 27 27 | 
             
                include Json
         | 
| 28 28 |  | 
| 29 | 
            -
             | 
| 30 29 | 
             
                property :title
         | 
| 31 30 | 
             
                property :artist, populate_if_empty: Artist do
         | 
| 32 31 | 
             
                  property :name
         | 
| 33 32 | 
             
                end
         | 
| 34 33 | 
             
              end
         | 
| 35 34 |  | 
| 36 | 
            -
             | 
| 37 | 
            -
              let (:artist) { Artist.new("A-ha") }
         | 
| 35 | 
            +
              let(:artist) { Artist.new("A-ha") }
         | 
| 38 36 | 
             
              it do
         | 
| 39 37 | 
             
                artist_id = artist.object_id
         | 
| 40 38 |  | 
| @@ -43,9 +41,9 @@ class DeserializeTest < MiniTest::Spec | |
| 43 41 |  | 
| 44 42 | 
             
                form.validate(json)
         | 
| 45 43 |  | 
| 46 | 
            -
                form.title | 
| 47 | 
            -
                form.artist.name | 
| 48 | 
            -
                form.artist.model.object_id | 
| 44 | 
            +
                assert_equal form.title, "Apocalypse Soon"
         | 
| 45 | 
            +
                assert_equal form.artist.name, "Mute"
         | 
| 46 | 
            +
                assert_equal form.artist.model.object_id, artist_id
         | 
| 49 47 | 
             
              end
         | 
| 50 48 |  | 
| 51 49 | 
             
              describe "infering the deserializer from another form should NOT copy its populators" do
         | 
| @@ -62,14 +60,13 @@ class DeserializeTest < MiniTest::Spec | |
| 62 60 | 
             
                # also tests the Form#deserializer API. # FIXME.
         | 
| 63 61 | 
             
                it "uses deserializer inferred from JsonAlbumForm but deserializes/populates to CompilationForm" do
         | 
| 64 62 | 
             
                  form = CompilationForm.new(Album.new)
         | 
| 65 | 
            -
                  form.validate("artist"=> {"name" => "Horowitz"}) # the deserializer doesn't know symbols.
         | 
| 63 | 
            +
                  form.validate("artist" => {"name" => "Horowitz"}) # the deserializer doesn't know symbols.
         | 
| 66 64 | 
             
                  form.sync
         | 
| 67 | 
            -
                  form.artist.model | 
| 65 | 
            +
                  assert_equal form.artist.model, Artist.new("Horowitz", %{{"name"=>"Horowitz"}})
         | 
| 68 66 | 
             
                end
         | 
| 69 67 | 
             
              end
         | 
| 70 68 | 
             
            end
         | 
| 71 69 |  | 
| 72 | 
            -
             | 
| 73 70 | 
             
            class ValidateWithBlockTest < MiniTest::Spec
         | 
| 74 71 | 
             
              Song  = Struct.new(:title, :album, :composer)
         | 
| 75 72 | 
             
              Album = Struct.new(:title, :artist)
         | 
| @@ -90,15 +87,15 @@ class ValidateWithBlockTest < MiniTest::Spec | |
| 90 87 | 
             
                deserializer = Disposable::Rescheme.from(AlbumForm,
         | 
| 91 88 | 
             
                  include:          [Representable::JSON],
         | 
| 92 89 | 
             
                  superclass:       Representable::Decorator,
         | 
| 93 | 
            -
                  definitions_from:  | 
| 90 | 
            +
                  definitions_from: ->(inline) { inline.definitions },
         | 
| 94 91 | 
             
                  options_from:     :deserializer
         | 
| 95 92 | 
             
                )
         | 
| 96 93 |  | 
| 97 | 
            -
                form.validate(json)  | 
| 94 | 
            +
                assert form.validate(json) { |params|
         | 
| 98 95 | 
             
                  deserializer.new(form).from_json(params)
         | 
| 99 | 
            -
                 | 
| 96 | 
            +
                } # with block must return result, too.
         | 
| 100 97 |  | 
| 101 | 
            -
                form.title | 
| 102 | 
            -
                form.artist.name | 
| 98 | 
            +
                assert_equal form.title, "Apocalypse Soon"
         | 
| 99 | 
            +
                assert_equal form.artist.name, "Mute"
         | 
| 103 100 | 
             
              end
         | 
| 104 101 | 
             
            end
         | 
| @@ -0,0 +1,134 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
            require 'reform/form/dry'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class DocsDryVTest < Minitest::Spec
         | 
| 5 | 
            +
              #:basic
         | 
| 6 | 
            +
              class AlbumForm < Reform::Form
         | 
| 7 | 
            +
                feature Reform::Form::Dry
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                property :name
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                validation do
         | 
| 12 | 
            +
                  params do
         | 
| 13 | 
            +
                    required(:name).filled
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
              #:basic end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              it 'validates correctly' do
         | 
| 20 | 
            +
                form = DocsDryVTest::AlbumForm.new(Album.new(nil, nil, nil))
         | 
| 21 | 
            +
                result = form.call(name: nil)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                refute result.success?
         | 
| 24 | 
            +
                assert_equal({ name: ['must be filled'] }, form.errors.messages)
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            class DocsDryVWithRulesTest < Minitest::Spec
         | 
| 29 | 
            +
              #:basic_with_rules
         | 
| 30 | 
            +
              class AlbumForm < Reform::Form
         | 
| 31 | 
            +
                feature Reform::Form::Dry
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                property :name
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                validation name: :default do
         | 
| 36 | 
            +
                  option :form
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  params do
         | 
| 39 | 
            +
                    required(:name).filled
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  rule(:name) do
         | 
| 43 | 
            +
                    key.failure('must be unique') if Album.where.not(id: form.model.id).where(name: value).exists?
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
              #:basic_with_rules end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              it 'validates correctly' do
         | 
| 50 | 
            +
                Album = Struct.new(:name, :songs, :artist, :user)
         | 
| 51 | 
            +
                form = DocsDryVWithRulesTest::AlbumForm.new(Album.new(nil, nil, nil, nil))
         | 
| 52 | 
            +
                result = form.call(name: nil)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                refute result.success?
         | 
| 55 | 
            +
                assert_equal({ name: ['must be filled'] }, form.errors.messages)
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            class DryVWithNestedTest < Minitest::Spec
         | 
| 60 | 
            +
              #:nested
         | 
| 61 | 
            +
              class AlbumForm < Reform::Form
         | 
| 62 | 
            +
                feature Reform::Form::Dry
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                property :name
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                validation do
         | 
| 67 | 
            +
                  params { required(:name).filled }
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                property :artist do
         | 
| 71 | 
            +
                  property :name
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  validation do
         | 
| 74 | 
            +
                    params { required(:name).filled }
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
              #:nested end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              it 'validates correctly' do
         | 
| 81 | 
            +
                form = DryVWithNestedTest::AlbumForm.new(Album.new(nil, nil, Artist.new(nil)))
         | 
| 82 | 
            +
                result = form.call(name: nil, artist: { name: '' })
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                refute result.success?
         | 
| 85 | 
            +
                assert_equal({ name: ['must be filled'], 'artist.name': ['must be filled'] }, form.errors.messages)
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            class DryVValGroupTest < Minitest::Spec
         | 
| 90 | 
            +
              class AlbumForm < Reform::Form
         | 
| 91 | 
            +
                feature Reform::Form::Dry
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                property :name
         | 
| 94 | 
            +
                property :artist
         | 
| 95 | 
            +
                #:validation_groups
         | 
| 96 | 
            +
                validation name: :default do
         | 
| 97 | 
            +
                  params { required(:name).filled }
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                validation name: :artist, if: :default do
         | 
| 101 | 
            +
                  params { required(:artist).filled }
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                validation name: :famous, after: :default do
         | 
| 105 | 
            +
                  params { optional(:artist) }
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  rule(:artist) do
         | 
| 108 | 
            +
                    if value
         | 
| 109 | 
            +
                      key.failure('only famous artist') unless value =~ /famous/
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
                #:validation_groups end
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              it 'validates correctly' do
         | 
| 117 | 
            +
                form = DryVValGroupTest::AlbumForm.new(Album.new(nil, nil, nil))
         | 
| 118 | 
            +
                result = form.call(name: nil)
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                refute result.success?
         | 
| 121 | 
            +
                assert_equal({ name: ['must be filled'] }, result.errors.messages)
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                result = form.call(name: 'Title')
         | 
| 124 | 
            +
                refute result.success?
         | 
| 125 | 
            +
                assert_equal({ artist: ['must be filled'] }, result.errors.messages)
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                result = form.call(name: 'Title', artist: 'Artist')
         | 
| 128 | 
            +
                refute result.success?
         | 
| 129 | 
            +
                assert_equal({ artist: ['only famous artist'] }, result.errors.messages)
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                result = form.call(name: 'Title', artist: 'Artist famous')
         | 
| 132 | 
            +
                assert result.success?
         | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
            end
         | 
    
        data/test/errors_test.rb
    CHANGED
    
    | @@ -1,24 +1,28 @@ | |
| 1 1 | 
             
            require "test_helper"
         | 
| 2 2 |  | 
| 3 | 
            -
            # TODO:
         | 
| 4 | 
            -
            # This test should, at some point soon, only test the `Errors` object and its
         | 
| 5 | 
            -
            # Rails-ish API. No validation specifics, etc. to be tested here.
         | 
| 6 | 
            -
             | 
| 7 3 | 
             
            class ErrorsTest < MiniTest::Spec
         | 
| 8 4 | 
             
              class AlbumForm < TestForm
         | 
| 9 5 | 
             
                property :title
         | 
| 6 | 
            +
                validation do
         | 
| 7 | 
            +
                  params { required(:title).filled }
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                property :artists, default: []
         | 
| 11 | 
            +
                property :producer do
         | 
| 12 | 
            +
                  property :name
         | 
| 13 | 
            +
                end
         | 
| 10 14 |  | 
| 11 15 | 
             
                property :hit do
         | 
| 12 16 | 
             
                  property :title
         | 
| 13 17 | 
             
                  validation do
         | 
| 14 | 
            -
                    required(:title).filled
         | 
| 18 | 
            +
                    params { required(:title).filled }
         | 
| 15 19 | 
             
                  end
         | 
| 16 20 | 
             
                end
         | 
| 17 21 |  | 
| 18 22 | 
             
                collection :songs do
         | 
| 19 23 | 
             
                  property :title
         | 
| 20 24 | 
             
                  validation do
         | 
| 21 | 
            -
                    required(:title).filled
         | 
| 25 | 
            +
                    params { required(:title).filled }
         | 
| 22 26 | 
             
                  end
         | 
| 23 27 | 
             
                end
         | 
| 24 28 |  | 
| @@ -27,154 +31,195 @@ class ErrorsTest < MiniTest::Spec | |
| 27 31 | 
             
                  property :label do
         | 
| 28 32 | 
             
                    property :name
         | 
| 29 33 | 
             
                    validation do
         | 
| 30 | 
            -
                      required(:name).filled
         | 
| 34 | 
            +
                      params { required(:name).filled }
         | 
| 31 35 | 
             
                    end
         | 
| 32 36 | 
             
                  end
         | 
| 33 37 | 
             
                  # TODO: make band a required object.
         | 
| 34 38 |  | 
| 35 39 | 
             
                  validation do
         | 
| 36 | 
            -
                     | 
| 37 | 
            -
                      config.messages_file = "test/fixtures/dry_error_messages.yml"
         | 
| 40 | 
            +
                    config.messages.load_paths << "test/fixtures/dry_error_messages.yml"
         | 
| 38 41 |  | 
| 39 | 
            -
             | 
| 40 | 
            -
                        value != "Nickelback"
         | 
| 41 | 
            -
                      end
         | 
| 42 | 
            -
                    end
         | 
| 42 | 
            +
                    params { required(:name).filled }
         | 
| 43 43 |  | 
| 44 | 
            -
                     | 
| 44 | 
            +
                    rule(:name) { key.failure(:good_musical_taste?) if value == "Nickelback" }
         | 
| 45 45 | 
             
                  end
         | 
| 46 46 | 
             
                end
         | 
| 47 47 |  | 
| 48 48 | 
             
                validation do
         | 
| 49 | 
            -
                   | 
| 49 | 
            +
                  params do
         | 
| 50 | 
            +
                    required(:title).filled
         | 
| 51 | 
            +
                    required(:artists).each(:str?)
         | 
| 52 | 
            +
                    required(:producer).hash do
         | 
| 53 | 
            +
                      required(:name).filled
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
                  end
         | 
| 50 56 | 
             
                end
         | 
| 51 57 | 
             
              end
         | 
| 52 58 |  | 
| 53 | 
            -
              let | 
| 59 | 
            +
              let(:album_title) { "Blackhawks Over Los Angeles" }
         | 
| 60 | 
            +
              let(:album) do
         | 
| 54 61 | 
             
                OpenStruct.new(
         | 
| 55 | 
            -
                  : | 
| 56 | 
            -
                  : | 
| 57 | 
            -
                  : | 
| 58 | 
            -
                  : | 
| 62 | 
            +
                  title: album_title,
         | 
| 63 | 
            +
                  hit: song,
         | 
| 64 | 
            +
                  songs: songs, # TODO: document this requirement,
         | 
| 65 | 
            +
                  band: Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
         | 
| 66 | 
            +
                  producer: Struct.new(:name).new("Sun Records")
         | 
| 59 67 | 
             
                )
         | 
| 60 68 | 
             
              end
         | 
| 61 | 
            -
              let | 
| 62 | 
            -
              let | 
| 63 | 
            -
              let | 
| 69 | 
            +
              let(:song)  { OpenStruct.new(title: "Downtown") }
         | 
| 70 | 
            +
              let(:songs) { [song = OpenStruct.new(title: "Calling"), song] }
         | 
| 71 | 
            +
              let(:form)  { AlbumForm.new(album) }
         | 
| 64 72 |  | 
| 73 | 
            +
              describe "#validate with invalid array property" do
         | 
| 74 | 
            +
                it do
         | 
| 75 | 
            +
                  refute form.validate(
         | 
| 76 | 
            +
                    title: "Swimming Pool - EP",
         | 
| 77 | 
            +
                    band: {
         | 
| 78 | 
            +
                      name: "Marie Madeleine",
         | 
| 79 | 
            +
                      label: {name: "Ekler'o'shocK"}
         | 
| 80 | 
            +
                    },
         | 
| 81 | 
            +
                    artists: [42, "Good Charlotte", 43]
         | 
| 82 | 
            +
                  )
         | 
| 83 | 
            +
                  assert_equal form.errors.messages, artists: {0 => ["must be a string"], 2 => ["must be a string"]}
         | 
| 84 | 
            +
                  assert_equal form.errors.size, 1
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 65 87 |  | 
| 66 88 | 
             
              describe "#errors without #validate" do
         | 
| 67 89 | 
             
                it do
         | 
| 68 | 
            -
                  form.errors.size | 
| 90 | 
            +
                  assert_equal form.errors.size, 0
         | 
| 69 91 | 
             
                end
         | 
| 70 92 | 
             
              end
         | 
| 71 93 |  | 
| 72 94 | 
             
              describe "blank everywhere" do
         | 
| 73 | 
            -
                before  | 
| 74 | 
            -
                   | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
                   | 
| 80 | 
            -
                    :title  => ["must be filled"],
         | 
| 81 | 
            -
                    :"hit.title"=>["must be filled"],
         | 
| 82 | 
            -
                    :"songs.title"=>["must be filled"],
         | 
| 83 | 
            -
                    :"band.label.name"=>["must be filled"]
         | 
| 84 | 
            -
                  })
         | 
| 95 | 
            +
                before do
         | 
| 96 | 
            +
                  form.validate(
         | 
| 97 | 
            +
                    "hit" => {"title" => ""},
         | 
| 98 | 
            +
                    "title" => "",
         | 
| 99 | 
            +
                    "songs" => [{"title" => ""}, {"title" => ""}],
         | 
| 100 | 
            +
                    "producer" => {"name" => ""}
         | 
| 101 | 
            +
                  )
         | 
| 85 102 | 
             
                end
         | 
| 86 103 |  | 
| 87 104 | 
             
                it do
         | 
| 88 | 
            -
                   | 
| 89 | 
            -
             | 
| 105 | 
            +
                  assert_equal form.errors.messages,{
         | 
| 106 | 
            +
                    title: ["must be filled"],
         | 
| 107 | 
            +
                    "hit.title": ["must be filled"],
         | 
| 108 | 
            +
                    "songs.title": ["must be filled"],
         | 
| 109 | 
            +
                    "band.label.name": ["must be filled"],
         | 
| 110 | 
            +
                    "producer.name": ["must be filled"]
         | 
| 111 | 
            +
                  }
         | 
| 90 112 | 
             
                end
         | 
| 91 113 |  | 
| 114 | 
            +
                # it do
         | 
| 115 | 
            +
                #   form.errors.must_equal({:title  => ["must be filled"]})
         | 
| 116 | 
            +
                #   TODO: this should only contain local errors?
         | 
| 117 | 
            +
                # end
         | 
| 118 | 
            +
             | 
| 92 119 | 
             
                # nested forms keep their own Errors:
         | 
| 93 | 
            -
                it { form. | 
| 94 | 
            -
                it { form. | 
| 120 | 
            +
                it { assert_equal form.producer.errors.messages, name: ["must be filled"] }
         | 
| 121 | 
            +
                it { assert_equal form.hit.errors.messages, title: ["must be filled"] }
         | 
| 122 | 
            +
                it { assert_equal form.songs[0].errors.messages, title: ["must be filled"] }
         | 
| 95 123 |  | 
| 96 124 | 
             
                it do
         | 
| 97 | 
            -
                  form.errors.messages | 
| 98 | 
            -
                    : | 
| 99 | 
            -
                     | 
| 100 | 
            -
                     | 
| 101 | 
            -
                     | 
| 102 | 
            -
             | 
| 103 | 
            -
                   | 
| 125 | 
            +
                  assert_equal form.errors.messages, {
         | 
| 126 | 
            +
                    title: ["must be filled"],
         | 
| 127 | 
            +
                    "hit.title": ["must be filled"],
         | 
| 128 | 
            +
                    "songs.title": ["must be filled"],
         | 
| 129 | 
            +
                    "band.label.name": ["must be filled"],
         | 
| 130 | 
            +
                    "producer.name": ["must be filled"]
         | 
| 131 | 
            +
                  }
         | 
| 132 | 
            +
                  assert_equal form.errors.size, 5
         | 
| 104 133 | 
             
                end
         | 
| 105 134 | 
             
              end
         | 
| 106 135 |  | 
| 107 | 
            -
             | 
| 108 136 | 
             
              describe "#validate with main form invalid" do
         | 
| 109 137 | 
             
                it do
         | 
| 110 | 
            -
                  form.validate("title"=>"", "band"=>{"label"=>{: | 
| 111 | 
            -
                  form.errors.messages | 
| 112 | 
            -
                  form.errors.size | 
| 138 | 
            +
                  refute form.validate("title" => "", "band" => {"label" => {name: "Fat Wreck"}}, "producer" => nil)
         | 
| 139 | 
            +
                  assert_equal form.errors.messages, title: ["must be filled"], producer: ["must be a hash"]
         | 
| 140 | 
            +
                  assert_equal form.errors.size, 2
         | 
| 113 141 | 
             
                end
         | 
| 114 142 | 
             
              end
         | 
| 115 143 |  | 
| 116 | 
            -
             | 
| 117 144 | 
             
              describe "#validate with middle nested form invalid" do
         | 
| 118 | 
            -
                before { @result = form.validate("hit"=>{"title" => ""}, "band"=>{"label"=>{: | 
| 145 | 
            +
                before { @result = form.validate("hit" => {"title" => ""}, "band" => {"label" => {name: "Fat Wreck"}}) }
         | 
| 119 146 |  | 
| 120 | 
            -
                it { @result | 
| 121 | 
            -
                it { form.errors.messages | 
| 122 | 
            -
                it { form.errors.size | 
| 147 | 
            +
                it { refute @result }
         | 
| 148 | 
            +
                it { assert_equal form.errors.messages, "hit.title": ["must be filled"] }
         | 
| 149 | 
            +
                it { assert_equal form.errors.size, 1 }
         | 
| 123 150 | 
             
              end
         | 
| 124 151 |  | 
| 125 | 
            -
             | 
| 126 152 | 
             
              describe "#validate with collection form invalid" do
         | 
| 127 | 
            -
                before { @result = form.validate("songs"=>[{"title" => ""}], "band"=>{"label"=>{: | 
| 153 | 
            +
                before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {name: "Fat Wreck"}}) }
         | 
| 128 154 |  | 
| 129 | 
            -
                it { @result | 
| 130 | 
            -
                it { form.errors.messages | 
| 131 | 
            -
                it { form.errors.size | 
| 155 | 
            +
                it { refute @result }
         | 
| 156 | 
            +
                it { assert_equal form.errors.messages, "songs.title": ["must be filled"] }
         | 
| 157 | 
            +
                it { assert_equal form.errors.size, 1 }
         | 
| 132 158 | 
             
              end
         | 
| 133 159 |  | 
| 134 | 
            -
             | 
| 135 160 | 
             
              describe "#validate with collection and 2-level-nested invalid" do
         | 
| 136 | 
            -
                before { @result = form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
         | 
| 161 | 
            +
                before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
         | 
| 137 162 |  | 
| 138 | 
            -
                it { @result | 
| 139 | 
            -
                it { form.errors.messages | 
| 140 | 
            -
                it { form.errors.size | 
| 163 | 
            +
                it { refute @result }
         | 
| 164 | 
            +
                it { assert_equal form.errors.messages, "songs.title": ["must be filled"], "band.label.name": ["must be filled"] }
         | 
| 165 | 
            +
                it { assert_equal form.errors.size, 2 }
         | 
| 141 166 | 
             
              end
         | 
| 142 167 |  | 
| 143 168 | 
             
              describe "#validate with nested form using :base invalid" do
         | 
| 144 169 | 
             
                it do
         | 
| 145 | 
            -
                  result = form.validate("songs"=>[{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
         | 
| 146 | 
            -
                  result | 
| 147 | 
            -
                  form.errors.messages | 
| 148 | 
            -
                  form.errors.size | 
| 170 | 
            +
                  result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
         | 
| 171 | 
            +
                  refute result
         | 
| 172 | 
            +
                  assert_equal form.errors.messages, "band.name": ["you're a bad person"]
         | 
| 173 | 
            +
                  assert_equal form.errors.size, 1
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
              end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
              describe "#add" do
         | 
| 178 | 
            +
                let(:album_title) { nil }
         | 
| 179 | 
            +
                it do
         | 
| 180 | 
            +
                  result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
         | 
| 181 | 
            +
                  refute result
         | 
| 182 | 
            +
                  assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"]
         | 
| 183 | 
            +
                  # add a new custom error
         | 
| 184 | 
            +
                  form.errors.add(:policy, "error_text")
         | 
| 185 | 
            +
                  assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"]
         | 
| 186 | 
            +
                  # does not duplicate errors
         | 
| 187 | 
            +
                  form.errors.add(:title, "must be filled")
         | 
| 188 | 
            +
                  assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"]
         | 
| 189 | 
            +
                  # merge existing errors
         | 
| 190 | 
            +
                  form.errors.add(:policy, "another error")
         | 
| 191 | 
            +
                  assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text", "another error"]
         | 
| 149 192 | 
             
                end
         | 
| 150 193 | 
             
              end
         | 
| 151 194 |  | 
| 152 195 | 
             
              describe "correct #validate" do
         | 
| 153 | 
            -
                before  | 
| 154 | 
            -
                   | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
                 | 
| 161 | 
            -
             | 
| 162 | 
            -
                it {  | 
| 163 | 
            -
                it { form. | 
| 196 | 
            +
                before do
         | 
| 197 | 
            +
                  @result = form.validate(
         | 
| 198 | 
            +
                    "hit"   => {"title" => "Sacrifice"},
         | 
| 199 | 
            +
                    "title" => "Second Heat",
         | 
| 200 | 
            +
                    "songs" => [{"title" => "Heart Of A Lion"}],
         | 
| 201 | 
            +
                    "band"  => {"label" => {name: "Fat Wreck"}}
         | 
| 202 | 
            +
                  )
         | 
| 203 | 
            +
                end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                it { assert @result }
         | 
| 206 | 
            +
                it { assert_equal form.hit.title, "Sacrifice" }
         | 
| 207 | 
            +
                it { assert_equal form.title, "Second Heat" }
         | 
| 208 | 
            +
                it { assert_equal form.songs.first.title, "Heart Of A Lion" }
         | 
| 164 209 | 
             
                it do
         | 
| 165 210 | 
             
                  skip "WE DON'T NEED COUNT AND EMPTY? ON THE CORE ERRORS OBJECT"
         | 
| 166 | 
            -
                  form.errors.size | 
| 167 | 
            -
                  form.errors.empty | 
| 211 | 
            +
                  assert_equal form.errors.size, 0
         | 
| 212 | 
            +
                  assert form.errors.empty
         | 
| 168 213 | 
             
                end
         | 
| 169 214 | 
             
              end
         | 
| 170 215 |  | 
| 171 | 
            -
             | 
| 172 216 | 
             
              describe "Errors#to_s" do
         | 
| 173 | 
            -
                before { form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
         | 
| 217 | 
            +
                before { form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
         | 
| 174 218 |  | 
| 175 219 | 
             
                # to_s is aliased to messages
         | 
| 176 220 | 
             
                it {
         | 
| 177 221 | 
             
                  skip "why do we need Errors#to_s ?"
         | 
| 178 | 
            -
                  form.errors.to_s | 
| 222 | 
            +
                  assert_equal form.errors.to_s, "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}"
         | 
| 223 | 
            +
                }
         | 
| 179 224 | 
             
              end
         | 
| 180 225 | 
             
            end
         |