module to overwrite method defined via define_method

G

Gaspard Bucher

Hi List !

I found a tricky thing: if you use define_method and then include a
module that contains a method with the same name, the method in the
module is just ignored.

Why is that so ?

How can I overwrite a method defined through 'define_method' ?

Example ruby code: http://bit.ly/2NjP5w

Gaspard
 
D

David A. Black

Hi --

Hi List !

I found a tricky thing: if you use define_method and then include a
module that contains a method with the same name, the method in the
module is just ignored.

Why is that so ?

How can I overwrite a method defined through 'define_method' ?

Example ruby code: http://bit.ly/2NjP5w

It's a matter of the order of method lookup. In general, an object
looks for a method first in its class, and then in modules mixed into
that class.

module M
def x; puts "x in M"; end
end

class A
def x; puts "x in A"; end
include M
end

A.new.x # x in A

It's the same if the method is defined with #define_method.

Yehuda Katz has recently proposed that there be a way to insert a
module in the lookup order before the class. I'm not sure where that
proposal stands at the moment.


David
 
G

Gaspard Bucher

David said:
Hi --


It's a matter of the order of method lookup. In general, an object
looks for a method first in its class, and then in modules mixed into
that class.

Damned ! This means that all the dynamic methods created by rails with
"has_one" and such cannot be overwritten by including a module except if
you write:

module A
def self.included(base)
base.send:)define_method, 'foo') do
puts "'foo' from A"
end
end
end

Any other solution ?

Gaspard
 
D

David A. Black

Hi --

Damned ! This means that all the dynamic methods created by rails with
"has_one" and such cannot be overwritten by including a module except if
you write:

module A
def self.included(base)
base.send:)define_method, 'foo') do
puts "'foo' from A"
end
end
end

Any other solution ?

I would consider rewriting that as:

module A
def self.included(base)
base.class_eval do
def foo
puts "'foo' from A"
end
end
end
end

so as to normalize it back to the "def" form.

Keep in mind, too, that this is a bit fragile because the order
matters. If you include the module first and then do has_many, (or
attr_accessor, or any other instance-method generator), the has_many
will "win".


David
 
C

Caleb Clausen

Damned ! This means that all the dynamic methods created by rails with
"has_one" and such cannot be overwritten by including a module except if
you write:

module A
def self.included(base)
base.send:)define_method, 'foo') do
puts "'foo' from A"
end
end
end

Any other solution ?

If you use extend instead of include, the methods in the module should
be seen first. Extend operates on instances, not classes, tho, so
you'll have to call extend in your initialize.

Alternately, if you make a subclass and then use include in the
subclass, I believe that will also utilize the module version. Not
sure how that might interoperate with rails.
 
7

7stud --

Gaspard said:
Hi List !

I found a tricky thing: if you use define_method and then include a
module that contains a method with the same name, the method in the
module is just ignored.

Why is that so ?

By the way, you can get the lookup order using the ancestors method:

module Redef
def foo
puts "'foo' from module"
end
end

class A
define_method("foo") do
puts "'foo' from define_method"
end

include Redef
end


p A.ancestors

--output:--
[A, Redef, Object, Kernel]


Although, ancestors() doesn't include the singleton classes:

module Redef
def foo
puts "'foo' from module"
end
end

class A
def foo
puts "foo from class A"
end

def initialize
class << self
include Redef
end
end

end

a = A.new
a.foo
p A.ancestors


--output:--
[A, Object, Kernel]
 
G

Gaspard Bucher

7stud said:
class A
def foo
puts "foo from class A"
end

def initialize
class << self
include Redef
end
end

end

Thanks for the note 7stud. I think you can also write the above example
as:

class A
def foo
puts "foo from A"
end

def initialize
extend Redef
end
end
 
7

7stud --

Can anyone explain this one:

module Redef
def foo
puts "'foo' from module"
end
end

class A
define_method("foo") do
puts "'foo' from define_method"
end

def initialize
class << self
include Redef
end
end

#include Redef

end

p A.ancestors
a = A.new
a.foo

--output:--
[A, Object, Kernel]
'foo' from module

As expected, the version of foo in a's singleton class is found before
the version of foo in class A in the method lookup before.

But what about here:

module Redef
def foo
puts "'foo' from module"
end
end

class A
define_method("foo") do
puts "'foo' from define_method"
end

def initialize
class << self
include Redef
end
end

include Redef #<---*****CHANGE HERE

end

p A.ancestors
a = A.new
a.foo


I would expect the order of the look up for the foo method to be:

a's singleton class ==> 'foo from module'
a's class(=A) ==> 'foo from define method'
a's mixins(=Redef) ==> 'foo from module'


But the output is:

[A, Redef, Object, Kernel]
'foo' from define_method
 
7

7stud --

Gaspard said:
Thanks for the note 7stud. I think you can also write the above example
as:

class A
def foo
puts "foo from A"
end

def initialize
extend Redef
end
end

Yes. I thought it might be clearer the other way.
 
D

David A. Black

Hi --

But what about here:

module Redef
def foo
puts "'foo' from module"
end
end

class A
define_method("foo") do
puts "'foo' from define_method"
end

def initialize
class << self
include Redef
end
end

include Redef #<---*****CHANGE HERE

end

p A.ancestors
a = A.new
a.foo


I would expect the order of the look up for the foo method to be:

a's singleton class ==> 'foo from module'
a's class(=A) ==> 'foo from define method'
a's mixins(=Redef) ==> 'foo from module'


But the output is:

[A, Redef, Object, Kernel]
'foo' from define_method

I believe what's happening is this: when you include Redef in a's
singleton class, Ruby sees that it's already in the ancestors (since
it's been included in A) and doesn't add it.

In this example, I've got one module that's included in A, and one
isn't, and I extend my instance with both. As you can see, the Dummy
module appears before A in the ancestor list for a's singleton class,
whereas Redef doesn't.

module Redef
def foo
puts "'foo' from module"
end
end

module Dummy
end

class A
def foo
puts "'foo' from A"
end

def initialize
extend(Redef)
extend(Dummy)
end

include Redef

end

a = A.new
a.foo
p A.ancestors
class << a; p ancestors; end

Output:

'foo' from A
[A, Redef, Object, Kernel]
[Dummy, A, Redef, Object, Kernel]


David
 
R

Rick DeNatale

Hi --

But what about here:

module Redef
=A0def foo
=A0 puts "'foo' from module"
=A0end
end

class A
=A0define_method("foo") do
=A0 puts "'foo' from define_method"
=A0end

=A0def initialize
=A0 class << self
=A0 =A0 include Redef
=A0 end
=A0end

=A0include Redef =A0 #<---*****CHANGE HERE

end

p A.ancestors
a =3D A.new
a.foo


I would expect the order of the look up for the foo method to be:

a's singleton class =3D=3D> 'foo from module'
a's class(=3DA) =3D=3D> 'foo from define method'
a's mixins(=3DRedef) =3D=3D> 'foo from module'


But the output is:

[A, Redef, Object, Kernel]
'foo' from define_method

I believe what's happening is this: when you include Redef in a's
singleton class, Ruby sees that it's already in the ancestors (since
it's been included in A) and doesn't add it.

Yep.

Ruby will only include a module once in the implementation chain, it
turns out it's because of the way the super keyword is implemented.

I talked to Matz about why this was at RubyConf in 2007 and wrote
about the conversation

http://talklikeaduck.denhaven2.com/2007/11/03/a-chat-with-matz-classs-varia=
ble-reversion-and-a-mystery-explained.


--=20
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale
 
D

David Masover

Damned ! This means that all the dynamic methods created by rails with
"has_one" and such cannot be overwritten by including a module except if
you write:

module A
def self.included(base)
base.send:)define_method, 'foo') do
puts "'foo' from A"
end
end
end

Any other solution ?

As others said, drop the 'define_method'. I'd take it a step further:

module A
module ClassMethods
def foo
puts "'foo' from A"
end
end
def self.included(base)
base.send :extend, ClassMethods
end
end

This is a common idiom. It's not so much load order as the fact that class
methods don't automatically get included by "include" -- but they are also
instance methods on the class, if that makes sense.

The advantage of doing it this way is that you can put a lot more stuff in
ClassMethods, or even mix in other modules (via "include") inside
ClassMethods. It's also nice and self-documenting, and it's used all over the
place -- I believe inside Rails, at least.

I would even go so far as to call this a best practice. Thoughts?
 
G

Gaspard Bucher

I think your example does not correspond to our problem: with extend you
are adding class methods to the included class and our problem was
overwriting *instance* methods. In fact to avoid the "define_method"
inside the class priority thing this could be a better option by using
an anonymous Module:

class Base
def self.has_one(thing)
m = Module.new do
define_method(thing) do
puts "Has one #{thing}"
end
end
include m
end
end

module Redef
def dog
puts "Super says:"
super
puts "But I say: it has nothing because I decide so."
end
end

class Dummy < Base
has_one :dog
include Redef
end

Dummy.new.dog
 
D

David A. Black

Hi --

As others said, drop the 'define_method'. I'd take it a step further:

module A
module ClassMethods
def foo
puts "'foo' from A"
end
end
def self.included(base)
base.send :extend, ClassMethods

That's the long way round :)

base.extend(ClassMethods)
I would even go so far as to call this a best practice. Thoughts?

The name "ClassMethods" is potentially a bit confusing, since they're
not exactly class methods... but I think it's an effective way to do
what it does. I don't think it's inherently a better practice than,
say, extending a class explicitly in the class -- meaning, I wouldn't
go out of my way to set it up this way if it didn't fall into place
fairly naturally in a give case.


David
 

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,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top