问题
The Short and Sweet
Running this code in Ruby 1.9:
FOO = "global constant"
class Something
FOO = "success!"
def self.handle &block
self.new.instance_eval &block
end
end
class Other
FOO = "wrong constant"
def self.handle
Something.handle{FOO}
end
end
puts Something.handle{FOO}
puts Other.handle
I get "success!" and "wrong constant". How can I get both calls to print "success!"? This is not a contrived exercise for fun - I wouldn't waste people's time for that. I have a real-world problem, and I've whittled it down to the simplest possible example that demonstrates the issue. Read on for the "why".
A More Thorough Explanation
Calling Something.handle{FOO} works correctly. Ruby uses the definition of FOO given in the Something class. However, If I try to call it the same way from another class, it gives me the definition of FOO for that class.
I thought the idea of tighter constant looksups in Ruby 1.9 was to avoid problems like this. instance_eval is supposed to use the scope of its receiver (self.new, in this case), not the scope of the calling block. It works for things like instance variables, but not for constants. This is not a problem of precedence - remove the "global" and "wrong" constants, and ruby still won't be able to find the remaining correct constant.
The real-world problem I'm having is that I have a module with several classes. I have a method that accepts a block, and runs the block in the context of the module. Inside that block, I want to be able to refer to those classes by their short names (like I can anywhere in the module itself), rather than having to prepend the module name.
This is painful:
ThirdPartyApis::MyAnswerSite.connection question.id, answer.id do |question_id, answer_id|
question = ThirdPartyApis::MyAnswerSite::Question.find question_id
answer = ThirdPartyApis::MyAnswerSite::Answer.find answer_id
ThirdPartyApis::MyAnswerSite::Solution.new question, answer
end
This is pleasant:
ThirdPartyApis::MyAnswerSite.connection question.id, answer.id do |question_id, answer_id|
question = Question.find question_id
answer = Answer.find answer_id
Solution.new question, answer
end
Summary
So that's the long-winded explanation. Please don't offer workarounds that don't address my question. I appreciate the desire to explore other avenues, but my question is simple: can ONLY the Something class by modified, in a way that prints "success!" twice at the end? That's the test case, and any solution that fits this is my accepted answer, and its author is my personal hero for the week. Please!
回答1:
I got it to work using the sourcify gem (gem install sourcify
):
require 'sourcify'
FOO = "global constant"
class Something
FOO = "success!"
def self.handle &block
(self.new.instance_eval(block.to_source)).call
end
end
class Other
FOO = "wrong constant"
def self.handle
Something.handle{FOO}
end
end
puts Something.handle{FOO}
=> success!
puts Other.handle
=> success!
edit: Oops, you could see where I was working on using bindings too, removed that code.
来源:https://stackoverflow.com/questions/4892726/constant-lookup-with-instance-eval-in-ruby-1-9