简体   繁体   中英

Ruby methods called at top of classes

I've seen this a lot in Ruby (started using Ruby for the first time a couple years ago):

class MyAwesomeClass

  another_method

  def initialize; end 
end

And I can't get my head around what type of method another_method is...

Looking at examples, it's sometimes declared as a class method, but usually you call class methods in your code like this:

AnotherClass.another_method

And most importantly you call your methods regardless if they are from another class, module, or inherited class inside another method of your class you use them. For example:

module SomeModule
  def some_method; end
end

class AnotherClass
  private

  def another_method; end
end

class YetAnotherClass
  self.yet_another_method; end
end

class MyAwesomeClass < AnotherClass
  include SomeModule

  def initialize
    some_method # method from my included module
    another_method # method inherited from AnotherClass
    YetAnotherClass.yet_another_method # class method called directly
  end

end

So what are these methods that can be called at the top of a class and NOT called inside of another method as my examples above?

Are you able to just call class methods inside another class (that it inherits from) outside of your method calls?

I can't get my head around what type of method another_method is...

There is only one type of method in Ruby: every method in Ruby is an instance method of some module. There is no other type of method.

We may sometimes talk about "class methods" or "singleton methods", but that is just a convenient way of talking about a particular way to use methods. The methods themselves are no different.

Every method in Ruby is an instance method of some module. The question is simply: which module?

We are sometimes talking about class methods, but class methods don't really exist. Class methods are simply singleton methods of an object that happens to be an instance of the Class class.

There is absolutely no difference between

Foo = ''
def Foo.bar; end

and

class Foo; end
def Foo.bar; end

and

module Foo; end
def Foo.bar; end

We call the first one a "singleton method", the second one a "class method", and the third one a "module function", but there is actually no difference between the three. The only difference is that the first object is an instance of the String class, the second object is an instance of the Class class, and the third object is an instance of the Module class, that's it.

Actually, I must confess something: I lied. I wrote that class methods are simply singleton methods of an object that happens to be an instance of the Class class, but in reality … singleton methods don't exist either.

A singleton method is simply a boring old normal instance method of the singleton class .

There is no difference between

foo = ''
def foo.bar; end

and

foo = ''
class << foo
  def bar; end
end

There you have it: singleton methods are actually just instance methods of the singleton class, class methods are actually just instance methods of the singleton class of an object that is an instance of the Class class, module functions are actually just instance methods of the singleton class of an object that is an instance of the Module class.

It's just that "instance method of the singleton class of an object that is an instance of the Class class" is annoying to say and write all the time, so we say "class method" instead, knowing full well that there is no such thing .

Looking at examples, it's sometimes declared as a class method, but usually you call class methods in your code like this:

AnotherClass.another_method

Again, there is no such thing as a class method. There is exactly one kind of method, and they are always called the same way:

receiver.method(args)

If the receiver is self , then you can leave it out, and if you have no arguments, you can leave them out, too.

And most importantly you call your methods regardless if they are from another class, module, or inherited class inside another method of your class you use them.

That is not true.

In fact, in your own example , you are calling two methods outside of a method body: Module#private and Module#include , and you seem to have no trouble with those. Other methods that I am sure you have already called outside of a method body are Module#attr_accessor , Kernel#require , or Kernel#puts . In fact, in simple scripts, there is often not a single method definition body at all.

So what are these methods that can be called at the top of a class and NOT called inside of another method as my examples above?

They are instance methods just like any other instance methods, there is absolutely nothing special about them.

Are you able to just call class methods inside another class (that it inherits from) outside of your method calls?

Since class methods don't exist, and these are simply instance methods just like every other instance method, the method lookup algorithm is of course also just the method lookup algorithm for instance methods:

  1. Start with the private internal hidden __klass__ pointer of the receiver. If you can't find the method there …
  2. Get the private internal hidden __superklass__ pointer and repeat.

And that's it. (Yes, okay, there is a tiny bit more to it: if you come to a point where there is no __superklass__ pointer, then you try again with method_missing and the name of the method as an argument, and if you also cannot find that , then you raise a NoMethodError .)

So, let's try that in your example:

class MyAwesomeClass
  another_method
end

Okay, first off: what is the receiver? Well, if no receiver is given, then the receiver is always implicitly self . Now, what is self here?

A ha, That is actually the most important question in Ruby. always. At any point in Ruby, you must always know what self is.

Well, we know that inside of a method body, self is the receiver of the message send. So, we can guess: what would make the most sense for self inside of a class definition. The class itself, of course: Let's test that:

what_is_self = class MyAwesomeClass
  self
end

p what_is_self
# MyAwesomeClass

Well, looks like we're right. Okay, we know the receiver of the message send is MyAwesomeClass , so what is the first step in our algorithm? We get the private internal hidden __klass__ pointer.

We cannot actually do that in Ruby (it is a private internal hidden implementation detail of the Ruby implementation). But let me assure you that is pretty much always going to be the singleton class . (There are some objects which cannot have singleton classes, and some Ruby implementations optimize the creation of singleton classes so that they are only created when needed, but for all intents and purposes, we can assume that the __klass__ pointer is always the singleton class.)

We look inside the singleton class, and we find no definition of the method another_method . So, we move to the second step of the algorithm: we get the __superklass__ of the singleton class.

The __superklass__ of the singleton class is usually going to be the class of the object. So, in this case, the object is MyAwesomeClass , which is a class, so the class of MyAwesomeClass and the __superklass__ of the singleton class is going to be Class .

Again, we look inside Class and we don't find another_method . Ergo, we get Class 's __superklass__ , which is Module . We also don't find another_method here, so we move on to Object , which also doesn't have another_method .

Now, it gets interesting again, because Object 's __superklass__ pointer is actually Kernel and not BasicObject as you might have thought. (More precisely, it is an include class which shares its method table with the Kernel module, since Kernel is not a class at all and thus cannot be a superclass.)

Kernel also doesn't have a method named another_method , so we get Kernel 's __superklass__ (again, technically we are talking about an include class that proxies Kernel and not Kernel itself), which is BasicObject . Finally, BasicObject does not have a __superklass__ .

Which means we start aaaaaaaaaaaall the way back from step #1 again, but this time as-if you had written

class MyAwesomeClass
  method_missing(:another_method)
end

We do the whole song-and-dance again, and we also never find a method until we get to the very top, to BasicObject , which has a method_missing that roughly looks like this:

class BasicObject
  def method_missing(meth, *args)
    raise NoMethodError, "undefined method `#{meth}' for #{inspect}", meth, args, receiver: self
  end
end

So, if you want your call to another_method to not fail, it needs to be defined anywhere along the whole chain we just walked up:

  • In MyAwesomeClass 's singleton class
  • In a module that MyAwesomeClass extends
  • In Class
  • In a module that Class includes
  • Or in a module included by that module
  • In Module
  • In a module that Module includes
  • Or in a module included by that module
  • In Object
  • In Kernel
  • Or another module that Object includes
  • Or in a module that Kernel includes
  • In BasicObject
  • In a module that BasicObject includes
  • Or in a module included by that module
  • Or maybe the Ruby implementation you are using has an implementation-specific superclass of BasicObject (eg MacRuby has Objective-C's NSObject as the superclass of BasicObject )

To illustrate difference between different type of methods, consider this example:

class Foo
  puts 'method called outside of method'

  def initialize
    puts 'initialize called'
  end

  def instanse_method
    puts 'instance method called'
  end

  def self.clazz_method
    puts 'class method called'
  end
end

Foo
foo = Foo.new
foo.instanse_method
Foo.clazz_method

What will be the output?

method called outside of method
initialize called
instance method called
class method called

So what are these methods that can be called at the top of a class and NOT called inside of another method as my examples above?

As you can see, any method can be called before initialize method. It's executed when class is loaded.

Just calling

Foo

would be sufficient enough to output:

method called outside of method

(but notice that it was not called multiple times)

Looking at examples, it's sometimes declared as a class method, but usually you call class methods in your code like this: AnotherClass.another_method

It's like static function in PHP. In my example, it's Foo#clazz_method .

And most importantly you call your methods regardless if they are from another class, module, or inherited class inside another method of your class you use them

Usually, you have to include a file that define another class before using its method. But most frameworks like Ruby on Rails, have already built-in autoloading system, so it looks like it's working magically without explicit require or include . But there are times when RoR does not know where your custom classes are. In this case, you have to require the file explicitly before you use it. Well, usually in the beginning of the file.

It'll be easier to understand by looking at it in action.

# Step 1
# Run this in IRB
class MyClass
  def self.another_method
    puts "Executed when class is loaded to memory"
  end
end


# Step 2
# Now when you run the following in IRB after that, its going to execute the
#   class method which was defined in its parent class (MyClass)
class MyAwesomeClass < MyClass
  another_method # Executed ONCE when the class is loaded to memory for the first
  def initialize; end 
end


# Now if you instantiate MyAwesomeClass though, it will not print the same as of
#   Step 2 as class method :another_method already has been executed when class
#   was loaded
awesome1 = MyAwesomeClass.new

The body of a class will be interpreted and executed sequentially & behaves much like you'd expect it inside an instance method. Try putting a puts "Hello World" in the body of your class definition like so:

class MyClass
  # Executes when the class is defined in IRB(Loaded to memory)
  puts "Hello World"
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM