Search code examples
ruby-on-railsrubyblockdiscourse

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


Solution

  • 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