Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what is motivation for setting klass = self in this section of code

I was looking through some code in discourse and stumbled across this and was wondering why klass = self. As I know they are better ruby developers than I, there must be a good reason.

Why wouldn't they call self.remove_from_cache!(message["key"], false)? Is the block creating a new scope where self refers to the class of MessageBus? Are there other examples of where you would need to create this type of construct in Ruby or is this the main one? If MessageBus.subscribe was an instance of a MessageBus (say m_bus.subscribe) would self refer to m_bus in the block? Does the fact that ensure_class_listener is class method have any bearing on this? Sorry for all the questions but just want to be sure.

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

EDIT #1

The implementation of MessageBus.subscribe appears to be here: https://github.com/SamSaffron/message_bus/blob/master/lib/message_bus.rb#L217

like image 483
timpone Avatar asked Jan 10 '14 21:01

timpone


2 Answers

First of all:

Is the block creating a new scope where self refers to the class of MessageBus?

Nope.

If MessageBus.subscribe was an instance of a MessageBus (say m_bus.subscribe) would self refer to m_bus in the block?

Nope.

Does the fact that ensure_class_listener is class method have any bearing on this?

Nope.


Let's start with a simple example:

def test_self
  p self

  2.times do |n|
    p self
  end
end

test_self

prints out

main
main
main

As you can see self refers to the same object, the top level main object.

Now, to the interesting part:

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

p self
Foo.new.test_self do
  p self
end

gives

main
main

Not too surprising, we are passing a block along and calling from within our object. But if we try with this:

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

p self

Foo.new.test_self do
  p self
end

gives

main
#<Foo:0x007f908a97c698>

WUT???

Ruby's instance_eval can take a block and run it using the current object as self: in this way the same block of code changed its meaning.


Therefore, my assumption is that MessageBus is doing something equivalent: for this reason, we can't pass self from within the block because it will change its meaning when being instance_evaled


EDIT!!!

I've looked at the message bus implementation and there isn't a good reason why we should do klass = self.

Look here, we take the block and save it in an internal data structure:

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

Now let's look at what ensure_subscriber_thread does:

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

So it just calls the block, no instance_eval or instance_exec at all!


My new Hypothesis

Discourse is an application with a lof of Javascript; it is a very common pattern in Javascript to do this:

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

So I guess it just leaked into ruby aswell, note that it doesn't do anything wrong, it's just useless! :D

like image 148
Ju Liu Avatar answered Sep 22 '22 22:09

Ju Liu


I don't know Discourse's code base well enough to say for sure, but my guess would be that MessageBus.subscribe uses instance_exec with the block passed to it to enable some sort of DSL within the block. If that's the case, then self would point to the object containing the DSL's methods within that block.

Setting klass to self outside the block and using that inside the block ensures that remove_from_cache! is actually getting called on the same self that's referred to outside the subscribe block.

like image 23
Ajedi32 Avatar answered Sep 21 '22 22:09

Ajedi32