为什么在这里我可以像调用实例方法一样调用类方法?

时间:2019-12-05 16:29:32

标签: sql ruby metaprogramming

我正在查看此示例:

class SQLObject
  def self.columns
    return @columns if @columns
    columns = DBConnection.execute2(<<-SQL).first
      SELECT
        "#{table_name}".*
      FROM
        "#{table_name}"
      LIMIT
        0
    SQL
    columns.map!(&:to_sym)
    @columns = columns
  end

  def self.table_name
    @table_name ||= self.name.underscore.pluralize
  end

  def insert
    column_symbols = self.class.columns.drop(1)
    column_names = column_symbols.map(&:to_s).join(", ")
    question_marks = (['?'] * column_symbols.count).join(", ")
    DBConnection.execute(<<-SQL, *attribute_values)
      INSERT INTO
        #{self.class.table_name} (#{column_names})
      VALUES
        (#{question_marks})
    SQL
    self.id = DBConnection.last_insert_row_id
  end
end

我很困惑为什么可以在“ self.columns”方法中调用table_name方法,就像它是实例方法一样。 “ table_name”方法不是类方法吗?因此,在“ self.columns”方法中也不应将其称为“ self.class.table_name”吗?

2 个答案:

答案 0 :(得分:1)

在抽象类中,self引用正确的类,而不是对象。这就是为什么您无需显式告知self

即可访问该方法的原因

答案 1 :(得分:0)

简短的回答:self是Ruby中的隐式接收者。如果您将其保留,则邮件将发送到self。换句话说,在消息发送中不必说self.foo,因为foo总是 与{{1} }。

长答案:Ruby中没有“ 类方法”之类的东西。实际上,所谓的“ 类方法”不过是在对象上定义的 singleton方法而已,而对象恰好是类self.foo的实例。 / p>

实际上,Ruby中也没有 singleton方法 singleton方法实际上只不过是在对象的 singleton类上定义的常规的 instance方法

[注意:这并不是说您不应使用这些术语。说“类方法”显然比“类Class对象的单例类的实例方法”更简单,并且传达意图更好。另外,请注意,存在诸如Object#singleton_methods之类的方法。这些术语显然存在于Ruby Community 中,但重要的是要理解,这些概念 并不存在于Ruby Language 中。它们是一种通讯工具,不是真正的Ruby概念。]

单例类是与单个对象关联的类。每个对象都有一个 one 单例类,并且该对象是其Singleton类的 only 实例(“单例实例”,因此是名称)。

实际上,对象总是的继承层次结构始于其单例类,即对象的 class指针始终指向其单例类,而单例然后,该类的 superclass指针指向创建此对象的类。您只是看不到继承链中的单例类,因为诸如Object#class之类的方法在计算继承链时会“跳过”单例类。但是,它总是在方法查找期间使用。

实际上,方法查找实际上非常简单(唯一的例外是Module#prepend,在此解释中我将忽略它):

  1. 取消引用接收者的类指针
  2. 如果该类的方法表具有消息名称旁边的方法,请调用它。停止。
  3. 如果没有,请取消引用该类的超类指针
  4. GOTO#2。
  5. 如果您到达的类没有超类,请使用消息method_missing(original_message_name, ...)重新启动算法,除非消息名称已经为Class,在这种情况下,请提出一个NoMethodError

所以,这全都归结为两个问题:

  • 定义方法时,您要在哪个类(或模块)中定义
  • 发送消息时,您要将消息发送到哪个对象?

使用method_missing 定义方法时,Ruby将首先对表达式def foo.bar求值,然后在结果对象的单例类上定义方法foo definee )。如果您不提供bar,即您只说foo,则default definee上将定义def bar,这有点棘手。通常,它是最接近的词法包围模块定义。如果没有封闭模块定义,即您在顶层,则默认定义为Object,方法可见性为private

当您使用bar 发送一条消息时,Ruby将首先对表达式foo.bar求值,然后将消息foo发送到结果对象(< em> receiver )。如果您不提供bar,即您只说foo,则默认接收者bar

这带给我们下一个问题:

  • 什么是self

在方法定义主体中,self是导致该方法调用的消息发送的接收方。因此,如果您发送消息self,那么在方法foo.bar的定义中对self的任何引用将在该方法的这一执行期间求值为bar

在模块或类定义主体中,foo是类或方法本身。 (这就是为什么使用self定义单例类的 singleton方法实例方法的原因在模块或类定义主体中起作用。)

在块或lambda文字中,def self.bar在词法上被捕获,即self在写块或lambda文字时的位置。 但是,有两种方法可以更改self的评估方式,最著名的是BasicObject#instance_evalBasicObject#instance_execModule#module_eval,{{3 }},Module#module_execModule#class_eval,…方法族。

如果您始终在代码中的任何时候都知道这三个问题的答案(我在哪里定义方法,什么是方法调用的接收者,什么是self),那么您就有了基本了解Ruby最重要的部分。

因此,将它们放在一起:

在使用self定义方法时,我们位于类定义主体中,因此def self.columns引用类对象本身(self)。

语法SQLObjectdef foo.bar的单例类上定义了方法bar。在这种情况下,您可以在foo的单例类上定义方法columns。这意味着对象SQLObject是Universe中唯一会响应消息SQLObject的对象。

这两项同样适用于columns的定义。

self.table_name的方法体内,self.columns将动态引用接收者。

当您向self发送消息columns时(即您写SQLObject),然后在SQLObject.columns方法的主体内,接收方(即{{1} })设置为SQLObject::columns。 (在这种情况下,接收者将始终为self,因为这是单例类的方法。)

因此,在SQLObject的方法体内,当您编写消息send SQLObject时,这是一条带有隐式接收器的消息send。由于隐式接收者是self.columns,因此等效于table_name。此时的self已绑定到self.table_name,因此等效于self。换句话说,邮件已发送到SQLObject

现在,按照我上面概述的算法,方法查找首先从SQLObject.table_name中检索类指针。如前所述,类指针始终指向单例类。因此,我们查看SQLObject的单例类,以查看是否找到了名为SQLObject的方法,我们做到了!

方法查找完成,一切正常。