检查Ruby中是否存在模块

18

我正在通过从命令行传递的参数动态定义一个模块名称,例如 Required::Module::#{ARGV.first}

有没有办法检查该模块是否存在?并且如果不知道确切的名称如何运行它的方法?


2
有趣。这里没有直接的答案,需要值得注意的计谋。好问题。 - New Alexandria
8个回答

37

使用const_defined?来实现此功能。

Required::Module.const_defined?(:ModuleName)

返回 true 或 false。


1
现在可以这样写: Module.const_defined?(:Name) 它会返回 true 或 false。 - SunTechnique

14
defined?(Required::Module)

如果它存在,则返回"constant",否则返回nil

更新:抱歉,没有仔细阅读您的问题。

defined?(eval("Required::Module::"+string))

这应该能给你想要的东西。


1
但是这似乎不能与动态命名的模块一起使用,例如defined?(“Parade :: Procedure ::#{procedure.capitalize}”)返回“expression”,即使我调用的模块不存在。 - Andrei Serdeliuc ॐ
我想避免使用 eval,特别是当 string 是用户输入的值时。这可能会造成严重的损害。(更多信息请参见 https://dev59.com/KnRB5IYBdhLWcg3wZWfi) - Andrew K

11

使用const_get方法检查模块是否存在:

begin
    mod = Required::Module::const_get "ModuleName"
    #It exists
rescue NameError
    #Doesn't exist
end

10
我认为异常处理并不是最好的方法,它会使代码变得缓慢和臃肿。相反,应该使用const_defined?来替代。 - horseyguy
你应该编辑答案,做类似于 mod = Required::Module.const_defined?("ModuleName") ? Required::Module::const_get "ModuleName" : nil 的操作。 - Leo Correa
1
请注意,如果模块在静态上下文中已知,则更容易使用 Required::Module::ModuleName - 两者都会抛出 NameError。但是,如果只有在运行时才知道模块名称,则这很有用。 - Aram Kocharyan
1
不要忘记将inherited = false作为第二个参数传递,这很重要。否则,您将与祖先发生冲突。 - puchu

2

您需要检查以下内容:

  • 一个常量是否引用了一个模块,
  • 常量所引用的对象是否是一个模块。

尝试这样做:

 def module_exists?(name, base = self.class)
   base.const_defined?(name) && base.const_get(name).instance_of?(::Module)
 end

然后在你的代码中:

 module_exists?(ARGV.first, Required::Module)

如果在给定的命名空间基础中存在指定名称的模块,它将返回true。与其他答案中给出的示例不同的是,如果查询的名称引用类而不是模块,则它将返回false

在测试中包含类

如果您想更改该行为并强制该方法也对类(而不仅仅是模块)返回true,请将instance_of?更改为is_a?

面向对象编程

如果您的Required :: Module 模块是您要测试其子模块的唯一模块,则还可以以更面向对象的方式编写代码:

 module Required::Module
   def submodule_exists?(name)
     const_defined?(name) && const_get(name).instance_of?(::Module)
   end
 end
 module_function :submodule_exists?

然后在你的代码中:

 Required::Module.submodule_exists?(ARGV.first)

2

如果您已经得到ActiveSupport

mod = ("Required::Module::#{ARGV.first}".constantize rescue nil)

2
我认为这没有意义,因为有更干净的本地方法可以做到这一点。 - Smar

1
在需要扩展其他内容时,您不能基于常量来测试,因为扩展可能不定义新常量。相反,应该基于其他内容的存在,例如新方法。我使用以下内容来测试是否已经需要了open_uri_redirections
if OpenURI.methods.include?(:redirectable_safe?)
  # extension loaded
else
  # extension not loaded
fi

1
当前选择的答案不正确。无论调用该方法的对象是什么,const_get和const_defined都会查找任何常量名称。例如,如果我想在Rails应用程序中检查MyModule :: Rails,使用const_get将返回正常的Rails模块。
要在特定命名空间内检查常量,请使用constants方法并检查您的类:
MyModule.constants.include?(“Rails”)# => false

1
赞。类似于instance_methods等,const_defined?包括第二个参数true(默认情况下,会考虑继承)和false(仅搜索接收者)。我认为我更喜欢使用它,即使它是一个相当模糊的布尔属性,因为Ruby解释器有更优化的搜索方法,而不是通过constants创建一个Ruby数组,并要求Ruby用include?进行搜索。 - Andrew Hodgkinson

0

如果存在,则获取该类:

dynamic_klass = "Required::Module::#{ARGV.first}".classify.safe_constantize

如果类中存在该方法,则调用它:

dynamic_klass.send("some_method") if dynamic_klass.present?

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