Ruby - Как включить классы в модуль - PullRequest
0 голосов
/ 20 сентября 2019

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

Я хотел бы использовать модуль для пространства имен.Модуль будет включать определения классов.

Это была моя попытка.

lib / HtmlBody.rb

module HtmlBody
    require_relative './html_body/HeadingTags'
    require_relative './html_body/AnchorTags'
    require_relative './html_body/ImgTags'
end

lib / html_body / HeadingTags.rb

class HeadingTags
  ...
end

А из другого файла мне потребуется модуль lib / HtmlBody .

require_relative 'lib/HtmlBody'

HtmlBody::HeadingTags.new

Это будетвернуть ошибку.:

1: from (irb):9:in `rescue in irb_binding'
NameError (uninitialized constant HtmlBody::HeadingTags)

Я не уверен, в чем проблема.Я понимаю, что там написано неинициализировано , но я не уверен почему.Кажется, что он ищет константу вместо чтения класса?

Как вы должны включать классы, расположенные в отдельных файлах внутри модуля?

Это то, что мне не хватает в Ruby и require / require_relative вероятно.

Ответы [ 4 ]

5 голосов
/ 20 сентября 2019

Я не уверен, в чем проблема.Я понимаю, что там написано неинициализировано , но я не уверен почему.Кажется, он ищет константу вместо чтения класса?

Мне не понятно, что вы имеете в виду под "чтением класса".Да, Руби ищет постоянную.Имена переменных, начинающиеся с заглавной буквы, являются константами, поэтому HtmlBody - это константа, HeadingTags - это константа, а HtmlBody::HeadingTags - это константа HeadingTags, расположенная в классе или модуле, на который ссылается константа HtmlBody.

Как вы должны включать классы, расположенные в отдельных файлах внутри модуля?

Пространство имен внутри модуля определяется путем определения класса внутри модуля,Если вы уверены , что модуль уже существует, вы можете определить класс следующим образом:

class HtmlBody::HeadingTags
  # …
end

Однако, если HtmlBody не определено (или не является классом или модулем), это не удастся.

module HtmlBody
  class HeadingTags
    # …
  end
end

Это будет гарантировать, что модуль HtmlBody будет создан, если он не существует (и просто повторно открыт, если он уже существует).

Существует также небольшое различие в правилах постоянного поиска между ними, что, однако, не относится к вашему вопросу (но имейте это в виду).

Это то, чего мне не хватает в Ruby и require / require_relative вероятно.

Действительно, ваш вопрос проистекает из фундаментального недопонимания того, что Kerne#load / Kernel#require /Kernel#require_relative делает.

Вот очень сложное, подробное, подробное объяснение всех невероятно запутанных вещей, которые делают эти три метода.Готовься!Вы готовы?Здесь мы идем:

Они запускают файл.

Подождите ... это все?Да это оно!Это все, что нужно сделать.Они запускают файл.

Итак, что происходит, когда вы запускаете файл, который выглядит следующим образом:

class HeadingTags
  # …
end

Он определяет класс с именем HeadingTags впространство имен верхнего уровня, верно?

Хорошо, так что же произойдет, когда мы сейчас сделаем это:

require_relative './html_body/HeadingTags'

Ну, мы сказали, что require_relative просто запускаетфайл.И мы сказали, что запуск этого файла определяет класс с именем HeadingTags в пространстве имен верхнего уровня.Следовательно, это, очевидно, определит класс с именем HeadingTags в пространстве имен верхнего уровня.

Теперь посмотрим на ваш код: что происходит, когда мы делаем это:

module HtmlBody
  require_relative './html_body/HeadingTags'
end

Опять же, мы сказали, что require_relative просто запускает файл.Ничего больше.Не меньше.Просто запустите файл.И что мы сказали, что работает этот файл?Он определяет класс с именем HeadingTags в пространстве имен верхнего уровня.

Итак, что будет делать вызов require_relative из определения модуля HtmlBody?Он определит класс с именем HeadingTags в пространстве имен верхнего уровня .Поскольку require_relative просто запускает файл, и, следовательно, результат будет точно таким же, как и запуск файла, и результат запуска файла заключается в том, что он определяет класс в пространстве имен верхнего уровня.

Итак,как вы на самом деле достигаете того, что вы пытаетесь сделать?Что ж, если вы хотите определить класс внутри модуля, вы должны ... определить класс внутри модуля!

lib / html_body.rb

require_relative 'html_body/heading_tags'
require_relative 'html_body/anchor_tags'
require_relative 'html_body/img_tags'

module HtmlBody; end

lib / html_body / heading_tags.rb

module HtmlBody
  class HeadingTags
    # …
  end
end

lib / html_body / anchor_tags.rb

module HtmlBody
  class AnchorTags
    # …
  end
end

lib / html_body/img_tags.rb

module HtmlBody
  class ImgTags
    # …
  end
end

main.rb

require_relative 'lib/html_body'

HtmlBody::HeadingTags.new
3 голосов
/ 20 сентября 2019

Вы получаете ошибку Uninitialized Constant, потому что в ruby ​​HeadingTags и HtmlBody::HeadingTags - это две разные константы.Ruby не учитывает путь к файлу здесь.Чтобы достичь того, что вы хотите, вам нужно явно объявить HeadingTags как принадлежащий HtmlBody, например:

htmlbody.rb

module HtmlBody; end

htmlbody / headingtags.rb

module HtmlBody
    class HeadingTags; end
end

Или альтернативно class HtmlBody::HeadingTags; end.Однако, если вы хотите динамически определять константы в модуле, вы можете обратиться к методу const_set.

1 голос
/ 20 сентября 2019

Ответ Йорга гораздо более тщательный и объясняет, почему.Я подчинюсь его, но оставлю свой краткий ответ на месте.https://stackoverflow.com/a/58034705/1937435

Вы можете выполнить то, что пытаетесь сделать с помощью eval, но это не рекомендуется.Функциональность, которую вы здесь ищете, реализована с помощью include и extension.

include назначит методы модуля на уровне экземпляра.extend назначит методы модуля на уровне класса.

module HtmlBody; end

module HeadingInstanceMethods
  def h1
    puts "I am h1"
  end
end

module HeadingClassMethods
  def valid_headers
    ["h1", "h2"]
  end
end

module HtmlBody
  class HeadingTags
    include HeadingInstanceMethods
    extend HeadingClassMethods
  end
end 

HtmlBody::HeadingTags.valid_headers # => ["h1", "h2"]
HtmlBody::HeadingTags.new.h1 # => I am h1

Когда вы объединяете их в отдельные файлы, просто сделайте обычный require или require_relative в верхней части файла (нев пространстве имен) и назовите их соответственно.

0 голосов
/ 20 сентября 2019

Я не предполагаю, что это хорошая идея, но рубин всегда готов дать вам достаточно веревки, чтобы выстрелить себе в ногу, так что ...

Просто ради удовольствия, вы можете взломатьэта функциональность вместе (с чрезвычайно общими сделанными допущениями) как:

module HtmlBody
  def self.include_in_scope(constant_name,path)
    require_relative path
    self.const_set(constant_name.to_s, Object.send(:remove_const, constant_name.to_s))
  end
end

klass_list = {HeadingTags: './html_body/HeadingTags', 
              AnchorTags: './html_body/AnchorTags'
              ImgTags: './html_body/ImgTags'}

klass_list.each do |name,path|
   HtmlBody.include_in_scope(name, path) 
end

Теперь все классы имеют пространство имен в HtmlBody например HtmlBody::HeadingTags, но не включены в область верхнего уровня, например ::HeadingTags повыситNameError.

Пример

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