E
Erik Veenstra
The story continues...
Now, we have this Module#wrap_method for wrapping instance
methods. But what about wrapping module methods, like
Module#wrap_module_method?
We are going to take this dangerous type checking thing to the
next level. Just as an example. JUST AS AN EXAMPLE!!!
Imagine, we want to move the types to be positioned before the
method instead of after the method. Just for better
readability:
class Foo
def_types Numeric, String, [:to_s, :gsub]
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
# Very long method...
end
end
.... instead of:
class Foo
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
# Very long method...
end
typed :bar, Numeric, String, [:to_s, :gsub]
end
We can do this by storing the types in def_types and
overwriting Foo::method_added. But what about the old
functionality in Foo::method_added? Sure, alias to another
method and than use this alias. That's the common way to work
around this problem. (I've never liked it...) But, again, we
can use "wrap_method" to add the new functionality to the
original method. Introducing "wrap_module_method":
def def_types(*types)
wrap_module_methodmethod_added) do |org_method, args, block|
if types
method_name = args[0]
t = types
types = nil # Avoid looping
typed(method_name, *t)
end
org_method.call(*args, &block) if org_method
end
end
Do you see that this "wrap_module_method" looks like
"wrap_method"? They should look the same. They are brother and
sister.
Since we do a "wrap_module_method" in "method_added" and
"wrap_module_method" does a "wrap_method" and "wrap_method"
adds a method and thus does a "method_added", the wrapped
"method_added" gets called over and over again. That's why I
introduced this loop-avoiding-system. You only have to add this
mechanism when wrapping Module#method_added, not for other
module methods.
Once again, it should be possible to wrap the wrapper:
class Foo
def_types Numeric, String, [:to_s, :gsub]
def_stat "/tmp/stats.log"
def bar1(x, y, z)
end
def bar2(x, y, z) # bar2 is neither logged, nor checked.
end
end
See below for a full implementation of both wrapping methods
and of both type checking methods.
(Once again, it's not about this type checking, or duck-type
checking: it's all about "wrap_method" and
"wrap_module_method".)
gegroet,
Erik V. - http://www.erikveen.dds.nl/
----------------------------------------------------------------
class Module
# Meta-Meta-Programming
# With this, we can create monitoring functions.
# It might not be clearly readable,
# but it's written only once.
# Write once, read never.
# Forget about the internals.
# Just use it.
# It should be part of Ruby itself, anyway...
def wrap_method(method_name, &block1)
@_wrap_method_count_ ||= 0
@_wrap_method_count_ += 1
prefix = "_wrap_method_#{@_wrap_method_count_}"
module_eval <<-EOF
if instance_methods.include?#{method_name}.to_s)
alias :#{prefix}_org :#{method_name} # Store the original method for later use.
end
define_method#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.
def #{method_name}(*args2, &block2)
if respond_to?#{prefix}_org)
#{prefix}_block.call(method#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2!
else
#{prefix}_block.call(nil, args2, block2)
end
end
EOF
end
def wrap_module_method(method_name, &block1)
class << self
self
end.module_eval do
wrap_method(method_name) do |org_method, args2, block2|
block1.call(org_method, args2, block2)
end
end
end
end
----------------------------------------------------------------
class Module
# Type checking.
# Or duck-type checking.
# Example:
# class Foo
# def_types String, Numeric, [:to_s, :gsub]
# def :bar(x, y, x)
# # x should be Numeric
# # y should be a String
# # z should respond to :to_s and :gsub
# end
# end
def def_types(*types)
wrap_module_methodmethod_added) do |org_method, args, block|
if types
method_name = args[0]
t = types
types = nil # Avoid looping
typed(method_name, *t)
end
org_method.call(*args, &block) if org_method
end
end
# Example:
# class Foo
# def :bar(x, y, x)
# # x should be Numeric
# # y should be a String
# # z should respond to :to_s and :gsub
# end
# typed :bar, String, Numeric, [:to_s, :gsub]
# end
def typed(method_name, *types)
wrap_method(method_name) do |org_method, args, block|
args.each_with_index do |arg, n|
[types[n]].flatten.each do |typ|
if typ.kind_of?(Module)
unless arg.kind_of?(typ)
raise ArgumentError, "Wrong argument type (#{arg.class} instead of #{typ}, argument #{n+1})."
end
elsif typ.kind_of?(Symbol)
unless arg.respond_to?(typ)
raise ArgumentError, "#{arg} doesn't respond to :#{typ} (argument #{n+1})."
end
else
raise ArgumentError, "Wrong type in types (#{typ}, argument #{n+1})"
end
end
end
org_method.call(*args, &block)
end
end
end
----------------------------------------------------------------
Now, we have this Module#wrap_method for wrapping instance
methods. But what about wrapping module methods, like
Module#wrap_module_method?
We are going to take this dangerous type checking thing to the
next level. Just as an example. JUST AS AN EXAMPLE!!!
Imagine, we want to move the types to be positioned before the
method instead of after the method. Just for better
readability:
class Foo
def_types Numeric, String, [:to_s, :gsub]
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
# Very long method...
end
end
.... instead of:
class Foo
def bar(x, y, z)
# x should be Numeric
# y should be a String
# z should respond to :to_s and :gsub
# Very long method...
end
typed :bar, Numeric, String, [:to_s, :gsub]
end
We can do this by storing the types in def_types and
overwriting Foo::method_added. But what about the old
functionality in Foo::method_added? Sure, alias to another
method and than use this alias. That's the common way to work
around this problem. (I've never liked it...) But, again, we
can use "wrap_method" to add the new functionality to the
original method. Introducing "wrap_module_method":
def def_types(*types)
wrap_module_methodmethod_added) do |org_method, args, block|
if types
method_name = args[0]
t = types
types = nil # Avoid looping
typed(method_name, *t)
end
org_method.call(*args, &block) if org_method
end
end
Do you see that this "wrap_module_method" looks like
"wrap_method"? They should look the same. They are brother and
sister.
Since we do a "wrap_module_method" in "method_added" and
"wrap_module_method" does a "wrap_method" and "wrap_method"
adds a method and thus does a "method_added", the wrapped
"method_added" gets called over and over again. That's why I
introduced this loop-avoiding-system. You only have to add this
mechanism when wrapping Module#method_added, not for other
module methods.
Once again, it should be possible to wrap the wrapper:
class Foo
def_types Numeric, String, [:to_s, :gsub]
def_stat "/tmp/stats.log"
def bar1(x, y, z)
end
def bar2(x, y, z) # bar2 is neither logged, nor checked.
end
end
See below for a full implementation of both wrapping methods
and of both type checking methods.
(Once again, it's not about this type checking, or duck-type
checking: it's all about "wrap_method" and
"wrap_module_method".)
gegroet,
Erik V. - http://www.erikveen.dds.nl/
----------------------------------------------------------------
class Module
# Meta-Meta-Programming
# With this, we can create monitoring functions.
# It might not be clearly readable,
# but it's written only once.
# Write once, read never.
# Forget about the internals.
# Just use it.
# It should be part of Ruby itself, anyway...
def wrap_method(method_name, &block1)
@_wrap_method_count_ ||= 0
@_wrap_method_count_ += 1
prefix = "_wrap_method_#{@_wrap_method_count_}"
module_eval <<-EOF
if instance_methods.include?#{method_name}.to_s)
alias :#{prefix}_org :#{method_name} # Store the original method for later use.
end
define_method#{prefix}_block) {block1} # Store the block of the call to Module#wrap_method.
def #{method_name}(*args2, &block2)
if respond_to?#{prefix}_org)
#{prefix}_block.call(method#{prefix}_org), args2, block2) # Note that this is not *args2 and not &block2!
else
#{prefix}_block.call(nil, args2, block2)
end
end
EOF
end
def wrap_module_method(method_name, &block1)
class << self
self
end.module_eval do
wrap_method(method_name) do |org_method, args2, block2|
block1.call(org_method, args2, block2)
end
end
end
end
----------------------------------------------------------------
class Module
# Type checking.
# Or duck-type checking.
# Example:
# class Foo
# def_types String, Numeric, [:to_s, :gsub]
# def :bar(x, y, x)
# # x should be Numeric
# # y should be a String
# # z should respond to :to_s and :gsub
# end
# end
def def_types(*types)
wrap_module_methodmethod_added) do |org_method, args, block|
if types
method_name = args[0]
t = types
types = nil # Avoid looping
typed(method_name, *t)
end
org_method.call(*args, &block) if org_method
end
end
# Example:
# class Foo
# def :bar(x, y, x)
# # x should be Numeric
# # y should be a String
# # z should respond to :to_s and :gsub
# end
# typed :bar, String, Numeric, [:to_s, :gsub]
# end
def typed(method_name, *types)
wrap_method(method_name) do |org_method, args, block|
args.each_with_index do |arg, n|
[types[n]].flatten.each do |typ|
if typ.kind_of?(Module)
unless arg.kind_of?(typ)
raise ArgumentError, "Wrong argument type (#{arg.class} instead of #{typ}, argument #{n+1})."
end
elsif typ.kind_of?(Symbol)
unless arg.respond_to?(typ)
raise ArgumentError, "#{arg} doesn't respond to :#{typ} (argument #{n+1})."
end
else
raise ArgumentError, "Wrong type in types (#{typ}, argument #{n+1})"
end
end
end
org_method.call(*args, &block)
end
end
end
----------------------------------------------------------------