Ruby-如何在模块中包含类

时间:2019-09-20 18:17:12

标签: ruby

我是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 个答案:

答案 0 :(得分:5)

  

我不确定是什么问题。我了解它说未初始化,但是我不确定为什么。似乎是在寻找常量而不是在阅读课?

我不清楚您“读课”的意思。是的,Ruby正在寻找一个常量。以大写字母开头的变量名称是常量,ergo,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

答案 1 :(得分:3)

您收到错误Uninitialized Constant,因为在红宝石中,HeadingTagsHtmlBody::HeadingTags是两个不同的常量。 Ruby在这里没有考虑文件路径。要实现所需的功能,您需要明确声明HeadingTags属于HtmlBody,如下所示:

htmlbody.rb

module HtmlBody; end

htmlbody / headingtags.rb

module HtmlBody
    class HeadingTags; end
end

或者class HtmlBody::HeadingTags; end。 但是,如果要在模块下动态定义常量,则可以查看const_set方法。

答案 2 :(得分:1)

Jorg的答案更为详尽,并详细说明了原因。我会顺从他的意见,但我的回答要简洁。 https://stackoverflow.com/a/58034705/1937435

您可以使用eval完成您想做的事情,但是不建议这样做。您在此处寻找的功能是通过包含和扩展来实现的。

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

将它们拼接成单独的文件时,只需在文件顶部(不在名称空间中)执行通常的requirerequire_relative并相应地调用它们即可。

答案 3 :(得分:0)

我并不是说这是个好主意,但红宝石总是愿意给你足够的绳索以将自己射向脚,所以...

仅出于娱乐目的,您可以将这些功能(以非常通用的假设)结合在一起:

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

Example