3

I am writing a small tool for validating method arguments in Ruby. Ideally, the functionality would be like this:

class Test
  extend ArgValidator

  def say(word)
    puts word
  end

  validate_args(:say) do
    raise(ArgumentError) if not word.is_a?(String)
  end

end

Test.new.say(3)  # => ArgumentError

That is, the ArgValidator module provides a validate_args method that takes an instance method name and a block. Calling validate_args(<meth>) causes the validation code (supplied in the block) to be run before the method body. The validation block is executed against the bindings of the method body. There are two features of the above code that I am specifically striving for:

  • the target method body has no knowledge of the validation code (it is decorated)
  • the validation block should have access to the same bindings as the method body, without needing to hardcode a duplicated method signature

My current approach is to have validate_args decorate the target method with the validation code. By using Method#parameters, I can get signature information from the target method and come very close to dynamically replicating the bindings of the method body itself. Unfortunately, #parameters provides no information about default argument values.

Is there any way to achieve my goal?

Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
Sean Mackesey
  • 10,701
  • 11
  • 40
  • 66
  • Perhaps this answer on different thread may help: [How can I inspect what is the default value for optional parameter in ruby's method?](http://stackoverflow.com/a/34753714/794242) – Wand Maker Jun 11 '16 at 18:32
  • 3
    If you simply have the `validated_args` method specify the arguments they expect to validate that will simplify greatly what you're trying to accomplish. The magic appearance of `word` in the validate_args block is counterintuitive and makes it harder to understand as the `say` method definition may not be 3 lines up like you have shown the example above. You also give the person validating the args the choice as to what they want to validate... e.g. `validate_args(number1, number2, *i_dont_care_bout_these)` – Zach Dennis Jun 11 '16 at 18:45
  • I hate to say it but this is a little batty and would drive any veteran Ruby programmer up the wall. This completely contradicts the spirit and contract of Ruby which, by and large, is based around the sacred principle of [Duck Typing](https://en.wikipedia.org/wiki/Duck_typing). Literally raising a fuss when one of the arguments is not the right "type" is the wrong attitude. Your method, should it need a string, should ask for one: `word.to_s` should do the job. Why is `3` unacceptable input in this case? Having the validation declared external to the method is dark magic, best avoided. – tadman Jun 11 '16 at 20:51
  • @tadman Thanks for the advice tadman, but I disagree. All computer programs have preconditions: properties that the input must satisfy for the program to execute as intended. Duck typing does not eliminate preconditions. It just means that the required "type" is an interface (a set of defined methods) rather than a class. When writing a public API, it's important to verify that input matches preconditions. Otherwise users can end up with inscrutable error messages triggered by an explosion deep inside your program. Also, validation external to the method can be misused but is not always bad. – Sean Mackesey Jun 11 '16 at 21:25
  • @ZachDennis I take your point Zach. There is a tradeoff here between brittleness and readability of code. I understand the system I propose can be abused, but it doesn't have to be. Indeed I'm unlikely to use this without putting the validator code immediately under the target method. Hardcoding argument names in two places makes the code more brittle, but if validator code is kept adjacent to the target then the readability issue is almost nonexistent. – Sean Mackesey Jun 11 '16 at 21:31

0 Answers0