Ruby инкапсулирует / упаковывает существующий код в пространство имен - PullRequest
0 голосов
/ 13 января 2020

Я искал все вокруг и не нашел никакого ответа на проблему, с которой я столкнулся в Ruby. Я пишу приложение, которое использует наборы основных модулей, которые доступны в разных версиях. Если я выбираю версию базового набора за другой, обе версии кода будут исходить из одного и того же и будут sh друг с другом. Это вполне нормально, и я согласен с этим.

Один из подходов может состоять в том, чтобы выгрузить предыдущую версию, чтобы загрузить новую, но я бы хотел, чтобы все они были загружены в определенные c пространства имен ( чтобы избежать трудоемкой выгрузки / перезагрузки кода все время). 2 возможных решения для меня (или, возможно, другого)

  1. Либо введите код, затем переместите его в пространство имен версии (некоторые подсказки, чтобы сделать это, см. Ниже, но пока не работает)
  2. Или исходный код непосредственно в пространство имен версии (не знаю, как это сделать точно, возможно, с помощью module_eval, но нужно перекодировать требуемый процесс с зависимостями). Кажется ли возможным какое-либо решение?

Вот очень простое po c того, что я пытаюсь достичь

file: 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

Несмотря на то, что обычно говорят: «модуль является константой», кажется, что это нечто большее. В чем различия и как заставить работать версию Dynami c?

Любые другие идеи?

Спасибо за помощь, ребята:)

Ответы [ 2 ]

0 голосов
/ 13 января 2020

Хорошо. Наконец-то я пришел к решению, которое может помочь другим или которое можно обсудить.

Получение ошибки Неинициализированная константа CoreA :: CoreB (NameError) заставляет меня принять проблему под новым углом. Если я не могу получить доступ к модулю CoreB из CoreA (поскольку при переопределении констант модуля в модуле CoreSet вложение модуля нарушено), то почему бы не ссылаться в каждом модуле ядра на другие модули в наборе? И, наконец, он работает без какого-либо грязного взлома, я просто создаю указатели, и ядро ​​Ruby находит его изначально;)

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
0 голосов
/ 13 января 2020

В вашем коде есть несколько ошибок (что вполне подходит для PO C, я думаю), но главная из них заключается в том, что 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> et c и использовать идиоматические c и безопасные способы (авто) их загрузки? :) Игра с определением вложенных констант Dynami c (и особенно их удаление) - один из самых простых способов отстрелить собственную ногу ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...