在这部分代码中设置klass = self的动机是什么

时间:2014-01-10 21:02:22

标签: ruby-on-rails ruby block discourse

我在话语中查看了一些代码并偶然发现了这一点,并想知道为什么klass = self。据我所知,他们比我更好的红宝石开发者,必须有充分的理由。

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

THX

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 个答案:

答案 0 :(得分: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>

WUT ???

Ruby的instance_eval可以使用一个块并使用当前对象self来运行它:这样,相同的代码块改变了它的含义。


因此,我的假设是MessageBus正在做一些等价的东西:因此,我们不能从块中传递self,因为它会在instance_evaled时改变它的含义


EDIT !!!

我查看了消息总线的实现,并没有充分的理由说明我们应该klass = self

查看here,我们将块保存在内部数据结构中:

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

现在让我们看一下what ensure_subscriber_thread does

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

所以它只是调用块,根本没有instance_evalinstance_exec


我的新假设

话语是一个带有lof的Javascript的应用程序;在Javascript中这是一个非常常见的模式:

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

所以我猜它只是泄漏到ruby中,注意它没有做错任何事,它只是没用! :d

答案 1 :(得分:2)

我不知道Discourse的代码库是否足够肯定,但我的猜测是MessageBus.subscribe使用instance_exec传递给它的块来启用块内的某种DSL 。如果是这种情况,则self将指向包含该块内DSL方法的对象。

klass设置为块外部并在块内部使用该块确保remove_from_cache!实际上在订阅块外部引用的同一self上调用。

答案 2 :(得分:2)

是的,很可能是传递给MessageBus.subscribe的阻塞在不同的上下文中被评估,其中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'

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

答案 3 :(得分:2)

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

使用git blame遗憾地不再产生(它是原始提交的一部分)。

粗略地查看源代码不会产生instance_eval/exec次调用,因此该块甚至会将消息作为参数传递给好的度量。

代码可以在没有klass = self部分的情况下工作。

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

确定btw的唯一方法是实际编辑或供应gem,并在块中添加p self调用或其他内容,并查看它输出的内容。如果下面有隐藏的instance_eval电话,那么您将看到谁是自我。