Jonathan Paisley
jp-ww****@dcs*****
Sun Mar 12 07:24:39 JST 2006
Hi, People get caught out sometimes by the dynamic function-style constants on the OSX module (such as OSX.NSKeyValueChangeKindKey). Below is a sample implementation using const_missing that makes these available as normal constants (OSX::NS...). Please let me know what you think. tc_constants.rb =============== require 'test/unit' require 'osx/cocoa' ### This is the implementation (would go in one of the main ruby support files) class Module old_const_missing = instance_method(:const_missing) define_method(:const_missing) do |c| # See if there's a function-style constant on OSX. if c.to_s =~ /^NS/ and OSX.respond_to?(c) then begin return OSX.const_set(c,OSX.send(c)) rescue ArgumentError # Silently ignore real functions that need an argument # and fall through end end # Let the original implementation take care of creating # the exception. begin old_const_missing.bind(self).call(c) rescue NameError => e # Catch the exception and trim the backtrace to exclude # this hook. e.backtrace.slice!(0,3) raise e end end end ### Tests below module TestConstModule def self.const_missing(c) :module_success end end class TestConstClass def self.const_missing(c) :class_success end end class TC_Constants < Test::Unit::TestCase include OSX # Check that const lookup directly in OSX module # or via the include statement above works. def test_normal_constant assert_equal 10, OSX::NSKeyDown assert_equal 10, NSKeyDown end # Check that a function constant can be accessed directly def test_function_constant assert_equal "NSGlobalDomain", OSX.NSGlobalDomain.to_s end # Check that a function constant works when accessed as a real # constant using ::. Then check that it has now become # a real constant on OSX. def test_redirected_function_constant assert !OSX.const_defined?(:NSGlobalDomain) assert_equal "NSGlobalDomain", OSX::NSGlobalDomain.to_s assert OSX.const_defined?(:NSGlobalDomain) # This gets found in OSX directly since the previous # lookup defined it assert_equal "NSGlobalDomain", NSGlobalDomain.to_s end # Similar to above but without explicit reference to OSX module. def test_indirect_function_constant assert !OSX.const_defined?(:NSFileTypeRegular) assert !Object.const_defined?(:NSFileTypeRegular) assert_equal "NSFileTypeRegular", NSFileTypeRegular.to_s assert !Object.const_defined?(:NSFileTypeRegular) assert OSX.const_defined?(:NSFileTypeRegular) end def test_real_function assert_raises NameError do OSX::NSClassFromString end end # Check that a normal bad constant fails properly def test_nonexistent_constant assert_raises NameError do OSX::NonexistentConstant end assert_raises NameError do NonexistentConstant end end # Ensure that const_missing still works within a different module def test_module_const_missing assert_equal :module_success, TestConstModule::Foo end # Ensure that const_missing still works within a different class def test_module_const_missing assert_equal :class_success, TestConstClass::Foo end # Check that const_missing backtraces aren't affected def test_backtraces begin NonexistentConstant rescue NameError => e end assert_not_nil e assert_match /test_backtraces/, e.backtrace[0] end end