Need help understanding metaclassing

I’ve been reading lots of Ruby tutorials trying to figure out
metaclassing - the one that got me closest was Why?s poignant guide to
Ruby, but I’m still struggling over some of the Ruby-specific syntax.

I attached a class I’ve been writing that needs to be highly extensible,
and it’s loaded with comments I wrote to try to understand each part of
it. If any part of the comments are wrong, it would be nice to have my
misconceptions cleared up. I’ve only been using Ruby for a day and, wow.

Here’s the part I’m having the hardest time figuring out, and Why?s
guide kind of skipped over it:

  metaclass.instance_eval do
    define_method( a ) do |val|
      @attribs ||= {}
      @attribs[a] = val
    end
  end

So, I understand that the block inside the instance_eval will be
executed in the instance of the object(meaning when I create a new
object, right?)

However, I don’t understand the next part. define_method is a method
call to the method that is defined further down, right? But what is the
" do |val|" after the method call, and what is the point of the two
lines after that?

If anyone could explain this to me, I would be greatly indebted.

Hi –

On Fri, 26 Oct 2007, Chris C. wrote:

guide kind of skipped over it:
object, right?)

However, I don’t understand the next part. define_method is a method
call to the method that is defined further down, right? But what is the
" do |val|" after the method call, and what is the point of the two
lines after that?

If anyone could explain this to me, I would be greatly indebted.

The do/end sequence is a code block. Have a look at some of the
tutorials – they’ll definitely talk about code blocks. When you pass
a code block to define_method, the code block serves as the method
body for the method you’re defining.

The lines after that assign an empty hash to @aattribs (except if
@attribs is already set, the assignment is skipped; that’s what ||=
does), and sets the value of @attribs[a] to val. val is the block
parameter, and will therefore also be a method parameter for the new
method.

David

On 10/25/07, Chris C. [email protected] wrote:

guide kind of skipped over it:
object, right?)
Wrong!

the block argument to instance_eval is evaluated right away, in the
context of metaclass.

The context is missing but I’m assuming that this is inside a class
definition, and metaclass has been defined as a method to return the
singleton class. So lets say we had:

class Foo
metaclass #=> the singleton class of Foo.
metaclass.instance_eval do
# Inside this block self is bound to the singleton class of
foo
define_method(:bar) {“bar”}
end
end

This has the same effect as
class Foo
def self.bar
“bar”
end
end

I.e. it defines a class method of Foo.

Now I’d usually use class_eval, or module_eval which do the same thing
but only work if the receiver is a class/module.

However, I don’t understand the next part. define_method is a method
call to the method that is defined further down, right? But what is the
" do |val|" after the method call, and what is the point of the two
lines after that?

David A. Black has already answered this.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Hi –

On Fri, 26 Oct 2007, Robert D. wrote:

Notwithstanding the answers you got already and they are very worthy
indeed I would like to add my grain of salt.
I like to see metaprogramming a little bit as program generation

A.module_eval {

s/{/do/ :slight_smile:

define_method :b do |n| 42*n end
end

is just the same as the following of course
class A
def b n
42*n
end
end

It’s very key, though, that define_method takes a block, since that
gives the method body access to local variables.

David

On 10/25/07, Chris C. [email protected] wrote:

guide kind of skipped over it:
object, right?)


Posted via http://www.ruby-forum.com/.

Notwithstanding the answers you got already and they are very worthy
indeed I would like to add my grain of salt.
I like to see metaprogramming a little bit as program generation

A.module_eval {
define_method :b do |n| 42*n end
end

is just the same as the following of course
class A
def b n
42*n
end
end
I therefore imagine that define_method does write code, the following
is of course
only a metaphor

class Module
def define_method name, &blk
eval “def #{name}( #{blk.params} )
#{blk.body}
end”
end
end

HTH
Robert

Rick Denatale wrote:

class Foo
metaclass #=> the singleton class of Foo.
metaclass.instance_eval do
# Inside this block self is bound to the singleton class of
foo
define_method(:bar) {“bar”}
end
end

This has the same effect as
class Foo
def self.bar
“bar”
end
end

I.e. it defines a class method of Foo.

Now I’d usually use class_eval, or module_eval which do the same thing
but only work if the receiver is a class/module.

Hold on, now I’m confused. Given this…

class Creature

def singleton
class << self
self
end
end

end

c = Creature.new
cs = c.singleton

cs.instance_eval do
define_method(:eat) do
puts “You eat.”
end
end

cs.eat # => errors:
NoMethodError: undefined method `eat’ for
#<Class:#Creature:0xb7f78ae8>

Why is that? I’ve tried class_eval, too and I get the same thing. I
thought I understood this stuff pretty well but I guess not.

Daniel W. wrote:

Daniel W. wrote:

cs.instance_eval do
define_method(:eat) do
puts “You eat.”
end
end

cs.eat # => errors:
NoMethodError: undefined method `eat’ for
#<Class:#Creature:0xb7f78ae8>

Why is that? I’ve tried class_eval, too and I get the same thing. I
thought I understood this stuff pretty well but I guess not.

This works:

cs.instance_eval do
def eat
puts “You eat.”
end
end

cs.eat # You eat.

What’s up with define_method?

The method is put into the Creature class and is inaccessible in the
simpleton class.

Try this:

class Creature

def singleton
class << self
self
end
end

end

c = Creature.new
cs = c.singleton

cs.instance_eval do
define_method(:eat) do
puts “You eat.”
end
end

c.eat

Daniel W. wrote:

cs.instance_eval do
define_method(:eat) do
puts “You eat.”
end
end

cs.eat # => errors:
NoMethodError: undefined method `eat’ for
#<Class:#Creature:0xb7f78ae8>

Why is that? I’ve tried class_eval, too and I get the same thing. I
thought I understood this stuff pretty well but I guess not.

This works:

cs.instance_eval do
def eat
puts “You eat.”
end
end

cs.eat # You eat.

What’s up with define_method?

Daniel W. wrote:

That’s the same code as above. Still doesn’t work though. =)

Nope, it’s c.eat instead of cs.eat… works for me! :slight_smile:

Chris C. wrote:

The method is put into the Creature class and is inaccessible in the
simpleton class.

Try this:

class Creature

def singleton
class << self
self
end
end

end

c = Creature.new
cs = c.singleton

cs.instance_eval do
define_method(:eat) do
puts “You eat.”
end
end

c.eat

That’s the same code as above. Still doesn’t work though. =)

Chris C. wrote:

Daniel W. wrote:

That’s the same code as above. Still doesn’t work though. =)

Nope, it’s c.eat instead of cs.eat… works for me! :slight_smile:

Hehe, I feel sheepish. (Yes, works for me, too.)

So… what I gather then is that it’s possible to, from the viewpoint of
the singleton, duck back into the object that spawned it. Why you would
do that I don’t know, but it can be done.

It looks like the only way to define a method on a singleton object is
to use…

cs.instance_eval do
def eat
puts “You eat.”
end
end

I find that restrictive because you can’t dynamically create a method
name.

It looks likes you can extend it with modules:

module Bounce

def bounce
puts “You bounce.”
end

end

cs.extend Bounce
cs.bounce # You bounce.

What is special about the singleton object such that it cannot be
extended using define_method?

On 10/25/07, Chris C. [email protected] wrote:

Daniel W. wrote:

That’s the same code as above. Still doesn’t work though. =)

Nope, it’s c.eat instead of cs.eat… works for me! :slight_smile:

Right.

This is a slightly different case that I had described.

In my example

class Foo
metaclass.instance_eval {define_method(:bar) {“bar”}}
end

It’s Foo’s singleton/metaclass which get the define_method call.

Whereas

this new case is equivalent to

c = Creature.new
c.singleton.instance_eval {define_method(:eat) {puts “You eat”}}

So in my case, the class Foo ends up with a new method, i.e. a class
method since Foo is a class,
but c ends up with a new singleton (instance) method.

And if you did

c.class.instance_eval{define_method(:walk) {puts “You walk”}}

An instance method would be created which would be invocable for all
instances of Creature, because this is the same as

Creature.instance)_eval …


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

On 10/25/07, Daniel W. [email protected] wrote:

It looks like the only way to define a method on a singleton object is
to use…

cs.instance_eval do
def eat
puts “You eat.”
end
end

No, your code did exactly that.

c.eat worked, but did you try

Creature.new.eat

In other words, only the object referenced by c got the method.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

user = DefaultUser.new
# Give them a random email address - TODO this should be changed so 

that it first clears out
# any user that might exist with that email address first to ensure
a proper creation. Oh well.
user.email(“john.doe#{rand(100)}@abc.com”)

results in an error.

Why can’t I use the attribute accessor? Rather - I understand why I
can’t, because it was created in the User class rather than the
DefaultUser class, but how can I get access to it?

On 10/25/07, Daniel W. [email protected] wrote:

do that I don’t know, but it can be done.
I find that restrictive because you can’t dynamically create a method
name.
a = “Hello World”
size = 42
class << a
define_method :a do size end
end
class << a; self end.send :define_method, :b do size end

a.extend Module::new{
define_method :c do size end
}
module M
def d; size end
end
a.extend M
puts a.a
puts a.b
puts a.c
puts a.d

HTH
Robert

On 10/25/07, Chris C. [email protected] wrote:

Why can’t I use the attribute accessor? Rather - I understand why I
can’t, because it was created in the User class rather than the
DefaultUser class, but how can I get access to it?

Posted via http://www.ruby-forum.com/.

class User
@users = {}
class << self
attr_reader :users
end
def initialize name
email = nil
loop do
email = “#{name}@#{rand 1000}”
break unless self.class.users[ email ]
end
self.class.users[ email ] = self

is this what you want?
R.

Robert D. wrote:

On 10/25/07, Chris C. [email protected] wrote:

Why can’t I use the attribute accessor? Rather - I understand why I
can’t, because it was created in the User class rather than the
DefaultUser class, but how can I get access to it?

Posted via http://www.ruby-forum.com/.

class User
@users = {}
class << self
attr_reader :users
end
def initialize name
email = nil
loop do
email = “#{name}@#{rand 1000}”
break unless self.class.users[ email ]
end
self.class.users[ email ] = self

is this what you want?
R.

I don’t really want to define it in the User class, because it might not
belong to a particular user… maybe I am not making sense.

I am writing a testing application for many web sites that require user
registrations. Each website requires similar users, but different ones
might have additional fields required of a user, so my idea is to create
a metaclass of user for each user configuration a website might require.
So, for a web site based in the UK, the metaclass looks like this:

class UKUser < User
attribs :address3, :i_own_or_work_for_a_small,
:my_small_business_has_its, :yes_please_send_me_the_mi, :postal
first_name( “John” )
last_name( “Doe” )
email( “john.doe#{rand(100)}@abc.com” )
address1( “1 Sunshine Parkway” )
address2( “” )
address3( “” )
city( “London” )
postal( “EH15 2JJ” )
gender( “male” )
phone( “(555)123-4567” )
age( “18-25” )
my_small_business_has_its( “no” )
i_own_or_work_for_a_small( “no” )
yes_please_send_me_the_mi( “no” )
end

So I am able to say

user = UKUser.new
user.address3(“Apartment 12”)
user.my_small_business_has_its(“yes”)

However, I cannot do this:

user.email(“[email protected]”)

Any ideas?

Drew O. wrote:

Chris C. wrote:

Any ideas?

Basically the same idea as why’s solution to dwenthy’s array:

Someone else responded with:
“You added a singleton method email and an accessor pair email/email= to
the User class. The email method with no arguments called on User
instances is the reader, you need to use the email= method.”

which is correct - the method user.email("") only works from the
standpoint of the singleton, but you CAN call user.email = “bla” from
outside the singleton context.

Chris C. wrote:

Any ideas?

Basically the same idea as why’s solution to dwenthy’s array:

class User
def self.metaclass
class << self; self; end
end

def self.attribs *args
return @attribs if args.empty?
attr_accessor *args
args.each do |arg|
metaclass.instance_eval do
define_method(arg) do |val|
@attribs ||= {}
@attribs[arg] = val
end
end
end

class_eval do
  define_method(:initialize) do
    self.class.attribs.each do |attrib,val|
      instance_variable_set("@#{attrib}",val)
    end
  end
end

end
end

class UKUser < User
attribs :name, :age, :weight
name “john doe”
age 25
weight 170
end

u = UKUser.new
puts u.name
puts u.weight
puts u.not_real

Chris C. wrote:

Drew O. wrote:

Chris C. wrote:

Any ideas?

Basically the same idea as why’s solution to dwenthy’s array:

Someone else responded with:
“You added a singleton method email and an accessor pair email/email= to
the User class. The email method with no arguments called on User
instances is the reader, you need to use the email= method.”

which is correct - the method user.email("") only works from the
standpoint of the singleton, but you CAN call user.email = “bla” from
outside the singleton context.

Not really sure what you’re asking here, but I can tell you what my code
does. It adds methods to the metaclass associated with each symbol
passed to the attribs method. These essentially act like class methods
for any class the inherits from User. Then, I add a instance method
#initialize that is called when new objects are created and sets the
instance variables for that object to the values given in the class
methods. Make sense?

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs