On 11/26/05, Ross Bamford <rosco / roscopeco.remove.co.uk> wrote:

I hope can answer some of your questions:

>
> 1) Is this the right way to define a method with a dynamic name?
>
>                eval <<-EOC
>                  def #{method_name}
>                    'You called #{method_name}'
>                  end
>                EOC
>
> it seems to(?) work, but I wonder if theres a better way?

You can also use define_method, but it has limitations:

class A
  def hello
    puts "hello"
  end
end

a = A.new
a.hello

method_name = "hello"
A.class_eval {
define_method method_name do
    puts "You called #{method_name}"
end
}

a.hello
-- OUTPUT --
hello
You called hello

You can't define methods that take blocks (as you can't pass a block
argument to a block) which means that you need to use your original method
(eval + string) if you want to dynamically create a method that takes
a block argument.

Also note the differences in scope between using "def ... end" and a
define_method + a closure.

>
> 2) I'm struggling with boolean attributes, and question marks.
> attr_accessor (etc) won't allow me to pass those names in (I guess because
> they're illegal instance var names). I tried:
>
>        attr_accessor :is_green
>        alias is_green? is_green
>
> which works, but again it strikes me theres probably a better way that
> I've missed.

I can't think of a standard way to do this that is quicker but you could
do this:

class Module
  def predicate(*names)
    names.each do |name|
      attr_accessor name
      alias_method "#{name}?", name
    end
  end
end

class A
  predicate :is_green
end

a = A.new
a.is_green = true
p a.is_green?
-- OUTPUT --
true

>
> 3) Does this
>
>        class MyClass
>          @field = "one"
>       end
>
> create a class or instance variable? And from that, does
>
>        class MyClass
>          @@field = "one"
>        end
>
> create a class variable on MyClass, Class, or something else? I've tried a
> few experiments and am more confused than when I started :)

Just to add a comment to David's explanation.

class A
  @foo = "A's foo"
end
p A.instance_eval { @foo }

A.instance_eval { @foo = "A's foo again" }

class A
  puts @foo
end
-- OUTPUT --
A's foo
A's foo again

The thing to get your head around is that a class is an object in its own right
(i.e. it is an instance of class Class).

When you open a class (with say "class A"), all the subsequent statements
are executed in the context of that class instance object.

Regarding @@class_variables - they work a bit like globals within a
class hierarchy. For example:

class Model
  @@models = []

  def models
    @@models
  end
end

class Element < Model
  @@models = ['Hello from Element']
end

class Schema < Model
  @@models = ['Hello from Schema']
end

e = Element.new
p e.models
__END__
["Hello from Schema"]

In other words, they do not belong to a class as such but a class hierarchy.
(Note how setting @@models in Schema has changed it for Element).

Personally, I avoid using @@class_variables and use class instance variables
(with accessors) instead - much less chance of accidentally stomping on
another class's state.

Regards,

Sean