define_method and instance variable maddness

S

Sharon Phillips

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
 
T

Trans

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.

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.
 
G

Gary Wright

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


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
  • ||=[]).include? obj
    @lists
    • << 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
 
A

ara.t.howard

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 ;-) 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/
 
S

Sharon Phillips

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

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 ;-) 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/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top