继承自 class 模块

Inheriting from class Module

我正在尝试了解 shrine gem 源代码,它是一个用于文件附件的工具包。您可以在 rails 中为您的模型定义上传器,如下所示:

class Picture < ApplicationRecord
  include ImageUploader::Attachment.new(:image) 
end

Attachment 的 class 定义可以在 link.

中找到

仅此而已:

class Attachment < Module
  @shrine_class = ::Shrine
end

我的假设是,这允许您在包含中实例化 class,以便这些方法现在可以在您包含它的地方使用,类似于混合。 Module 是 ruby class 吗?这究竟是如何工作的?

编辑:

为清楚起见,ImageUploader 在我的应用中定义如下:

class ImageUploader < Shrine
  plugin :remove_attachment
end

所以 ImageUploader::Attachment.new(:image) 正在使用 Shrine 中定义的 Attachment class。

Modules 是一种将方法、classes 和常量组合在一起的方法。这是第一个样本分组 class

app/services/purchase_service.rb

module PurchaseService

  class PurchaseRequest
    def initialize
      # init value
    end

    def request_item
      # action
    end
  end

  class PurchaseOrder
    def initialize
      # init value
    end

    def order_item
      # action
    end
  end
end

在控制器文件中,您可以使用 Module_name::Class_name.new 调用 class,如下所示

@purchase_svc = PurchaseService::PurchaseRequest.new
@purchase_svc.request_item
@purchase_svc = PurchaseService::PurchaseOrder.new
@purchase_svc.order_item

但有时您想要将不会自然形成 class 的事物组合在一起。 这是分组的第二个示例,但不是 class 形式

module_collection.rb, (一个文件有两个模块栈和队列)

module Stacklike
  def stack
    @stack ||= []
  end

  def add_to_stack(obj)
    @stack.push(obj)
  end

  def take_from_stack
    @stack.pop
  end
end

module Queuelike
  #
end

现在,如果一个对象(例如货物)需要具有堆栈功能,那么我将包含该模块。

cargo.rb,

  require './module_collection.rb'
  include Stacklike
    # as cargo needs stack
  class
    def initialize
      stack
      # this will call stack method inside module_collection.rb
    end
  end

Module确实是一个Rubyclass。 class Module 的实例是 Ruby 模块。为了说明,这两种定义模块的方式是等价的:

module MyModule
  # ...
end

# is equivalent to

MyModule = Module.new do
  # ...
end

如果 Module 的实例是 Ruby 模块,这意味着 [=12] 的任何 subclass 的实例=]也是一个Ruby模块,包括Shrine::Attachment。这是有道理的,因为我们知道我们只能 include 模块,所以 Shrine::Attachment 的实例必须是一个模块。

因为 Shrine 的 plugin system design, 这个:

class Attachment < Module
  @shrine_class = ::Shrine
end

不是 Shrine::Attachment 的全部实现;实际实现在 Shrine::Plugins::Base::AttachmentMethods 模块中定义,该模块包含在 Shrine::Attachment.

如果我们查看 Shrine::Attachment.new 的实现,我们可以看到它根据给定的属性名称动态定义自身的方法。例如,Shrine::Attachment.new(:image) 将生成一个定义了以下方法的模块:#image_attacher#image=#image#image_url。然后,这些方法将被添加到包含该 Shrine::Attachment 实例的模型中。


为什么我没有通过 Module.new(如 Refile does)创建新模块的方法,而不是创建 [=12= 的整个子 class ]?嗯,主要有两个原因:

首先,这提供了更好的内省,因为您现在看到的不是模型的祖先列表中的 #<Module:0x007f8183d27ab0>,而是指向其定义的实际 Shrine::Attachment 实例。你仍然可以 manually override #to_s and #inspect,但这个更好。

其次,由于 Shrine::Attachment 现在是 class,其他 Shrine 插件可以用更多行为扩展它。所以 remote_url plugin adds the #<attachment>_remote_url accessor, data_uri 插件添加了 #<attachment>_data_uri 访问器等

注意:准备这个答案需要几个小时。同时 janko-m 已经回答得很好。

Rails 或 Shrine 的人已经将人们在 Ruby 中可以做什么的知识推向了一个远远超出阅读 Ruby 书籍所能想象的水平,而我读了十几本。

99% 的时间包含在表单中

include SomeModule

SomeModule 在单独的文件 some_module.rb 中定义,该文件通过 require 'some_module'.

合并到当前源文件中

这个

include ImageUploader::Attachment.new(:image)

由于很多原因很棘手。

=== 内部 class ===

98% 的时间,class 是一个外部对象,主要包含 def 方法,一些包含和少量 class 实例变量。我没有写过大量的 Ruby 代码,但在特殊情况下只写过一次内部 class 。从外面看,它只能通过提供完整的 访问路径,例如 Shrine::AttachmentShrine::Plugins::Base::AttacherMethods.

我不知道一个 subclass "inherits" 内部 classes 这样就可以写

ImageUploader::Attachment

=== Module.new ===

如果您阅读了足够多的 Ruby 文档,您会发现 class 和模块之间的区别是我们无法实例化模块 1'000 次。模块仅用于围绕一个人的代码或(主要)在 class 中混合方法创建命名空间(更确切地说,include SomeModule 创建一个匿名 superclass 以便方法的搜索路径从 class 到 SomeModule,然后到 superclass(对象,如果没有明确定义))。

所以我可以发誓,Module没有新的方法,因为没有必要。但是有一个 return 是一个匿名模块。

嗯,话说回来,这里我们实例化了classImageUploader::Attachment,不是模块,甚至Module.new实例化了classModule

=== 包含表达式 ===

对于不使用常量而是表达式的 1% 的包含,表达式必须 return 一个模块。您对为什么 Attachment 继承自 Module 有了自己的答案。如果没有这样的继承,include 会抱怨。 运行 下面的代码,可以正常工作。但是如果你取消注释

#    include ImageUploader::Attachment_O.new(:image) 

在class图片中,有错误:

t.rb:28:in `include': wrong argument type Shrine::Attachment_O (expected Module) (TypeError)
    from t.rb:28:in `<class:Picture>'
    from t.rb:27:in `<main>'

文件 t.rb :

class Shrine
    class Attachment_O
        def initialize(parm=nil)
            puts "creating an instance of #{self.class.name}"
        end
    end

    class Attachment_M < Module
        def initialize(parm=nil)
            puts "creating an instance of #{self.class.name}"
        end
    end
end

print 'Attachment_O ancestors '; p Shrine::Attachment_O.ancestors
print 'Attachment_M ancestors '; p Shrine::Attachment_M.ancestors

class ImageUploader < Shrine
end

imupO = ImageUploader::Attachment_O.new
imupM = ImageUploader::Attachment_M.new

print 'imupO is a Module ? '; p imupO.is_a?(Module)
print 'imupM is a Module ? '; p imupM.is_a?(Module)

class Picture
#    include ImageUploader::Attachment_O.new(:image) 
    include ImageUploader::Attachment_M.new(:image) 
end

执行:

$ ruby -w t.rb 
Attachment_O ancestors [Shrine::Attachment_O, Object, Kernel, BasicObject]
Attachment_M ancestors [Shrine::Attachment_M, Module, Object, Kernel, BasicObject]
creating an instance of Shrine::Attachment_O
creating an instance of Shrine::Attachment_M
imupO is a Module ? false
imupM is a Module ? true
creating an instance of Shrine::Attachment_M

This is all it is:

乍一看,附件的定义似乎很奇怪,因为它是空的。 shrine.rb我没有详细研究过,但是我看到了这个:

# Load a new plugin into the current class ...
def plugin(plugin, *args, &block)
...
    self::Attachment.include(plugin::AttachmentMethods) if defined?(plugin::AttachmentMethods)

显然,Attachment 稍后会通过包含模块来填充方法,或者更准确地说,include 创建一个指向 Attachment 的匿名 superclass,它指向 AttachmentMethods,以便方法搜索机制在包含的模块中查找方法。 另请参阅 继承在 Ruby 中如何工作? .