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
The implementation of MessageBus.subscribe appears to be here: https://github.com/SamSaffron/message_bus/blob/master/lib/message_bus.rb#L217
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
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!
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With