Archive for the ‘reflection’ Category

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

Variable/optional parameter methods, symbols and ruby

First if you want only for optional parameter in a method, just see the next two examples :

 

 

def foo(bar=:hello)
  puts bar
end

foo     #=> hello
foo bye #=> bye

The second example :

 

 

def add_book(title=>"no title")
  arg={:, :author=>"anonymous", :language=>"esperanto"}
  arg.merge! p_argirb(main):020:1>
end

add_book :title=>"El Qujote", :author=>"Miguel De Cervantes", :language=>"spanish"
=> {:language=>"spanish", :title=>"El Qujote", :author=>"Miguel De Cervantes"}

add_book :title=>"El Qujote", :author=>"Miguel De Cervantes"
=> {:language=>"esperanto", :title=>"El Qujote", :author=>"Miguel De Cervantes"}

add_book :title=>"El Qujote", :author=>"Miguel De Cervantes" :foo=>:Bar
=> {:language=>"esperanto", :title=>"El Qujote", :author=>"Miguel De Cervantes"}

Sometimes is usefull to have an optional/variable parameters method, . In a recent domain specific languages (DSL) design i have to implement a object that have some optional parameters.

For example we want to add a book to our database, But we don’t want to write every single attribute, is the attribute is not present then is initialize with a default value. The properties are

add_book :title=>"El quijote", :author=> "Miguel de Cervantes"
add_book :title=>"El quijote", :author=> "Miguel de Cervantes", :tag=>"novel"
add_book :title=>"El quijote"
add_book :title=>"hogehoge", :language=>"Japanese" # => raise ArgumentError

Asking in the ruby list, Hidetoshi NAGAI, teach me how to use symbols, variable parameter methods (varargs), to create this clean example. The main idea is use a hash for parameter value preinitializate and check is the parameter is in the list. BTW, he also override the method_missing method to emulate all the getters and setters for the object.

 

 

class Book
  def initialize(properties = {})
      @props = {
          'title' => '',
          'author' => '',
          'tag' => nil,
          'price' => nil,
          'ISBN' => nil,
      }

      properties.each{|k, v|
          k = k.to_s
          raise ArgumentError, "unknown property: #{k}" unless @props.key?(k)
          @props[k] = v
      }
  end

  def method_missing(id, *args)
      prop = id.id2name
      case args.length
      when 1

          if prop[-1] == ?= # it will be a setter
              self[prop[0..-2]] = args[0]
              args[0]
          else # Else it will be "self.property(val)".
                                  # Then call "self[] = val"
              self[prop] = args[0]
              self
          end
      when 0 # the getter
          self[prop]
      else #continue the search
          super(id, *args)
      end
  end

  def [](prop)
      prop = prop.to_s
      raise ArgumentError, "unknown property: #{prop}" unless @props.key?(prop)
      @props[prop]
  end

  def []=(prop, val)
      prop = prop.to_s
      raise ArgumentError, "unknown property: #{prop}" unless @props.key?(prop)
      @props[prop] = val
  end
end

def add_book(props)
  book = Book.new(props)
  puts "case1: title : #{book.title} -- Author : #{book.author}"
  puts "case2: title : #{book[:title]} -- Author : #{book[:author]}"
  puts "case3: title : #{book['title']} -- Author : #{book['author']}"
  book
end

In a next post i explain ho to to improve the optinal parameter using meta-programming.