From: Erik Veenstra Date: 2006-02-09T01:08:23+09:00 Subject: Re: Meta-Meta-Programming 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_method(:method_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_method(:method_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 ----------------------------------------------------------------