提问人:Milmandre 提问时间:1/13/2020 最后编辑:Milmandre 更新时间:1/13/2020 访问量:43
Ruby 将现有代码封装/打包到命名空间中
Ruby encapsulate/package existing code into a namespace
问:
我一直在四处寻找,但没有找到任何解决我在 Ruby 中面临的问题的答案。我正在编写一个应用程序,它使用不同版本中可用的核心模块集。如果我在另一个核心版本之后采购一个核心集版本,则两个代码版本将同时获取,并且会相互冲突。这很正常,我对此没意见。
一种方法可能是卸载以前的版本以加载新版本,但我想将所有版本都加载到特定的命名空间中(以避免一直卸载/重新加载代码所耗时)。我(或其他)的 2 种可能解决方案
- 要么获取代码,然后将其移动到版本命名空间中(请参阅下面的一些线索,但尚不起作用)
- 或者将代码直接源代码源到版本命名空间中(不知道如何确切地做到这一点,也许使用module_eval但需要使用依赖项重新编码 require 进程)。是否有任何解决方案似乎可行?
这是我试图实现的一个非常简单的 poc
文件:coreset_1.0.0.rb
module CoreA
def self.who_am_i?; self.to_s; end
def self.get_coreb; CoreB end
end
module CoreB
def self.who_am_i?; self.to_s; end
end
文件:coreset_2.0.0.rb(有一些变化)
module CoreA
def self.my_name; self.to_s; end
def self.get_coreb; CoreB end
end
module CoreB
def self.my_name; self.to_s; end
end
文件:coreManager.rb
module CoreManager
def self.load_version(arg_version)
#Create a module set for the selected version
core_set_name = CoreSet + '_' + arg_version.gsub('.', '_')
core_set = eval("Module #{core_set_name}; end; #{core_set_name}"
#Load the requested code
require "coreset_#{arg_version}.rb"
#Move loaded code into it core set module
core_set.const_set(:CoreA, Object.send(:remove_const, :CoreA))
core_set.const_set(:CoreB, Object.send(:remove_const,:CoreB))
#Return the created core set
core_set
end
end
如果运行代码:
require 'coreManager.rb'
core_set = CoreManager.load_version("1.0.0")
puts core_set::CoreA.who_am_i?
puts core_set::CoreA.get_coreB
它返回:
CoreA #not CoreSet_1_0_0::CoreA
uninitialized constant CoreA::CoreB (NameError)
如果运行静态定义的东西,它可以工作
module CoreSet
module CoreA
def self.who_am_i?; self.to_s; end
def self.get_coreb; CoreB end
end
module CoreB
def self.who_am_i?; self.to_s; end
end
end
CoreSet::CoreA.get_coreb
它按预期返回:
CoreSet::CoreB
尽管通常说:“模块是一个常量”,但它似乎不止于此。有什么区别以及如何使动态版本工作?
还有其他想法吗?
感谢您的帮助,:)
答:
您的代码中有几件事被破坏了(我想这对 POC 来说没问题),但最主要的是将常量和全局变量加载到全局命名空间中。require
因此,您的模块并不像您所期望的那样以这种方式命名。有一种方法允许“包装”执行加载的文件,但它被包装在一个匿名模块中,因此您可以防止全局命名空间被污染,但您不能以这种方式“定位”要定义到特定命名空间中的常量。Core<X>
Kernel#load
您可以尝试将代码加载为文本,然后在动态创建的模块中对其进行评估,以匹配您的版本。例如,看看这个快速且非常肮脏的草图(文件应该位于目录中):coreset_...rb
coresets
module CoreSet; end
class CoreManager
class << self
def load_version(ver)
raise "Vesion #{ver} unknown" unless exists?(ver)
file = filename(ver)
code = File.read(file)
versioned_wrapper = Module.new do
class_eval code
end
CoreSet.const_set("V_#{ver.gsub('.', '_')}", versioned_wrapper)
end
private
def exists?(ver)
File.exists? filename(ver)
end
def filename(ver)
"coresets/coreset_#{ver.gsub('.', '_')}.rb"
end
end
end
CoreManager.load_version("1.0.0")
CoreManager.load_version("2.0.0")
p CoreSet::V_1_0_0::CoreA.who_am_i? # => "CoreSet::V_1_0_0::CoreA"
p CoreSet::V_1_0_0::CoreA.get_coreb # => CoreSet::V_1_0_0::CoreB
p CoreSet::V_2_0_0::CoreA.my_name # => "CoreSet::V_2_0_0::CoreA"
p CoreSet::V_2_0_0::CoreA.get_coreb # => CoreSet::V_2_0_0::CoreB
但是,请不要在家里这样做,请:)至少,我会三思而后行。
如果你只需要一次加载所有版本(你需要命名空间,对吧?)是什么阻止你静态地定义它们,比如等等,并使用惯用和安全的方式(自动)加载它们?:)使用动态嵌套常量定义(尤其是删除常量)是射出自己的脚的最简单方法之一......CoreSet::V1::Core<X>
评论
好的,我终于想出了一个可以帮助他人或可以讨论的解决方案。
获取错误未初始化常量 CoreA::CoreB (NameError) 导致我从一个新的角度看待问题。如果我无法从 CoreA 访问 CoreB 模块(因为在将模块常量重新定义到 CoreSet 模块中时模块嵌套已被破坏),那么为什么不在每个核心模块中引用集合中的其他模块?最后,它可以在没有任何肮脏的黑客攻击的情况下工作,我只是在创建指针,而 Ruby Core 发现它本身;)
module CoreManager
def self.load_version(arg_version)
#Create a module set for the selected version
core_set_name = CoreSet + '_' + arg_version.gsub('.', '_')
core_set = eval("Module #{core_set_name}; end; #{core_set_name}"
#Load the requested code
toplevel_consts = Object.constants
require "coreset_#{arg_version}.rb"
core_modules = Object.constants - toplevel_consts
#Move the core modules to the set namespace
core_modules.collect! do |core_module|
core_module_sym = core_module.to_s.to_sym
core_set.const_set(core_module_sym, Object.send(:remove_const, core_module_sym))
eval("#{core_set}::#{core_module}")
end
#Create connexion between set cores to skirt broken module nesting
core_modules.each do |current_core|
core_modules.each do |other_core|
current_core.const_set(other_core.to_s.to_sym, other_core) unless current_core == other_core
end
end
#Return the created core set
core_set
end
end
评论