【问题标题】:Inheriting from class Module从类模块继承
【发布时间】:2018-02-25 22:12:37
【问题描述】:

我正在尝试了解作为文件附件工具包的神社 gem 源代码。你可以像这样在你的模型上定义一个上传器:

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

Attachment 的类定义可以在这个link. 找到

就是这样:

class Attachment < Module
  @shrine_class = ::Shrine
end

我的假设是,这允许您实例化包含中的类,以便方法现在可以在您包含它的地方使用,类似于 mixin。 Module 是 ruby​​ 类吗?这具体是如何工作的?

编辑:

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

class ImageUploader < Shrine
  plugin :remove_attachment
end

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

【问题讨论】:

    标签: ruby-on-rails ruby shrine


    【解决方案1】:

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

    module MyModule
      # ...
    end
    
    # is equivalent to
    
    MyModule = Module.new do
      # ...
    end
    

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

    因为神社的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 的整个子类?嗯,主要有两个原因:

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

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

    【讨论】:

    • 感谢 gem,我真的很喜欢你使用的编码风格。我唯一不清楚的是,当我实例化 ImageUploader::Attachment.new(:image) 时,AttachmentMethods 内部的 initialize 方法是如何被调用的?
    • 在文件末尾调用Shrine.plugin Shrine::Plugins::Base,其中包括Shrine::Plugins::Base::AttachmentMethodsShrine::Attachment(参见Shrine::Plugins::Base::ClassMethods#plugin的实现),所以Shrine::Plugins::Base::AttachmentMethods#initialize和其他所有方法模块被添加到Shrine::Attachment,这就是Shrine::Attachment#initialize的定义方式。
    • 谢谢,虽然我把整个插件系统的实现都归功于 Jeremy Evans,但它实际上是来自 Roda gem 的复制粘贴:github.com/jeremyevans/roda
    • 我在您的博客上注意到您的宝石深受他的风格影响,我计划尽快尝试 Sequel 和 Roda。我肯定会将其中一些技术整合到我自己的工作中:D.
    【解决方案2】:

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

    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
    

    在 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
    

    【讨论】:

      【解决方案3】:

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

      Rails 或 Shrine 的人已经将 Ruby 的知识提升到了远超阅读 Ruby 书籍所能想象的水平,而我已经阅读了十几本。

      99% 的时间包含在表单中

      include SomeModule
      

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

      这个

      include ImageUploader::Attachment.new(:image)
      

      由于许多原因很棘手。

      === 内部 class===

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

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

      ImageUploader::Attachment
      

      === Module.new ===

      如果您阅读了足够多的 Ruby 文档,您会发现 1000 次类和模块之间的区别在于我们无法实例化模块。模块仅用于围绕代码创建命名空间或(主要)在类中混合方法(更准确地说,包括 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 的定义似乎很奇怪,因为它是空的。我没有详细研究shrink.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,以便方法搜索机制在包含的模块中找到方法。 另见How does Inheritance work in Ruby?

      【讨论】:

      • 感谢您的解释! Thus I would have sworn under torture that there is no new method for Module, because there is no need. But there is one, which returns an anonymous module. 真有趣哈哈。
      猜你喜欢
      • 2016-04-14
      • 1970-01-01
      • 2018-05-23
      • 2017-04-13
      • 1970-01-01
      • 2018-01-18
      • 2012-05-28
      • 2012-11-15
      • 1970-01-01
      相关资源
      最近更新 更多