Как динамически изменить вложенность в Ruby? - PullRequest
2 голосов
/ 25 февраля 2012

Я использую модули в качестве пространств имен и хочу динамически заполнять их классами, например:

module Module1
  # ...
end

module Module2
  # ...
end

[Module1, Module2].each do |the_module|
  the_module.module_eval do
    class ApiTest < ActiveSupport::TestCase
      # ...
    end
  end
end

module_eval будет работать, за исключением того, что не меняет вложенность, сохраняя внешнюю. В результате содержащиеся константы не вкладываются в модули.

Первоначальной мотивацией является создание одинаковых тестов для различных реализаций API, каждый из которых содержится в своем собственном модуле.

Ответы [ 5 ]

1 голос
/ 25 февраля 2012

Вот еще один вариант (примечание "self ::")

module Module1
  # ...
end

module Module2
  # ...
end

[Module1, Module2].each do |the_module|
  the_module.module_eval do
    class self::ApiTest < ActiveSupport::TestCase
      # ...
    end
  end
end
1 голос
/ 25 февраля 2012

Вам нужно использовать const_set:

module Bar; end
module Baz; end

[Bar, Baz].each do |mod|
  mod.const_set("Foo", Class.new do
    def hello
      "Hello world!"
    end
  end)
end

Bar::Foo.new.hello  # => "Hello world!"
Baz::Foo.new.hello  # => "Hello world!"

Если новому классу нужен суперкласс, вы можете передать его в качестве параметра в Class.new.

0 голосов
/ 16 марта 2012
# Get ourselves a clean, top-level binding.
def main_binding
  binding
end

module ModuleUtils
  module ModuleMethods ; end
  self.extend ModuleMethods

  module ModuleMethods
    # Get a binding with a Module.nesting list that contains the
    # given module and all of its containing modules as described
    # by its fully qualified name in inner-to-outer order.
    def module_path_binding(mod)
      raise ArgumentError.raise(
        "Can't determine path nesting for a module with a blank name"
      ) if mod.name.to_s.empty?
      m, b = nil, main_binding
      mod.name.split('::').each do |part|
        m, b =
        eval(
          "[ #{part} , #{part}.module_eval('binding') ]",
          b
        )
      end
      raise "Module found at name path not same as specified module" unless m == mod
      b
    end
  end
end

Затем вы можете выполнить код в любом контексте вложенного модуля, используя ModuleUtils.module_path_binding(<some-module>), чтобы получить привязку, которую вы можете передать в качестве второго аргумента eval ().

Подробнее на https://gist.github.com/2051705.

0 голосов
/ 25 февраля 2012

Я не тестировал, но может что-то вроде этого?

[Module1, Module2].each do |the_module|
  the_module.const_set("ApiTest", Class.new(ActiveSupport::TestCase) do
    # ...
  end)
end
0 голосов
/ 25 февраля 2012

Мне кажется, я нашел способ сделать это.

klass = Class.new(String)
klass.class_eval do
  def custom?; return true; end
end
Module1.module_exec do
 const_set :Custom, klass
end

Two::Custom.new.custom?
#=> true

Первый аргумент для Class::new это SuperClass (наследование от)

...