开发者

How do I get module mixins to work for static methods?

开发者 https://www.devze.com 2023-01-24 01:28 出处:网络
Lets say I have two modules.Is it possible to include a module inside another one that would behave like a mixin?

Lets say I have two modules. Is it possible to include a module inside another one that would behave like a mixin?

For example:

module A
  def self.foo
    puts "foo"
    bar
  end
end

module B
  in开发者_运维知识库clude A
  def self.bar
    puts "bar"
  end
end

B.bar
B.foo

Edit: I realized I originally copied the code down wrong. The methods need to be static. The corrected code is above(and does not work).


As you've learned it doesn't work but why it doesn't work is a really good lesson about the Ruby object model.

When you create an instance of an object what you have created is a new object with a set of instance variables and a pointer to the class of the object (and a few other things like an object ID and a pointer to the superclass) but the methods themselves are not in the instance of the object. The class definition contains the list of methods and their code (and a pointer to its own class, a pointer to its superclass, and an object ID).

When you call a method on an instance Ruby looks up the class of the instance and looks in that class's method list for the method you called. If it doesn't find it then it looks in the class' superclass. If it doesn't find it there it looks in that class' superclass until it runs out of superclasses. Then it goes back to the first class and looks for a method_missing method. If it doesn't find one it goes to the superclass and so on till it gets to the root object where it's designed to raise an error.

Let's say for instance you have a class Person and you make an instance of the class with the variable bubba like this:

class Person
  attr_accessor :dob, :name
  def age
    years = Time.now.year - @dob.year
    puts "You are #{years} year#{"s" if years != 1} old"
  end
  def feed
    puts "nom, nom, nom"
  end
end
bubba = Person.new
bubba.name = "Bubba"
bubba.dob = Time.new(1983,9,26)

The class diagram would look something like this:

How do I get module mixins to work for static methods?

So what's happening when you create a static method, a class/module method? Well, remember that almost everything is an object in Ruby and a module definition is an instance of the class Class. Yep, that code you type out is actually an instance too, it's live code. When you create a class method by using def self.method_name you are creating a method in the instance of the object that is the class/module definition.

Great, so where's that class method being defined at you ask? It's being defined in an anonymous class (aka singleton, eigen, ghost class) that is created for exactly this reason.

Going back to our Person class what if we add a class method on the instance bubba like so:

def bubba.drive_pickup
  puts "Yee-haw!"
end

That method gets put into a special singleton class created just for that instance and the singleton's superclass is now the Person class. This makes our method calling chain look like this:

How do I get module mixins to work for static methods?

Any other methods defined on the instance object bubba will also be put into that singleton class. There's never more than one singleton class per instance object.

So, to wrap it all up the reason why it doesn't work is the static methods in the modules are being defined in the singleton class for the instance of the module definition. When you include or extend from the module you are adding a pointer to the method table of the module but not the method table of the instance object of the singleton class for the module.

Think of it this way: If you create an instance x of type Z and an instance y of type Z should x know about y? No, not unless specifically told about it. So too your module that mixes in another module should not know about some other object that just happens to have that first module as its superclass.

For a much better explanation of the Ruby object model watch this awesome free video by the amazingly erudite Dave Thomas (no, not the guy from Wendy's):
http://scotland-on-rails.s3.amazonaws.com/2A04_DaveThomas-SOR.mp4

After watching that video I bought Dave Thomas's whole series on the Ruby object model from Pragmatic and it was well worth it.

P.S. Anyone please feel free to correct me on anything I forgot; like what's specifically in an object.


Use extend instead of include to add class methods.

module A
  module ClassMethods
    def foo
      puts "foo"
      puts bar
    end    
  end
  extend ClassMethods    
end

module B
  extend A::ClassMethods
  def self.bar
    puts "bar"
  end
end

B.bar
B.foo


The exact code you posted works exactly like you want to. So, the answer is Yes.

Would it have been really that hard to just execute it yourself?

0

精彩评论

暂无评论...
验证码 换一张
取 消