从类Module

时间:2017-09-17 06:12:26

标签: ruby-on-rails ruby shrine

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

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

可以在此link.

找到Attachment的类定义

这就是全部:

class Attachment < Module
  @shrine_class = ::Shrine
end

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

编辑:

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

class ImageUploader < Shrine
  plugin :remove_attachment
end

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

3 个答案:

答案 0 :(得分: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的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),而不是创建Module的整个子类?那么,有两个主要原因:

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

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

答案 1 :(得分:1)

Modules是一种将方法,类和常量分组在一起的方法。这是第一个分组类的样本

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

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

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

答案 2 :(得分:1)

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

Rails或Shrine的人们通过阅读Ruby书籍,将人们在Ruby中可以做的事情的知识推向远远超出人们想象的水平,而且我已经阅读了十几本。

99%的时间包括

形式
include SomeModule

SomeModule在单独的文件some_module.rb中定义,该文件与require 'some_module'合并到当前源文件中。

include ImageUploader::Attachment.new(:image)
由于很多原因,

很棘手。

=== inner class ===

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

我不知道子类&#34;继承&#34;内部类,以便人们可以写

ImageUploader::Attachment

=== Module.new ===

如果您阅读了足够的Ruby文档,那么您会发现类和模块之间的差异是我们无法实现模块的1到3000倍。模块仅用于围绕一个代码或(主要)mixin方法创建一个命名空间(更准确地说,包括SomeModule创建一个匿名超类,以便方法的搜索路径从类到SomeModule,然后到超类(如果没有明确定义,则为对象))。

因此,我会在折磨下宣誓没有新的方法,因为没有必要。但有一个,它返回一个匿名模块。

好吧,说过这里,我们实例化了类ImageUploader::Attachment,而不是模块,甚至Module.new实例化了类Module

=== include expression ===

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

#    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>'

file 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
  

这就是全部:

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

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

显然,附件后来通过包含一个模块来填充方法,或者更确切地说,include为Attachment创建一个匿名超类,它指向AttachmentMethods,以便方法搜索机制在包含的模块中查找方法。 另请参阅How does Inheritance work in Ruby?