6

我正在查看一些讨论中的代码并偶然发现了这一点,并且想知道为什么 klass = self. 我知道他们是比我更好的 ruby​​ 开发人员,这一定是有充分理由的。

他们为什么不调用 self.remove_from_cache!(message["key"], false)?该块是否创建了一个新范围,其中 self 指的是 MessageBus 类?是否还有其他示例说明您需要在 Ruby 中创建这种类型的构造,或者这是主要的构造?如果 MessageBus.subscribe 是 MessageBus 的一个实例(比如 m_bus.subscribe),是否会在块中自己引用 m_bus?ensure_class_listener 是类方法这一事实对此有什么影响吗?抱歉所有问题,但只是想确定一下。

谢谢

https://github.com/discourse/discourse/blob/master/app/models/site_customization.rb#L118

  def self.ensure_cache_listener
    unless @subscribed
      klass = self
      MessageBus.subscribe("/site_customization") do |msg|
        message = msg.data
        # what would self her refer to
        # what would self her refer to
        # would self.remove_from_cache!(message["key"], false) 
        klass.remove_from_cache!(message["key"], false)
      end

      @subscribed = true
    end
  end

编辑#1

MessageBus.subscribe 的实现似乎在这里: https ://github.com/SamSaffron/message_bus/blob/master/lib/message_bus.rb#L217

4

4 回答 4

5

首先:

该块是否创建了一个新范围,其中 self 指的是 MessageBus 类?

没有。

如果 MessageBus.subscribe 是 MessageBus 的一个实例(比如 m_bus.subscribe),是否会在块中自己引用 m_bus?

没有。

ensure_class_listener 是类方法这一事实对此有什么影响吗?

没有。


让我们从一个简单的例子开始:

def test_self
  p self

  2.times do |n|
    p self
  end
end

test_self

打印出来

main
main
main

如您所见, self 指的是同一个对象,即顶级main对象。

现在,到有趣的部分:

class Foo
  def test_self(&block)
    block.call
  end
end

p self
Foo.new.test_self do
  p self
end

main
main

毫不奇怪,我们正在传递一个块并从我们的对象内部调用。但是,如果我们尝试这样做:

class Foo
  def test_self(&block)
    instance_eval(&block)
  end
end

p self

Foo.new.test_self do
  p self
end

main
#<Foo:0x007f908a97c698>

呜呜???

Rubyinstance_eval可以使用当前对象作为self: 获取一个块并运行它:以这种方式,相同的代码块改变了它的含义。


因此,我的假设是 MessageBus 正在做一些等效的事情:因此,我们不能从块内传递 self ,因为它会在 instance_evaled 时改变其含义


编辑!!!

我已经查看了消息总线的实现,我们没有充分的理由应该这样做klass = self

这里,我们取出块并将其保存在内部数据结构中:

def subscribe_impl(channel, site_id, &blk)
  # ...
  @subscriptions[site_id][channel] << blk
  ensure_subscriber_thread
  blk
end

现在让我们看看ensure_subscriber_thread 做了什么:

multi_each(globals,locals, global_globals, local_globals) do |c|
  # ...
  c.call msg
  # ...
end

所以它只是调用块,没有instance_eval或根本没有instance_exec


我的新假设

Discourse 是一个带有大量 Javascript 的应用程序;这是 Javascript 中非常常见的模式:

var self = this;
$("ul.posts").click(function() {
  // here this does refer to the DOM element
  self.doStuff();
})

所以我猜它也只是泄漏到了红宝石中,请注意它没有做错任何事情,它只是没用!:D

于 2014-01-10T21:28:28.077 回答
2

+1 对@JuLiu 的冗长解释的回答,但我怀疑这种特殊情况是不需要定义klass的情况。

遗憾的是,使用 git blame 没有进一步的结果(它是原始提交的一部分)。

粗略查看源代码不会产生任何instance_eval/exec调用,因此该块甚至将消息作为参数传递以进行良好测量。

并且代码将在没有该klass = self部分的情况下工作。

我最好的猜测是这个:代码库的其余部分表明在各种 js 函数中使用相同的模式:var self = this,这是没有人费心去清理的东西。换句话说,代码库中的盲目一致性。

顺便说一句,唯一可以确定的方法是实际编辑或出售 gem 并p self在块内向该订单添加调用或其他内容,然后查看它的输出。如果下面有隐藏的instance_eval呼叫,您将看到谁是自我。

于 2014-01-10T22:18:33.410 回答
2

我不太了解 Discourse 的代码库,无法肯定地说,但我的猜测是MessageBus.subscribe使用instance_exec传递给它的块来启用块内的某种 DSL。如果是这种情况,那么self将指向该块中包含 DSL 方法的对象。

在块外设置klass为 self 并在块内使用它可确保remove_from_cache!实际上self在订阅块外引用的相同处被调用。

于 2014-01-10T21:25:23.220 回答
2

是的,很可能是传递给 MessageBus.subscribe 的阻塞在不同的上下文中被评估,其中 self 不等于外部范围的 self。这可以通过以下方式实现instance_exechttp ://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-instance_exec

它有助于编写干净的 DSL(例如,用于配置对象):

class SiteConfig
  class << self
    def setup(&block)
      instance_exec &block
    end

    def domain(str = nil)
      @domain ||= str
    end
  end
end

SiteConfig.setup do
  domain 'test.com'
end
SiteConfig.domain # => 'test.com'

一个人为的例子,是的,但希望它能说明这一点。

于 2014-01-10T21:28:48.457 回答