Reputation: 373
I'm working on a old code Rails 3, Ruby 1.9.3 code base. I references a gem I can't upgrade for various reasons. The gem has already been monkey patched (correct term?) using class_eval. I need to make a change to another method that looks something like:
SomeNamespace::Bar do
def some_method
@@part_of_header ||= JSON.dump({... Stuff ...})
# ... other code ...
headers = {
"Header Part" => @@part_of_header
#... other headers ...
}
end
end
The idea behind the @@part_of_header class variable is to cache the JSON dump so it can be reused. The @@part_of_header is NOT defined elsewhere in the Bar base class.
My monkey patched method looks like:
SomeNamespace::Bar.class_eval do
def some_method
@@part_of_header ||= JSON.dump({... Stuff ...})
# ... other code that I changed ...
headers = {
"Header Part" => @@part_of_header
#... other headers that I changed ...
}
end
end
The code works fine but I get the following warning on the lines with the @@part_of_header class variable:
Class variable access from toplevel
I tried moving the class variable to it's own method:
SomeNamespace::Bar.class_eval do
def header_part
@@part_of_header ||= JSON.dump({... Stuff ...})
end
def some_method
# ... other code that I changed ...
headers = {
"Header Part" => header_part
#... other headers that I changed ...
}
end
end
However the "toplevel" error just moved to the header_part method.
I also tried accessing the class variable using class_variable_set and class_variable_get but got undefined method errors.
Any advice on how to fix this warning? If it can't be fixed any advice on caching the JSON dump in the class_eval? Thanks.
Update: Thanks to @Josh for using the full class name with the class_variable_get/set. My final solution looks like:
SomeNamespace::Bar.class_eval do
def header_part
# Create the class variable if it does not exist, remember
# the base class does not define @@part_of_header.
if !SomeNamesapce::Bar.class_variable_defined?(:@@part_of_header)
SomeNamespace::Bar.class_variable_set(:@@part_of_header, nil)
end
if (SomeNamespace::Bar.class_variable_get(:@@part_of_header).nil?
header_part = JSON.dump({... Stuff ...})
SomeNamespace::Bar.class_variable_set(:@@part_of_header, header_part)
end
SomeNamespace::Bar.class_variable_get(:@@part_of_header)
end
def some_method
# ... other code that I changed ...
headers = {
"Header Part" => header_part
#... other headers that I changed ...
}
end
end
The above works but any feedback on the above solution would be appreciated. Thanks.
Upvotes: 1
Views: 463
Reputation: 58342
It looks like the problem is that, although the class_eval
do
block is executed within the context of SomeNamespace::Bar
, that context doesn't apply to references to class variables.
If you explicitly access the class variable, then things should work as expected:
# NOTE: Omitting conditional set (||=) for simplicity
SomeNamespace::Bar::class_variable_set(:@@part_of_header, JSON.dump({... Stuff ...}))
headers = {
"Header Part" => SomeNamespace::Bar::class_variable_get(:@@part_of_header)
#... other headers that I changed ...
}
If @@part_of_header
is really only used within some_method
, and if you're totally replacing some_method
, then there's also nothing wrong with you using your own module variable / class variable, instead of reusing the existing SomeNamespace::Bar::@@part_of_header
. I might prefer that approach; it feels like it better encapsulates your changes.
module MonkeyPatch
SomeNamespace::Bar.class_eval do
def some_method
# This is within the MonkeyPatch module, so it
# makes a new class variable for MonkeyPatch
@@part_of_header ||= "JSON.dump({'a': 12})"
# ... other code that I changed ...
headers = {
"Header Part" => @@part_of_header
#... other headers that I changed ...
}
puts headers
end
end
end
Upvotes: 1