项目里有个地方要动态的查找某个 string 是不是一个 class
我发现 defined?的方法似乎要直接传类名才可以。
比如:defined?(String)
的结果是'constant',正确。
但是 defined?('String'.constantize)
结果是‘method'。不解。irb 里 String == 'String'.constantize
的结果是 true。但同时我也发现String === 'String'.constantize
是 false 的。
或者用 Module.const_get 这个方法去判断一个类是否存在,如果想判断诸如‘A::B::C'的在 module 下面的类的话,const_get 会抛 name error 的异常。
目前就简单的 hack 出一个方法来帮助判断:
def present?(name)
begin
eval name
true
rescue
false
end
end
但觉得这种做法很烂。请问大家是如何处理这种情况的?
这样可行吗?
def present?(name)
name.split('::').inject(Object) {
|o, x| o = o.const_get(x) if x.capitalize==x && o.const_defined?(x)
}
end
benchmark
user system total real eval: 3.580000 0.000000 3.580000 ( 3.585330) const_get: 1.830000 0.000000 1.830000 ( 1.824264)
看半天还是没明白楼主要说什么。
Ruby 下面有叫做 constantize 的方法或变量吗?
irb 和正常的代码,执行结果当然可能不一样。irb 额外加载很多库的。
不好意思,没看懂 lz 想表达什么,看到 @hhuai 的回复。我想到了 ActiveResource
里查找类的一个方法供参考:
ActiveResource::Base#find_resource_in_modules
根据资源名称和模块名称查找类。从模块的最内层到最外层来查找。 ActiveResource::Base#find_or_create_resource_for
调用了这个方法。
源码见 https://github.com/rails/rails/blob/2-3-stable/activeresource/lib/active_resource/base.rb
# Tries to find a resource in a non empty list of nested modules
# Raises a NameError if it was not found in any of the given nested modules
def find_resource_in_modules(resource_name, module_names)
receiver = Object
namespaces = module_names[0, module_names.size-1].map do |module_name|
receiver = receiver.const_get(module_name)
end
if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(resource_name) }
return namespace.const_get(resource_name)
else
raise NameError
end
end
# Tries to find a resource for a given name; if it fails, then the resource is created
def find_or_create_resource_for(name)
resource_name = name.to_s.camelize
ancestors = self.class.name.split("::")
if ancestors.size > 1
find_resource_in_modules(resource_name, ancestors)
else
self.class.const_get(resource_name)
end
rescue NameError
if self.class.const_defined?(resource_name)
resource = self.class.const_get(resource_name)
else
resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
end
resource.prefix = self.class.prefix
resource.site = self.class.site
resource
end
@anklos 我觉得你的这个函数是最简单的实现,已经够用了,如果觉得 eval 不好看,换成这样试试:
# 不要叫 present?,会跟 Object#present? 重名
class String
def has_defined_class?
begin
!!self.classify.constantize
rescue NameError
false
end
end
end
'not_exist_class'.has_defined_class? # => false
'integer'.has_defined_class? # => true
先谢谢各位回答。 @hhuai 这个顶层 module 名也会显示是一个类。比如“M1::M2::class", if present?("M1") 也会通过。 其他我没发现什么问题。但是放到项目里处理时还是会出错。暂时没仔细 debug @zhangyuan 这个是正宗的。哈哈 @iwinux 不应将抛异常来作为一个判断条件。
def present?(name)
name.split('::').inject(Object) {
|o, x| o = o.const_get(x) if x.capitalize==x && o.const_defined?(x)
}.is_a? Class
end
eval 性能很差的,我这个性能好很多。
Class.const_defined?("User")
不过要先'运行'一下类
要么在启动 server 的时候
Dir.foreach("RAILS_APP_PATH/app/modules").each{|v| next if v =~ /^\./; require v}
@hhuai 今天调试了下,发现 bug 在哪了。 x.capitalize 只转换首字母,遇到'MethodA'这种格式的类名就不对了。当然也很好改,换成 camelize 就好了
刚刚翻 Rails 3.2 的 Release Notes,发现 Module 有一个新的实例方法 #qualified_const_defined?,看看是不是你想要的?