Module to overwrite method defined via define_method

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

···

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

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: gist:186219 · GitHub

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

···

On Mon, 14 Sep 2009, Gaspard Bucher wrote:

--
David A. Black, Director
Ruby Power and Light, LLC (http://www.rubypal.com)
Ruby/Rails training, consulting, mentoring, code review
Book: The Well-Grounded Rubyist (http://www.manning.com/black2\)

Gaspard Bucher wrote:

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]

···

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

David A. Black wrote:

Hi --

Example ruby code: gist:186219 · GitHub

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

···

On Mon, 14 Sep 2009, Gaspard Bucher wrote:

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

7stud -- wrote:

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

···

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

Hi --

David A. Black wrote:

Hi --

Example ruby code: gist:186219 · GitHub

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 ?

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

···

On Mon, 14 Sep 2009, Gaspard Bucher wrote:

On Mon, 14 Sep 2009, Gaspard Bucher wrote:

--
David A. Black, Director
Ruby Power and Light, LLC (http://www.rubypal.com)
Ruby/Rails training, consulting, mentoring, code review
Book: The Well-Grounded Rubyist (http://www.manning.com/black2\)

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.

···

On 9/13/09, Gaspard Bucher <gaspard@teti.ch> wrote:

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 Bucher wrote:

Any other solution ?

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

class A
  def sayhi
    puts "hi"
  end

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

  include Redef
end

class Wrapper
  include Redef

  def initialize(a_obj)
    @a = a_obj
  end

  def method_missing(meth_name, *args)
    @a.send(meth_name, *args)
  end

end

a = A.new
w = Wrapper.new(a)

w.sayhi #hi
w.foo #'foo' from module
w.non_existent

r1test.rb:26:in `send': undefined method `non_existent' for #<A:0x24928>
(NoMethodError)
        from r1test.rb:26:in `method_missing'
        from r1test.rb:35

···

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

Gaspard Bucher wrote:

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.

···

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

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?

···

On Sunday 13 September 2009 11:18:52 am Gaspard Bucher wrote:

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 ?

David,

Thanks for the highlights and the advice , I will consider keeping the
'def xxx' syntax.

Gaspard

David A. Black wrote:

···

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

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

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

···

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

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

David Masover wrote:

···

On Sunday 13 September 2009 11:18:52 am Gaspard Bucher wrote:

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?

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

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 ?

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 :slight_smile:

   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

···

On Tue, 15 Sep 2009, David Masover wrote:

On Sunday 13 September 2009 11:18:52 am Gaspard Bucher wrote:

--
David A. Black, Director
Ruby Power and Light, LLC (http://www.rubypal.com)
Ruby/Rails training, consulting, mentoring, code review
Book: The Well-Grounded Rubyist (http://www.manning.com/black2\)

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

···

On Mon, 14 Sep 2009, 7stud -- wrote:

--
David A. Black, Director
Ruby Power and Light, LLC (http://www.rubypal.com)
Ruby/Rails training, consulting, mentoring, code review
Book: The Well-Grounded Rubyist (http://www.manning.com/black2\)

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-variable-reversion-and-a-mystery-explained\.

···

On Sun, Sep 13, 2009 at 2:44 PM, David A. Black <dblack@rubypal.com> wrote:

Hi --

On Mon, 14 Sep 2009, 7stud -- wrote:

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.

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

Thanks.

···

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