Where did this value variable come from?

67 Views Asked by At

I was reading through an example of how to use metaprogramming to make attribute accessors, and I'm a bit confused on where this value variable came from:

class AttrAccessorObject
  def self.my_attr_accessor(*names)
    names.each do | name |
      define_method(name) {
        self.instance_variable_get("@#{name}".to_sym)
      }
      define_method("#{name}=") do | value |
        self.instance_variable_set("@#{name}".to_sym, value)
      end
    end
  end
end

I understand that the instance_variable_set method needs both the instance variable and the value to set the instance variable's new value, but where did this value variable in the code come from? Also, since it's using a "do/end" loop to use the value, I'm assuming that the "define_method("#{name}=") evaluates to an array of values, is that correct?

3

There are 3 best solutions below

0
max pleaner On

If you think of what a 'normal' method definition looks like:

def some_name=(value)
  @some_name = value
end

The |value| part is the same as (value) - it declares the arguments that are accepted by the method you're defining. So if I wanted to make a method that takes 3 args:

define_method(:some_method) do |arg1, arg2, arg2|
  [arg1, arg2, arg3]
end

Also, since it's using a "do/end" loop to use the value, I'm assuming that the "define_method("#{name}=") evaluates to an array of values, is that correct?

No, blocks are not just used with arrays. Think of a block like an anonymous function that is used as a parameter for another function. The function you call (e.g. each) may internally invoke the anonymous function 0, 1, or any number of times. In the case of each, it calls it N times (one time for each array element):

def my_each(array, &blk)
  for elem in array do
    blk.call(elem)
  end
end

but in the case of tap, it calls it only once:

def my_tap(obj, &blk)
  blk.call(obj)
  obj
end
0
Fabio On

value is part of method signature you are defining.

define_method can be explained as adding a "label" to the block of code and saving it's reference in the class for later consumption.

define_method("print_two_values") do | value1, value2 |
    puts "first: #{value1}, second: #{value2}"
end

Can be rewritten as

def print_two_values(value1, value2)
    puts "first: #{value1}, second: #{value2}"
end
4
Cary Swoveland On

It should not go unsaid that there are some weaknesses with the referenced code.

  1. There is no need to convert the first argument of Object#instance_variable_get and Object#instance_variable_set to a symbol.

  2. (Picky) There is no need for self. to precede instance_variable_get and instance_variable_set, as self is the implied receiver when a method is invoked without an explicit receiver.

  3. (Mainly) The method is unnecessarily complex as it is much easier to use Ruby's method Module#attr_accessor, which is purpose-built to create getter and setter methods:

class AttrAccessorObject
  def self.my_attr_accessor(*names)
    attr_accessor(*names)
  end
end

AttrAccessorObject.my_attr_accessor "pig", "owl"
  #=> ["pig", "owl"] 
AttrAccessorObject.instance_methods(false)
  #=> [:pig, :pig=, :owl, :owl=] 
inst = AttrAccessorObject.new
  #=> #<AttrAccessorObject:0x00005661152d6ed8> 
inst.owl = 'hoot'
  #=> "hoot" 
inst.owl
  #=> "hoot"