这里的关键是要意识到(几乎)Ruby 中的所有内容都是脚本,(几乎)Ruby 中的所有内容都是消息发送(Java 中的“虚拟方法调用”),以及(几乎)Ruby 中的所有内容是一个对象。
在 Java 中,唯一的可执行代码是在方法定义中。其他一切都只是声明,是编译器创建一些编译器内部数据结构的指令。类声明不是在运行时执行,而是在编译时由编译器解释,然后生成相应的类结构、方法、字段等。
在 Ruby 中并非如此。文件的内容只是一个从上到下执行的脚本。这就是为什么你可以拥有这样的东西:
if OS.windows?
class Foo
end
else
class Bar
end
end
同样,模块或类定义的主体只是一个脚本,它允许您拥有如下内容:
if OS.windows?
def foo
end
else
def bar
end
end
所以,如您所见,实际上几乎所有内容都是脚本,特别是类定义的主体只是一个从上到下执行的脚本,就像任何其他脚本一样。
顺便说一下,由于类定义是在运行时动态执行的,而不是由编译器静态解释的,所以类定义中的超类定义也是如此:实际上,它只是一个任意的 Ruby 表达式,它返回一个Class 的实例对象,它不一定是静态常量类,即你可以这样写:
class Foo < if rand < 0.5 then String else Array end
end
# Yes, I am aware that this makes zero sense. Here is a more sensible example:
class Point < Struct.new(:x, :y)
end
# `Struct` is a class whose `::new` method returns a class
class Search < R '/search'
end
# This is an example from the Camping web microframework:
# `Search` is a controller which is bound to the URI route `/search`
如果您想知道:如果类定义只是一段代码,它会返回任何内容吗?答案是“是的,确实如此!”实际上,Ruby 中的 everything 会返回一些东西,everything 是 Ruby 中的表达式。没有声明。只有表达式。类定义计算为在类定义中计算的最后一个表达式的值。通常,在类定义中求值的最后一个表达式是方法定义(是的,方法定义与任何其他表达式一样,因此也有返回值),方法定义的求值结果为方法名称被定义,表示为Symbol
此外,几乎所有东西都是一个对象:有一个顶级全局对象,它没有名称,但通常称为main(因为这是它的to_s 和inspect 返回的内容)。您在脚本的顶层运行的所有事情都发生在self(Java 中的this)绑定到main 上。并且类也是对象,它们是Class 类的实例。在类定义主体中,self 绑定到正在定义的类。
现在,“几乎所有东西都是消息发送”:通常,每当您在 Ruby 中看到某事正在“完成”时,它就是消息发送的结果:1 + 2 实际上是 1.+(2)、!false实际上是false.!,foo.bar = baz实际上是foo.bar=(baz),ary[idx]实际上是ary.[](idx),ary[idx] = val实际上是ary.[]=(idx, val)等等。
belongs_to 也不例外:它实际上是发送给self 的消息,在类定义的上下文中,它就是正在定义的类。换句话说,它只是调用“类方法”。 (注:Ruby 中并没有真正的类方法,每个方法都是实例方法。正如我们已经建立的,类和任何其他对象一样是对象,所以它们可以像任何其他对象一样拥有实例方法,不需要一个特殊的“类方法”或“静态方法”构造!)
让我们从一个更简单的例子开始:
class Foo
attr_reader :bar
end
同样,attr_reader 是发送给self 的消息,在本例中为Foo。 attr_reader 只生成一个属性读取器(Java 语言中的“getter”)。它看起来有点像这样(如果你还没有完全掌握 Ruby Reflection 也不要担心,无论如何你应该能够跟随):
class Class # in reality, it is defined in `Module`, a superclass of `Class`
def attr_reader(name)
define_method(name) do
instance_variable_get(:"@#{name}")
end
end
end
顺便说一句:def 不是消息发送,它是一个内置关键字。但是有一个相应的方法可以代替,你在上面看到使用:Module#define_method
事实上,Ruby 甚至没有构造函数! new 只是一个“类方法”,看起来很像这样:
class Class
def new(*args, &block)
new_obj = allocate
new_obj.initialize(*args, &block)
return new_obj
end
end
这里有一些微妙之处,例如initialize默认为private,因此实际上我们需要使用反射来规避访问限制,但这就是它的要点。
哦,既然我刚刚提到了private:那也是一种方法,而不是被解释为编译器指令的关键字。
所以,回顾一下,我们的立场是什么:类定义只是一段代码,方法定义只是被执行的表达式。但是由于类定义只是一段代码,其中可以有任意代码,包括消息发送(Java 中的“方法调用”)。 belongs_to 只是向类本身发送消息(方法调用),即方法是在类对象本身的继承链中的某个位置定义的(不要与 instances的继承链混淆> 班级!)
但真正重要的一点是:它只是一个方法调用。没什么可怕的。没什么神奇的。没什么特别的。
现在,这个方法调用了什么?
它是一个流行的 Ruby ORM 库ActiveRecord 的一部分,它在self(即调用出现在其定义中的类)和另一个名称作为参数提供的类(或者更准确地说:由于 ActiveRecord 很大程度上依赖于约定,因此类的名称可以从参数派生)。根据 Ruby 类之间的这种关联,ActiveRecord ORM 会自动推断出支持这些类的数据库表之间的对应关系。
解释它的工作原理相当于写一个关于 ActiveRecord 的完整教程,a)对于 Stack Overflow 的答案来说太过分了,b)其他人已经做得比我好得多。但无论如何我得到的印象是,你并不想知道belongs_to 在内部是如何工作的,而是方法调用如何出现在你受过 Java 训练的头脑中,这只是“死代码”而已不会被执行。这个谜题的解决方案很简单:它不是死代码,它会被执行。