Define_method and instance variable maddness

Hi,

This problem has been driving me nuts, so I've finally given in and decided to ask for help.

I'm just cutting my teeth on metaprogramming but am stuck referencing instance variables within define_method

Here's the code snippet in question. It works, but is extremely ugly with its constant usage of instance_variable_get and set.

child_list_name= ('@'+(child.to_s)+"_list")
self_method= (self.name.downcase+'=').to_sym
method_name= ('add_'+child.to_s).to_sym

define_method method_name do |new_child|
   instance_variable_set(child_list_name, []) if instance_variable_get(child_list_name).nil?
   unless instance_variable_get(child_list_name).include? new_child
     instance_variable_set(child_list_name, (instance_variable_get(child_list_name) << new_child))
     if new_child.respond_to? self_method
       new_child.send self_method, self
     end
   end
end

This is what I'd really like the new method to look like (for child== :personnel):

def add_personnel (new_personnel)
   unless (@personnel_list||=[]).include? new_personnel
     @personnel_list << new_personnel
     self_method= (self.name.downcase+'=').to_sym
     if new_personnel.respond_to? self_method
       new_personnel.send self_method, self
     end
   end
end

Any help appreciated.

Cheers,
Dave

I make two possible suggestions. Create yourself something shorter
than instance_variable_set/get. Eg.

    def __iv(name,value=Exception)
      if value==Exception
        instance_variable_get("@#{name}")
      else
        instance_variable_set("@#{name}", value)
      end
    end

That will make it easier to read:

  define_method method_name do |new_child|
    __iv(child_list_name, ) if __iv(child_list_name).nil?
    unless __iv(child_list_name).include? new_child
      __iv(child_list_name, (__iv(child_list_name) << new_child))
      if new_child.respond_to? self_method
        new_child.send self_method, self
      end
    end
  end

Secondly, use eval if it makes the code easier to maintain.

T.

···

On Nov 28, 4:28 am, Sharon Phillips <phillip...@yahoo.co.uk> wrote:

Hi,

This problem has been driving me nuts, so I've finally given in and
decided to ask for help.

I'm just cutting my teeth on metaprogramming but am stuck referencing
instance variables within define_method

Here's the code snippet in question. It works, but is extremely ugly
with its constant usage of instance_variable_get and set.

child_list_name= ('@'+(child.to_s)+"_list")
self_method= (self.name.downcase+'=').to_sym
method_name= ('add_'+child.to_s).to_sym

define_method method_name do |new_child|
   instance_variable_set(child_list_name, ) if
instance_variable_get(child_list_name).nil?
   unless instance_variable_get(child_list_name).include? new_child
     instance_variable_set(child_list_name,
(instance_variable_get(child_list_name) << new_child))
     if new_child.respond_to? self_method
       new_child.send self_method, self
     end
   end
end

This is what I'd really like the new method to look like (for
child== :personnel):

def add_personnel (new_personnel)
   unless (@personnel_list||=).include? new_personnel
     @personnel_list << new_personnel
     self_method= (self.name.downcase+'=').to_sym
     if new_personnel.respond_to? self_method
       new_personnel.send self_method, self
     end
   end
end

Any help appreciated.

It looks like you are maintaining multiple lists but I don't
understand why you are encoding the name of the list in the method
names and in the instance variables. Why not just use a hash
and methods that take a list name parameter?

def add(list, obj)
   unless (@lists[list]||=).include? obj
     @lists[list] << obj
     if obj.respond_to? :add_container
       obj.add_container(self)
     end
   end
end

Now if you really want something like 'add_personnel' it is easy:

listname='personnel'
define_method('add_#{listname}') do |obj|
   add(listname, obj)
end

The added object can derive a name directly from 'self' (self.name.downcase)
there is no need to figure that out for the object.

Note: I didn't test this code, but I hope you get the idea.

Bottom line: use a hash and access the key/value pairs instead of
trying to construct and dereference instance variable names.

Gary Wright

···

On Nov 28, 2007, at 4:28 AM, Sharon Phillips wrote:

This is what I'd really like the new method to look like (for child== :personnel):

def add_personnel (new_personnel)
  unless (@personnel_list||=).include? new_personnel
    @personnel_list << new_personnel
    self_method= (self.name.downcase+'=').to_sym
    if new_personnel.respond_to? self_method
      new_personnel.send self_method, self
    end
  end
end

eval strings, it's faster to debug by light years since you can dump it out to the screen. it also lets you use blocks. the cool kids use 'define_method', but then their methods cease to take blocks, enclose scope which cannot be freed, and are a pain in the butt to debug. starting with your sample method, which is the best way to start of course, i'd do

cfp:~ > cat a.rb
module Adder
   def attr_adder *names
     names.each do |name|
       code = <<-code
         def add_#{ name } (new_#{ name })
           unless (@#{ name }||=).include? new_#{ name }
             @#{ name } << new_#{ name }
             self_method= (self.name.downcase+'=').to_sym
             if new_#{ name }.respond_to? self_method
               new_#{ name }.send self_method, self
             end
           end
         end
       code
       puts code if $DEBUG
       module_eval code
     end
   end
   def self.included other
     other.extend self
   end
end

class C
   include Adder
   attr_adder :personnel, :foobar
end

obviously writing this is quite easy: just start with your template and do some string substitution :wink: being able to dump the code out is HUGE when it comes to metaprogramming. i've even had libs that dumped it out so it could be, gasp, rdoc'd.

define_method is quite useful at times, but i'd say this isn't it.

kind regards.

a @ http://codeforpeople.com/

···

On Nov 28, 2007, at 2:28 AM, Sharon Phillips wrote:

Here's the code snippet in question. It works, but is extremely ugly with its constant usage of instance_variable_get and set.

child_list_name= ('@'+(child.to_s)+"_list")
self_method= (self.name.downcase+'=').to_sym
method_name= ('add_'+child.to_s).to_sym

define_method method_name do |new_child|
  instance_variable_set(child_list_name, ) if instance_variable_get(child_list_name).nil?
  unless instance_variable_get(child_list_name).include? new_child
    instance_variable_set(child_list_name, (instance_variable_get(child_list_name) << new_child))
    if new_child.respond_to? self_method
      new_child.send self_method, self
    end
  end
end

This is what I'd really like the new method to look like (for child== :personnel):

def add_personnel (new_personnel)
  unless (@personnel_list||=).include? new_personnel
    @personnel_list << new_personnel
    self_method= (self.name.downcase+'=').to_sym
    if new_personnel.respond_to? self_method
      new_personnel.send self_method, self
    end
  end
end

Any help appreciated.

--
it is not enough to be compassionate. you must act.
h.h. the 14th dalai lama

eval strings, it's faster to debug by light years since you can dump it out to the screen. it also lets you use blocks. the cool kids use 'define_method', but then their methods cease to take blocks, enclose scope which cannot be freed, and are a pain in the butt to debug. starting with your sample method, which is the best way to start of course, i'd do

Funnily enough, that's how I'd done this originally, including calling my code variable 'code', using 'CODE' as the terminator and printing it to screen for debugging. I changed because I thought I was doing it the wrong way, and that the 'proper' way to do this sort of this was to use define_method.

However... I really like Garry Wright's suggestion of using a hash to manage these lists. Much neater design than mine.

Thanks for the suggestions.

Cheers,
Dave

···

On 29/11/2007, at 3:22 AM, ara.t.howard wrote:

On Nov 28, 2007, at 2:28 AM, Sharon Phillips wrote:

Here's the code snippet in question. It works, but is extremely ugly with its constant usage of instance_variable_get and set.

child_list_name= ('@'+(child.to_s)+"_list")
self_method= (self.name.downcase+'=').to_sym
method_name= ('add_'+child.to_s).to_sym

define_method method_name do |new_child|
instance_variable_set(child_list_name, ) if instance_variable_get(child_list_name).nil?
unless instance_variable_get(child_list_name).include? new_child
   instance_variable_set(child_list_name, (instance_variable_get(child_list_name) << new_child))
   if new_child.respond_to? self_method
     new_child.send self_method, self
   end
end
end

This is what I'd really like the new method to look like (for child== :personnel):

def add_personnel (new_personnel)
unless (@personnel_list||=).include? new_personnel
   @personnel_list << new_personnel
   self_method= (self.name.downcase+'=').to_sym
   if new_personnel.respond_to? self_method
     new_personnel.send self_method, self
   end
end
end

Any help appreciated.

eval strings, it's faster to debug by light years since you can dump it out to the screen. it also lets you use blocks. the cool kids use 'define_method', but then their methods cease to take blocks, enclose scope which cannot be freed, and are a pain in the butt to debug. starting with your sample method, which is the best way to start of course, i'd do

cfp:~ > cat a.rb
module Adder
def attr_adder *names
   names.each do |name|
     code = <<-code
       def add_#{ name } (new_#{ name })
         unless (@#{ name }||=).include? new_#{ name }
           @#{ name } << new_#{ name }
           self_method= (self.name.downcase+'=').to_sym
           if new_#{ name }.respond_to? self_method
             new_#{ name }.send self_method, self
           end
         end
       end
     code
     puts code if $DEBUG
     module_eval code
   end
end
def self.included other
   other.extend self
end
end

class C
include Adder
attr_adder :personnel, :foobar
end

obviously writing this is quite easy: just start with your template and do some string substitution :wink: being able to dump the code out is HUGE when it comes to metaprogramming. i've even had libs that dumped it out so it could be, gasp, rdoc'd.

define_method is quite useful at times, but i'd say this isn't it.

kind regards.

a @ http://codeforpeople.com/
--
it is not enough to be compassionate. you must act.
h.h. the 14th dalai lama