Oleg Kiviljov Oleg Kiviljov - 4 years ago 98
Ruby Question

Please explain this metaprogramming magic of ruby

module HashInitialized
def hash_initialized(*fields)
define_method(:initialize) do |h|
missing = fields - h.keys
raise Exception, "Not all fields set: #{missing}" if missing.any?

h.each do |k,v|
instance_variable_set("@#{k}", v) if fields.include?(k)
end
end
end
end

class Cheese
extend HashInitialized
attr_accessor :color, :odor, :taste
hash_initialized :color, :odor, :taste
end


Okay so what I understand:


  1. We extend the class Cheese with methods from HashInitialized, so the method hash_initialized from module HashInitialized becomes available as class method in Cheese

  2. We pass 3 symbols to hash_initialized method

  3. In hash_initialized method those 3 symbols are put in array called fields because of * operator

  4. In hash_initialized method we define the initialize method

  5. Now its the part I dont understand, what is the |h| refering to? Why are we able to call h.keys on |h|. Seems like it is Hash, but I dont see how it was passed there.

  6. Then we iterate of this |h| Hash, to set up instance variables, everything is clear here except the fact that |h| responds to each so it must be either array or a Hash, and I still dont understand where do those values come from.



Any help appreciated!

Code taken from rubymonk.com, Metaprogramming Ruby: Ascent

Answer Source

define_method(:initialize) do |h|... created an initialize method with one parameter (h). This means that it creates a constructor which expects the hash. Imagine that the code created looks like this:

class Cheese

  def initialize(h)
    missing = fields - h.keys
    raise Exception, "Not all fields set: #{missing}" if missing.any?

    h.each do |k,v|
      instance_variable_set("@#{k}", v) if fields.include?(k) 
    end
  end
end
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download