Reputation: 777
I have method that takes as a parameter an array of integer numbers, and I would like to use the decorator pattern to validate first if each of the numbers in the array is in a specified range.
I've seen decorator in ruby that extend class or include modules, but this seems a little excesive to me, is there a way to just decorate a method without relaying on a class or module ?
EDIT I have several methods that takes an array as parameter, and need to validate the range of the items. Instead of inline coding the validation in each of this method, I want a decorator for all of this methods, but I was wondering if decorator classes/modules are the only ones that exist in Ruby ?
class MyClass
..some code here ...
def method(array)
..some code here...
end
end
Upvotes: 4
Views: 3889
Reputation: 369536
Here's a simple example of how to wrap methods with a validator:
class Foo
def foo(a, b, c)
p a, b, c
end
def bar(a, b)
p a, b
end
validate(:foo, :bar) {|*args, &blk| args.reduce(:+) == 6 }
end
The Module#validate
method takes a list of method names, and a block with the validation logic for those methods.
foo = Foo.new
foo.foo(1, 2, 3)
# 1
# 2
# 3
foo.bar(2, 4)
# 2
# 4
foo.foo(2, 3, 4)
# [2, 3, 4] (Validator::ValidationError)
foo.bar(2, 3)
# [2, 3] (Validator::ValidationError)
As you can see, the validator rejected the argument list in the last two calls because they didn't pass the condition in the block.
This is the "magic" which makes it all happen, which isn't actually that magic. We generate a module
which overrides the methods we want to validate with a version that raise
s an exception if the validation fails and then simply calls super
. Then we prepend
that module to the class/module that we are currently in, i.e. the one that the call to the validate
method is in. And that's it, basically.
I chose to also be a good Ruby citizen and wrap the whole thing in a Refinement, so you need to say
using Validator
to actually activate it.
module Validator
class ValidationError < ArgumentError; end
refine Module do
def validate(*methods, &validator)
prepend(Module.new do
methods.each do |method|
define_method(method) do |*args, &blk|
raise ValidationError, args.inspect unless yield *args
super(*args, &blk)
end
end
end)
end
end
end
Upvotes: 7
Reputation: 211670
Normally when you're talking about decorators you'd use that in the context of a class, not a single method.
Here's an example of a class you can decorate:
class ValidatorSet
def initialize
@validators = [ ]
end
def <<(validator)
@validators << validator
end
def valid?(list)
@validators.all? do |v|
if v.respond_to?(:valid?)
v.valid?(list)
else
list.all?(&v)
end
end
end
end
class IsUnderTen
def valid?(list)
list.all? { |n| n < 10 }
end
end
validator = ValidatorSet.new
validator << IsUnderTen.new
validator << lambda { |n| n > 0 }
validator.valid?([ 1, 2, 3, 4, 5 ])
# => true
validator.valid?([ -1 ])
# => false
validator.valid?([ 9, 10, 11, 12 ])
# => false
You see this sort of thing used in the ActiveRecord validation chain.
Upvotes: 3