Dev environment `NameError: uninitialized constant` due to `const_get` use and eager_load behavior
Summary (TL;DR)
The code below:
module Gitlab
module Prometheus
end
end
module Prometheus
end
Object.const_get("Gitlab::Prometheus")
should not return the Prometheus
constant. It should return the Gitlab::Prometheus
.
This affects only development
and test
environments. production
is not affected because it eager_load
all constants.
Object.const_get
and eager_load
.
Confusing behavior due to combination of This was a very difficult "bug" to track down. Here's the gist.
There is a line in app/models/concerns/prometheus_adapter.rb
which looks like:
data = Kernel.const_get(query_class_name).new(prometheus_client_wrapper).query(*args)
This was reliably producing NameError: uninitialized constant Prometheus::Queries
when it was run in my GDK environment (where query_class_name = "Gitlab::Prometheus::Queries::KnativeInvocationQuery"
), but it was not doing this for anybody else.
After a lot of frustration, it turns out that const_get
was attempting to look for Prometheus::Queries
and the reason is a bit complicated. The way const_get
works is equivalent to the following:
Object.const_get("Gitlab::Prometheus::Queries::KnativeInvocationQuery")
# behaves exactly like:
Object.const_get("Gitlab").const_get("Prometheus").const_get("Queries").const_get("KnativeInvocationQuery")
Once the first const_get resolves, we have:
Gitlab.const_get("Prometheus").const_get("Queries") # ...
In my system, because eager_load
is not enabled in development there is no Gitlab::Prometheus
constant yet. Normally, the auto-loader would handle this gracefully and look for lib/gitlab/prometheus/*.rb
which would define Gitlab::Prometheus
. However, it just so happens that we have a constant Prometheus
(no namespace) which was automatically loaded from the prometheus-client-mmap
gem and this causes the above const_get
to resolve to:
Prometheus.const_get("Queries") # ...
Thus, with eager_load
disabled, our auto-loading broke.
Having eager_load
disabled on my system but enabled for others is the reason I was the only one seeing this issue. It turns out it came down to the setting prometheus_metrics_enabled
, which on my system was false. Apparently on newer GDK installations this now defaults to true. This is a pretty arbitrary and unintuitive rule by which to change such a fundamental behavior of the dev environment.
Main takeaways:
-
We should be extremely careful about our use of
const_get
to lookup any classes/modules which will rely on rails auto-loading. It will break unexpectedly when child classes match the names of classes in the root namespace. (See this blog post and this informative stack overflow answer for some more info)We should prefer
some_class_string.constantize
instead ofObject.const_get(some_class_string)
because this bypasses the undesired behavior. Perhaps we can lint for this in rubocop? -
We should re-evaluate the way we're utilizing eager_load in development. I think it should be always on or always off, and not based on some seemingly unrelated global settings. This caused my dev environment to behave completely differently to my colleagues due to the fact that my GDK was set up before gitlab-ce#43660 (11 months ago) and theirs was set up afterword.