dry-validation icon indicating copy to clipboard operation
dry-validation copied to clipboard

Support for "validate" helper within rule blocks

Open solnic opened this issue 3 years ago • 6 comments

Support for executing predicates using validate method within rule blocks will make it much easier to apply various checks with extra conditions:

params do
  optional(:per_page).filled(:integer)
end

rule(:per_page) do
  if key?
    key.failure("must be within 0..20 range") unless validate(gt?: 0, lteq?: 20)
  end
end

See the original conversation: https://github.com/dry-rb/dry-validation/issues/678

solnic avatar Dec 16 '20 06:12 solnic

Will it be possible to call validate inside rule block without providing an explicit key failure as shown above? Predicate macros and custom registered macros have key failures defined in them already.

musaffa avatar Dec 16 '20 15:12 musaffa

smth like valid?(gt?: 0, lteq?: 20). I'm not sure if it should be done in a single PR

flash-gordon avatar Dec 16 '20 17:12 flash-gordon

Will it be possible to call validate inside rule block without providing an explicit key failure as shown above?

@musaffa yes! I just updated the example but I think @flash-gordon has a point and we should have valid? too and I agree it should be in a separate PR as it would be a distinct new feature too.

solnic avatar Dec 17 '20 05:12 solnic

I monkey-patched Evaluator to add validate as a proof of concept:

module Dry
  module Validation
    class Evaluator
      def validate(*args)
        macros = args.each_with_object([]) do |spec, macros|
          case spec
          when Hash
            spec.each do |k, v|
              macros << [k, v.is_a?(Array) ? v : [v]]
            end
          else
            macros << Array(spec)
          end
        end
  
        macros.each do |args|
          macro = macro(*args.flatten(1))
          instance_exec(**macro.extract_block_options(_options.merge(macro: macro)), &macro.block)
        end
      end
    end
  end
end

Then you can do:

rule(:age) do
  validate(:filled?, gt?: 18)
end

I just took these methods from Rule to prepare the macros and this from Evaluator to run the macros and merged them.

I want to refactor it, write tests and docs and open a PR, but I wanted to clear this out with y'all first: I think in Rule we just assign @macros as it's passed (@macros = macros) here and here and move all the macro parsing to Evaluator. Sounds good?

zavan avatar Nov 16 '22 14:11 zavan

I forked and created a branch if you want to take a look: https://github.com/dry-rb/dry-validation/compare/main...zavan:dry-validation:macros-within-rule

No tests yet and it breaks the existing ones relying on Contract doubles, as contract is expected to respond to #macro.

zavan avatar Nov 16 '22 15:11 zavan

It would be great to have this. If you could open a PR with fixed tests that would be awesome.

solnic avatar Nov 26 '22 08:11 solnic