Rails 'forgets' class between initializer and queries

I'm encountering a peculiar issue on Rails.

We've set up a Hook class to handle a small pub/sub mechanism inside our app it goes something like this

class Hook
  @subscriptions = {}

  class << self
    def subscribe(message, &block)
      @subscriptions[message] ||= []
      @subscriptions[message] << block
    end

    def publish(message)
      @subscriptions[message].each(&:call)
    end
  end
end

(It's a tiny bit more fleshed out but you get the idea).

The problem is:

In an initializer we've got Hook.subscribe(:change) { ... } But when the model calls Hook.publish(:change), nothing happens. A quick check shows that until Hook is called again by the model, a defined? Hook returns nil.

The code in the model is

after_commit do
  # byebug
  Hook.publish(:change)
end

Even weirder: In the initializer I set up a global variable to hold the constant $hook = Hook, the constant is indeed here inside the $hook variable in the model but doesn't match the Hook it autoloads.

# inside a debugger in the model

> defined? Hook
=> nil

> $hook
=> Hook

> Hook # will trigger autoloading
=> Hook

> defined? Hook
=> "constant"

> Hook == $hook
=> false

> Hook.instance_variable_get(:@subscriptions)
=> {}

> $hook.instance_variable_get(:@subscriptions)
=> { change: [<Proc:...>] }

> Hook.object_id == $hook.object_id
=> false

If I set it back as the constant Hook = $hook through the debugger everything behaves properly again — the constant is maintained with the proper variables across requests, even.

I'm reaching the end of my wits here. As anybody got any idea what might be going on?

Upvotes: 0

Views: 70

Answers (1)

I finally found a fix, even if I don't fully understand it (I'm still a bit unclear on how Rails handles autoloading I guess — I'll need to investigate deeper when I have the time).

The Hook class was inside a module (OurApp::Hook) which I had omitted for clarity in the example I shared (my mistake, obviously). I noticed that OurApp.constants was losing a few constants other than Hook. I went inside lib/our_app.rb and modified it so:

module OurApp
  autoload :Hook, 'lib/our_app/hook.rb'
  # ...
end

That seems to do the trick, no more issues.

Thanks everybody !

Upvotes: 2

Related Questions