【问题标题】:How do I check if a class is defined?如何检查一个类是否已定义?
【发布时间】:2011-04-22 18:05:04
【问题描述】:

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

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

Object.const_get("Amber")

或(在 Rails 中)

"Amber".constantize

但是如果 Amber 还不是一个类,那么其中任何一个都将失败并显示 NameError: uninitialized constant Amber

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

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

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

【问题讨论】:

  • 示例中的 defined? 正是在做它应该做的事情:它检查是否定义了 String 对象上的 constantize 方法。它不关心字符串是否包含“Object”或“AClassNameThatCouldNotPossiblyExist”。

标签: ruby ruby-on-rails-3 class defined


【解决方案1】:

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

【讨论】:

  • 完美——谢谢。至于自动加载器,IIRC 有一种方法可以找出自动加载器列表中的内容。如果结果有问题,我会挖掘它。
  • 这也匹配非类的东西。
【解决方案2】:

在 Rails 中这真的很容易:

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

【讨论】:

  • rescue 很有帮助,因为有时可以卸载常量并且使用const_defined? 进行检查将是错误的。
  • 不推荐抑制异常,在这里阅读更多:github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
  • @AndrewK:在 Ruby 中救援经常被使用——我同意它不好;在 Elixir 世界中,如果不需要这样做,我们会尽量不这样做,但我看到很多人在 Ruby 中使用救援
  • @Eiji,同意。我只是想提一下,因为刚接触 Ruby 的人不知道它是一种反模式,应该避免。
  • 如果你在 "Amber" 类中有错误,这个救援会吞下它,你会花很多时间调试它。
【解决方案3】:

受上述@ctcherry 回复的启发,这里有一个“安全类方法发送”,其中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

只有当class_name 响应时才调用method 的更安全的版本:

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

【讨论】:

  • p.s.:如果你喜欢这个回复,请给 ctcherry 的回复投票,因为这为我指明了正确的方向。
【解决方案4】:

使用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

【讨论】:

    【解决方案5】:

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

    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
    

    【讨论】:

      【解决方案6】:

      另一种方法,以防您也想上课。如果未定义类,将返回 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
      

      【讨论】:

        猜你喜欢
        • 2011-07-06
        • 1970-01-01
        • 1970-01-01
        • 2021-12-21
        • 1970-01-01
        • 1970-01-01
        • 2011-04-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多