3

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!

skaffman
  • 398,947
  • 96
  • 818
  • 769
Jaime Bellmyer
  • 23,051
  • 7
  • 53
  • 50
  • FYI, in ree, I get "global constant" and "wrong constant". – apneadiving Feb 03 '11 at 23:28
  • apneadiving - that's because you're using Ruby 1.8, where constant lookups work differently. In 1.8, the constants are looked up from the calling scope (where the block is first defined). Ruby 1.9 halfway solves this problem by using instance_eval's receiver's constants - but so far, that only works *outside* of another class or module. – Jaime Bellmyer Feb 03 '11 at 23:37
  • Okay, I'm learning a lot, I'll shift to 1.9 to test a bit. – apneadiving Feb 03 '11 at 23:38

1 Answers1

3

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.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • Holy cow, that is awesome! I have to run out for a bit, but I will be singing your praises publicly tomorrow :) Thank you very, very much - if the library interface isn't simple to use, it just invites errors and endless debugging. I'm very grateful. – Jaime Bellmyer Feb 04 '11 at 00:07
  • haha, saw your edit. I wondered about the binding, but didn't know how sourcify works at first. looks like it takes a block of code, and "to_source" makes it "native" again, as if it's appearing inline. Brilliant. – Jaime Bellmyer Feb 04 '11 at 00:10
  • Glad I could help, I was pointed towards the sourcify gem when looking for a way to compare the literal content, not the output, of procs. The MRI Ruby team is supposed to add a `to_source` for procs (they keep putting it off) so hopefully you won't even need the sourcify gem soon. Oh, and sing the praises of Ng Tze Yang, the creator of sourcify, not me. ;) –  Feb 04 '11 at 00:19