如何在Ruby中检查类是否已经存在

72

如何检查 Ruby 中是否已经存在一个类?

我的代码是:

puts "enter the name of the Class to see if it exists"   
nameofclass=gets.chomp  
eval (" #{nameofclass}......  Not sure what to write here")

我在考虑使用:

eval "#{nameofclass}ancestors.     ....."
14个回答

88

您可以使用Module.const_get方法获取字符串引用的常量。它将返回该常量(通常类别由常量引用)。然后,您可以检查该常量是否为类别。

我会按照以下方式进行操作:

def class_exists?(class_name)
  klass = Module.const_get(class_name)
  return klass.is_a?(Class)
rescue NameError
  return false
end

另外,如果可能的话,我总是避免在接受用户输入时使用eval;我怀疑这不会用于任何严肃的应用程序,但了解安全风险也是值得的。


2
对我而言,如果被命名的东西也是一个模块就非常好了。使用 klass.is_a?(Class) || klass.is_a?(Module)。带命名空间的名称可以在这个方法中工作。例如 class_exists?('Delayed::Worker')。非常不错。 - Douglas Lovell

54
也许你可以使用defined?例如:
if defined?(MyClassName) == 'constant' && MyClassName.class == Class  
   puts "its a class" 
end
注意:需要进行类检查,例如:
Hello = 1 
puts defined?(Hello) == 'constant' # returns true

回答原问题:

puts "enter the name of the Class to see if it exists"
nameofclass=gets.chomp
eval("defined?(#{nameofclass}) == 'constant' and #{nameofclass}.class == Class")

这是最好的方法。在gems中有很多情况需要像这样做事情。defined? ::Rails - User128848244
1
defined?(MyClassName) == 'constant' && MyClassName.is_a?(Class) 看起来更直观。 - Jignesh Gohel
为什么 defined? Object.const_get("array".capitalize) 返回 "method",而运行 defined? Array 返回 "constant"? - kamal
最近有一些与此相关的问题,只是一个警告,如果MyClassName不存在,defined?将会抛出一个错误。至少在当前版本的Ruby中是如此。有关更多信息,您可以查看文档:(http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-defined-3F) - srussking
irb(main):003:0> defined? TestingTesting::Testing => nil irb(main):004:0> RUBY_VERSION => "2.6.1" - Sam Saffron
注意,在Rails中当自动加载尚未加载类时,可能会返回nil。 - x'ES

32

如果你想在特定作用域内查找常量,可以通过调用Module#const_defined?("SomeClass")来避免从Module.const_get中解救出NameError异常。

一个常见的作用域是Object,例如:Object.const_defined?("User")

参见:" Module"。


13
只有在类已被加载时,此语句才会返回true。>> Object.const_defined?("MyClass") # false >> MyClass # MyClass >> Object.const_defined?("MyClass") # true这至少在Rails控制台中是这样的,可能因环境而异。 - carpeliam

11

类名是常量。您可以使用defined?方法来查看是否已定义常量。

defined?(String)    # => "constant"
defined?(Undefined) # => nil

如果您感兴趣,您可以阅读更多关于defined?的工作原理。


6
这对于已定义为 'String' 的情况将不起作用,这其实就是问题所在。 - Mia Clarke

10
defined?(DatabaseCleaner) # => nil
require 'database_cleaner'
defined?(DatabaseCleaner) # => constant

8
Kernel.const_defined?("Fixnum") # => true

3
只有在类已加载的情况下才返回 true。 Loading test environment (Rails 4.2.5.1) 2.2.1 :001 > Kernel.const_defined?('ApiProvider') => false 2.2.1 :002 > ApiProvider => ApiProvider 2.2.1 :003 > Kernel.const_defined?('ApiProvider') => true - Jignesh Gohel

6

以下是我有时用来解决这个问题的方法。您可以像下面这样向String类添加以下方法:

class String
    def to_class
        my_const = Kernel.const_get(self)
        my_const.is_a?(Class) ? my_const : nil
    rescue NameError 
        nil
    end

    def is_a_defined_class?
        true if self.to_class
    rescue NameError
        false
    end
end

然后:

'String'.to_class
=> String
'unicorn'.to_class
=> nil
'puppy'.is_a_defined_class?
=> false
'Fixnum'.is_a_defined_class?
=> true

这是一种非常酷的方法,特别适用于处理字符串形式的用户输入(例如Rails)。感谢您提供这个酷炫的想法! - thegravian

6
这里有一种更简明的版本:
def class_exists?(class_name)
  eval("defined?(#{class_name}) && #{class_name}.is_a?(Class)") == true
end

class_name = "Blorp"
class_exists?(class_name)
=> false

class_name = "String"
class_exists?(class_name)
=> true

4

用一句话概括,我会写:

!!Module.const_get(nameofclass) rescue false

只有给定的nameofclass属于已定义的类时,该函数将返回true

这将对模块中的任何常量返回true(可能是全部,我不确定),而不仅仅是类。Bla="some string" ; !!Module.const_get(:Bla) rescue false 返回true。 - nash

2
如果它是一个简单的独立类,例如:

class Foo
end

那么我们可以使用 Object.const_defined?("Foo")。如果您的类在任何模块中,例如。

module Bar
  class Foo
  end
end

那么我们可以使用Module.const_get("Bar::Foo")

请注意,如果Module.const_get('Bar::Foo')找不到类,则会引发异常。而Object.const_defined?('Foo')将返回truefalse


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接