user1182000
user1182000

Reputation: 1635

Ruby Converting a variable into a constant at runtime

I'm trying to create a module that will be included in many different classes. It needs to record the caller's path to the class file so I can reference the path in later code. This code tries to add a method to the calling class, but fails because it just returns the current value of @@x.

# /home/eric/FindMe.rb
class FindMe
  include GladeGUI
end

# /home/eric/GladeGUI.rb
module GladeGUI

 def self.included(obj)
    @@x, = caller[0].partition(":") # this works @@x = "/home/eric/FindMe.rb"
    obj.class_eval do
      def my_class_file_path
        return ????? # I want to return "/home/eric/FindMe.rb"
      end
    end
  end

end

The GladeGUI module will be "included" in many different classes, so I can't just add code to the calling class. I need a way to make @@x compile into a constant value, so the method stored in the class looks like this:

      def my_class_file_path
        return "/home/eric/FindMe.rb"
      end

How do I convert a variable to a constant in code?

Thanks.

Upvotes: 2

Views: 164

Answers (1)

Orion Edwards
Orion Edwards

Reputation: 123612

It seems like you don't actually need it to be a "constant" - you just need some way to make the method return the correct value all the time and not allow other code to come along and change the value (with the current @@x solution, someone can just modify @@x and it will break)

The solution is to store the data in a local variable instead of a class or instance variable, and then access that local variable via a closure.
No other code will have scope to 'see' the local variable and thus it cannot be changed.

But then the problem becomes that when you use def inside a class_eval, the scope of the caller isn't captured, so the code can't see your local variable. You can use define_method instead

Here's an example

# /home/eric/GladeGUI.rb
module GladeGUI

 def self.included(obj)
    caller_file_path = caller[0].split(":").first
    obj.class_eval do
      define_method :my_class_file_path do
        return caller_file_path
      end
    end
  end

end

# /home/eric/FindMe.rb
class FindMe
  include GladeGUI
end

puts FindMe.new.my_class_file_path # prints the correct path

But - what if you want my_class_file_path to be a class method rather than an instance method - use define_singleton_method instead:

module GladeGUI
 def self.included(obj)
    caller_file_path = caller[0].split(":").first
    obj.class_eval do
      define_singleton_method :my_class_file_path do
        return caller_file_path
      end
    end
  end
end

...
puts FindMe.my_class_file_path

Interesting side note: This is how you can fake "private variables" in javascript :-)

Upvotes: 2

Related Questions