Ruby 1.9 const_defined?(“Timeout”) returns true when Timeout not in the list of constants

StackOverflow https://stackoverflow.com/questions/5395587

  •  28-10-2019
  •  | 
  •  

Question

I'm trying to upgrade Puppet to use Ruby 1.9 and running into trouble with constants. const_defined?("Timeout") is returning true even though :Timeout isn't in the list of constants. This doesn't happen on Ruby 1.8.7. Any ideas why?

[128, 137] in /Users/matthewrobinson/work/puppet/lib/puppet/util/classgen.rb
   128    def handleclassconst(klass, name, options)   
   129      const = genconst_string(name, options)
   130
   131      require 'ruby-debug'; 
   132      debugger if const == "Timeout"=> 
   133      if const_defined?(const)
   134        if options[:overwrite]   
   135          Puppet.info "Redefining #{name} in #{self}"
   136          remove_const(const)   
   137        else
(rdb:1) const
=> "Timeout"
(rdb:1) const_defined?(const)
=> true
(rdb:1) constants.grep /Timeout/
=> []
(rdb:1) constants
=> [:Ensure, :ParameterName, :Auth_type, :Allow_root, :Authenticate_user, :Auth_class, :Comment, :Group, :K_of_n, :Mechanisms, :Rule, :Session_owner, :Shared, :MetaParamNoop, :MetaParamSchedule, :MetaParamAudit, :MetaParamCheck, :MetaParamLoglevel, :MetaParamAlias, :MetaParamTag, :RelationshipMetaparam, :MetaParamRequire, :MetaParamSubscribe, :MetaParamBefore, :MetaParamNotify, :MetaParamStage, :Component, :Macauthorization, :Expirer, :ClassMethods, :InstanceMethods, :ExecutionStub, :POSIX, :Errors, :MethodHelper, :ClassGen, :Docs, :Execution, :Tagging, :Log, :Logging, :Package, :Warnings, :Cacher, :Autoload, :LoadedFile, :Settings, :Feature, :SUIDManager, :RunMode, :CommandLine, :InstanceLoader, :Pson, :Metric, :LogPaths, :ProviderFeatures, :InlineDocs, :FileLocking, :Storage, :Checksums]
(rdb:1) constants.grep /Path/
=> [:LogPaths]
(rdb:1) self
=> Puppet::Type::Macauthorization
Was it helpful?

Solution

The behavior of const_defined? in Ruby 1.9 can be made to be the same as in Ruby 1.8 by setting the new inherit parameter to false.

mod.const_defined?(sym, inherit=true)

Here's a example to illustrate the different behavior.

module Foo
  def self.bar
    puts "The constant I got was #{const_get("Timeout")}"
    if const_defined?("Timeout")
      puts "I found #{Timeout}!"
      remove_const("Timeout")
      puts "Timeout is now #{Timeout}"
    end
  end
end

class Timeout
end

puts Foo.bar

Under Ruby 1.9.2 the output from running this is:

The constant I got was Timeout
I found Timeout!
19_test.rb:6:in `remove_const': constant Foo::Timeout not defined (NameError)
        from 19_test.rb:6:in `bar'
        from 19_test.rb:13:in `<main>'

So even though const_defined? recognizes that Timeout is defined at top scope as a class, remove_const is only allowed to remove constants in Foo's scope.

In Ruby 1.8.7 the output is:

The constant I got was Timeout
nil

So const_get looks at ancestor scopes just like in Ruby 1.9.2, but const_defined? does not, which prevents remove_const from getting called.

Ruby 1.9.2 can be made to behave like 1.8.7 like so:

module Foo
  def self.bar
    puts "The constant I got was #{const_get("Timeout")}"
    if const_defined?("Timeout", false)
      puts "I found #{Timeout}!"
      remove_const("Timeout")
      puts "Timeout is now #{Timeout}"
    end
  end
end

class Timeout
end

puts Foo.bar

However, this is now not backwards compatible with Ruby 1.8 since const_defined? doesn't have a second parameter in 1.8. To get around this I made the following method that can be called instead of const_defined? and used in either version of Ruby.

def is_constant_defined?(const)
  if ::RUBY_VERSION =~ /1.9/
    const_defined?(const, false)
  else
    const_defined?(const)
  end
end

This solved this particular Ruby 1.9 upgrade issue. It may not be the best long term solution, and the real issue is that there's a class called Timeout at topscope AND sometimes a constant called Timeout in other classes that needs to be checked for, but this change gets the code much closer to running on Ruby 1.9.

OTHER TIPS

I can't say for sure what's going on.

However, the RDoc for const_defined? and constants is different in 1.8.7, whereas it's fairly similar in 1.9.

In 1.8.7, const_defined? says:

Returns true if a constant with the given name is defined by mod.

and constants says

Returns an array of the names of the constants accessible in mod. This includes the names of constants in any included modules (example at start of section).

However, in 1.9, const_defined? says

Returns true if a constant with the given name is defined by mod, or its ancestors if inherit is not false. [by default, inherit is true]

and constants says

Returns an array of the names of the constants accessible in mod. This includes the names of constants in any included modules (example at start of section), unless the all parameter is set to false. [by default, all is true]

So it seems like the behaviour of the two methods is consistent in 1.9, but not consistent in 1.8.7. But I could be wrong.

That being said, I'd suggest the following:

  • Create a toy example of using const_defined? and constants, preferably not involving Timeout, and play around with it until you are confident you understand what the two methods do, under both 1.8 and 1.9.
  • Work out where the Timeout constant belongs to. Also check whether IRB or the debugger may cause Timeout to become defined when it previously was undefined, and whether it gets loaded by default by one version of Ruby but not the other.

I also came across http://redmine.ruby-lang.org/issues/1915 when googling for const_defined? 1.8 1.9. I'm not sure if it's relevant or not.

I hope this helps - I'm not sure though!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top