3

我正在尝试了解作为文件附件工具包的神社 gem 源代码。您可以在 Rails 中的模型上定义上传器,如下所示:

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

Attachment可以在此链接中找到类定义。

这就是全部:

class Attachment < Module
  @shrine_class = ::Shrine
end

我的假设是,这允许您实例化包含中的类,以便方法现在在您包含它的位置可用,类似于 mixin。是Module红宝石类吗?这究竟是如何工作的?

编辑:

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

class ImageUploader < Shrine
  plugin :remove_attachment
end

ImageUploader::Attachment.new(:image)使用AttachmentShrine 中定义的类也是如此。

4

3 回答 3

2

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

module MyModule
  # ...
end

# is equivalent to

MyModule = Module.new do
  # ...
end

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

由于 Shrine 的插件系统设计,这:

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 一样),而不是创建一个完整的子类Module?嗯,主要有两个原因:

首先,这提供了更好的自省,因为#<Module:0x007f8183d27ab0>您现在看到的不是在模型的祖先列表中,而是Shrine::Attachment指向其定义的实际实例。您仍然可以手动覆盖#to_sand#inspect,但这更好。

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

于 2017-09-18T08:13:14.800 回答
1

注意:准备这个答案需要几个小时。与此同时,janko-m 的回答很好。

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

99 % 的时间包括形式

include SomeModule

SomeModule在一个单独的文件中定义,该some_module.rb文件以require 'some_module'.

这个

include ImageUploader::Attachment.new(:image)

由于许多原因很棘手。

=== 内部类 ===

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

我不知道子类“继承”内部类以便可以编写

ImageUploader::Attachment

=== Module.new ===

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

因此,我会在酷刑下发誓,Module 没有新方法,因为没有必要。但是有一个,它返回一个匿名模块。

好吧,话虽如此,这里我们实例化的是类ImageUploader::Attachment,而不是模块,甚至Module.new实例化了类Module

=== 包含表达式 ===

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

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

在图片类中,有一个错误:

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

这就是全部:

乍一看,Attachment 的定义似乎很奇怪,因为它是空的。我没有详细研究神殿.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 创建一个匿名超类,它指向 AttachmentMethods,以便方法搜索机制在包含的模块中找到方法。另请参阅继承在 Ruby 中如何工作? .

于 2017-09-18T11:06:16.933 回答
1

模块是将方法、类和常量组合在一起的一种方式。这是对班级进行分组的第一个示例

应用程序/服务/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

在 constroller 文件中,您可以使用Module_name::Class_name.new调用类,如下所示

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

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

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

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

货物.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
于 2017-09-17T07:07:58.147 回答