Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 756
Oleg Kiviljov Avatar asked Aug 23 '26 15:08

Oleg Kiviljov


1 Answers

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
like image 70
Uri Agassi Avatar answered Aug 26 '26 08:08

Uri Agassi