Archive for the ‘metaprogramming’ Category
blocks parameters list and metaprogramming
Filed under: api design, code, design, metaprogramming, Ruby |
Comments (1)
I have been playing with a Aquarium, the new AOP framework in Ruby. A point advice is create for example :
before :type=>A, :method=>:foo do |execution_point, *args| self.some_method() # call a class method from A other_method() # call other class method from A # call a method from the caller object execution_point.context.advised_object.bar() end
The DSL is nice but i found the access to the caller object is a tedious. I prefer something like this :
before :type=>A, :method=>:foo do |execution_point,this , *args| self.some_method() # call a class method from A other_method() # call other class method from A # call a method from the caller object this.bar() end
Dean Wampier think thats would be great if the user could specify combinations
like these:
|join_point, the_self, *method_args|
|join_point|
|the_self|
|the_self, *method_args|
|join_point, the_self|
|join_point, *method_args|
But the point is that the advices is create in the “definition time class” using some code generation at
runtime. How can i know the arity of the argument list.
module Kernel def before (name, &block) define_method(name){ if block.arity == 1 yield :only_one elsif block.arity == 2 yield :one,:two elsif block.arity == -2 yield :one, :a , :splater, :argument end } end end class A before :foo do |first| puts first end end a = A.new a.foo class A before :foo do |first , second| puts second end end a2 = A.new a2.foo class A before :foo do |first , *splat_arg| puts splat_arg end end a3 = A.new a3.foo
Thats great, the block can be acceded because its a Proc class. Now we can predict the number of parameter of that the user has write and create a more friendly DSL.
How to define a attribute using metaprogramming.
Just for fun and learning. We can add an attribute using meta-programming :
class MyClass attr_accessor :id, :diagram, :telegram end
We can define a custom function :
class Class def my_attr_accessor( *args ) args.each do |name| self.class_eval do attr_accessor :"#{name}" end end end end class MyNewClass my_attr_accessor :id, :diagram, :telegram end
We can create a set of attributes using class_eval :
class Another_class def initialize ["id","diagram","telegram"].each do |name| self.class.class_eval do attr_accessor :"#{name}" end end end end
Or more ruby idiomatic
class Yet_another_class def initialize self.class.class_eval do attr_accessor *[:id,:diagram,:telegram] end end end
Metaprogramming and reflection for optional parameters.
In the previous post, the example use reflection via method missing, to emulate some getters and setters.
Another option is to define the setters and getters methods on the fly, refactoring the Mult module using metaprogramming
module Mult def create_method(name, &block) self.class.send(:define_method, name, &block) end def initialize(properties = {}) props.each do |k, v| create_method(k) { @props[k] } create_method("#{k}=") { |value| @props[k]=value} end properties.each do |k, v| #k = k.to_s raise ArgumentError, "unknown property: #{k}" unless @props.key?(k) self.send("#{k}=",v) end end end
The iteration over the hash defaults value defines a setter and a getter for each of the properties we define in the book class.
In a second refactoring round, i replace the definitions of the getters/setters with a call to class_eval and defining inside the attribute accessor
module Mult def initialize(properties = {}) props.each do |k, v| self.class.class_eval do attr_accessor :"#{k}" end self.send("#{k}=",v) end properties.each do |k, v| k = k.to_s raise ArgumentError, "unknown property: #{k}" unless @props.key?(k) self.send("#{k}=",v) end end end
Ruby : module callbacks
We can override some callbacks methods in a module to modify the behaivor of a class, or the methods of a class:
If we wan to preform an action when a class “include” a module we need to override the method included in our module.
module Foo def self.included(mod) puts "and now CallbackTest included has been used by #{mod.inspect} form Foo..." end def self.method_added(method) puts "Added #{method}" end end
If we want , for example, log the methods added to our class we must override method_added method.
class Bar include Foo def self.method_added(method) puts "Added #{method}" end end class Bar def another_method end end
How to extends ruby classes
Add the quickshort algorithm to the array class.
class Array def qs return self if size <=1 pivot = shift less, more = partition{ |y| y < pivot } [*less.qs, pivot, *more.qs] end end
Add the factorial operation to the integer class
class Integer def factorial return 1 if self <= 1 self * (self-1).factorial end end