如何检查是否定义了类?


74

如何将字符串转换为类名,但前提是该类已经存在?

如果Amber已经是一个类,我可以通过以下方式将字符串从该类中获取:

Object.const_get("Amber")

或(在Rails中)

"Amber".constantize

但是,NameError: uninitialized constant Amber如果Amber还不是一门课,那么这两种方法都将失败。

我的第一个想法是使用该defined?方法,但它不会区分已经存在的类和不存在的类:

>> defined?("Object".constantize)
=> "method"
>> defined?("AClassNameThatCouldNotPossiblyExist".constantize)
=> "method"

因此,在尝试转换字符串之前,如何测试字符串是否为类命名?(好吧,begin/rescue块捕获NameError错误怎么样?太丑陋了吗?我同意...)


2
defined?在该示例中,它确实正在执行应做的事情:它检查是否constantize已定义String对象上的方法。不管字符串是否包含“ Object”或“ AClassNameThatCouldNotPossfullyExist”。
ToniTornado

Answers:


129

怎么const_defined?

请记住,在Rails中,开发模式中有自动加载功能,因此在测试时可能会很棘手:

>> Object.const_defined?('Account')
=> false
>> Account
=> Account(id: integer, username: string, google_api_key: string, created_at: datetime, updated_at: datetime, is_active: boolean, randomize_search_results: boolean, contact_url: string, hide_featured_results: boolean, paginate_search_results: boolean)
>> Object.const_defined?('Account')
=> true

2
很好,谢谢。对于自动加载器,IIRC有一种方法可以找出自动加载器列表中的内容。如果发现有问题,我将进行深入分析。
fearless_fool

4
这也匹配不是类的东西。
mahemoff

20

在rails中,这真的很容易:

amber = "Amber".constantize rescue nil
if amber # nil result in false
    # your code here
end

rescue之所以有用,是因为有时可以卸载常量,并且使用checkconst_defined?将为false。
斯宾塞

1
不建议抑制异常,请在此处阅读更多信息:github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
Andrew K

@AndrewK:在Ruby中,救助确实经常使用-我同意它不好。在Elixir世界中,如果不需要这样做,我们会尝试不这样做,但是我看到很多人在Ruby中使用救援
Eiji

1
@Eiji,同意。我只是想提到它,因为Ruby新手并不知道它是反模式,应该避免使用。
安德鲁K

13

受上述@ctcherry响应的启发,这是一个“安全类方法send”,其中class_name是一个字符串。如果class_name未命名类,则返回nil。

def class_send(class_name, method, *args)
  Object.const_defined?(class_name) ? Object.const_get(class_name).send(method, *args) : nil
end

一个更安全的版本,method仅在class_name响应时才调用:

def class_send(class_name, method, *args)
  return nil unless Object.const_defined?(class_name)
  c = Object.const_get(class_name)
  c.respond_to?(method) ? c.send(method, *args) : nil
end

2
ps:如果您喜欢此回复,请赞成ctcherry的回复,因为这是正确的方向。
fearless_fool

5

看来使用该Object.const_defined?方法的所有答案都是有缺陷的。如果由于延迟加载而尚未加载所讨论的类,则断言将失败。最终实现这一目标的唯一方法是这样的:

  validate :adapter_exists

  def adapter_exists
    # cannot use const_defined because of lazy loading it seems
    Object.const_get("Irs::#{adapter_name}")
  rescue NameError => e
    errors.add(:adapter_name, 'does not have an IrsAdapter')
  end

2

我创建了一个验证器来测试字符串是否为有效的类名(或有效类名的逗号分隔列表):

class ClassValidator < ActiveModel::EachValidator
  def validate_each(record,attribute,value)
    unless value.split(',').map { |s| s.strip.constantize.is_a?(Class) rescue false }.all?
      record.errors.add attribute, 'must be a valid Ruby class name (comma-separated list allowed)'
    end
  end
end

1

另一种方法,以防您也想上课。如果未定义该类,则将返回nil,因此您不必捕获异常。

class String
  def to_class(class_name)
    begin
      class_name = class_name.classify (optional bonus feature if using Rails)
      Object.const_get(class_name)
    rescue
      # swallow as we want to return nil
    end
  end
end

> 'Article'.to_class
class Article

> 'NoSuchThing'.to_class
nil

# use it to check if defined
> puts 'Hello yes this is class' if 'Article'.to_class
Hello yes this is class
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.