Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheriting from class Module

I am trying to understand the shrine gem source code that is a toolkit for file attachments. You can define an uploader on your model in rails like so:

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

The class definition for Attachment can be found at this link.

This is all it is:

class Attachment < Module
  @shrine_class = ::Shrine
end

My assumption is that this allows you to instantiate the class in the include so that the methods are now available where you included it, similar to a mixin. Is Module a ruby class? How exactly does this work?

Edit:

For clarity, ImageUploader is defined in my app like so:

class ImageUploader < Shrine
  plugin :remove_attachment
end

So ImageUploader::Attachment.new(:image) is using that Attachment class defined in Shrine.

like image 235
trueinViso Avatar asked Oct 16 '25 22:10

trueinViso


1 Answers

Module is indeed a Ruby class. An instance of the class Module is a Ruby module. To illustrate, these two ways of defining a module are equivalent:

module MyModule
  # ...
end

# is equivalent to

MyModule = Module.new do
  # ...
end

If an instance of Module is a Ruby module, that means that an instance of any subclass of Module is also a Ruby module, including Shrine::Attachment. This makes sense, because we know that we can include only modules, so an instance of Shrine::Attachment has to be a module.

Because of Shrine's plugin system design, this:

class Attachment < Module
  @shrine_class = ::Shrine
end

isn't the whole implementation of Shrine::Attachment; the actual implementation is defined in the Shrine::Plugins::Base::AttachmentMethods module, which gets included into Shrine::Attachment.

If we look at the implementation of Shrine::Attachment.new, we can see that it dynamically defines methods on itself based on the given attribute name. For example, Shrine::Attachment.new(:image) will generate a module with the following methods defined: #image_attacher, #image=, #image, and #image_url. These methods will then get added to the model that includes that Shrine::Attachment instance.


Why didn't I just have a method that creates a new module via Module.new (like Refile does), instead of creating a whole subclass of Module? Well, two main reasons:

First, this gives better introspection, because instead of seeing #<Module:0x007f8183d27ab0> in your model's ancestors list, you now see an actual Shrine::Attachment instance that points to its definition. You could still manually override #to_s and #inspect, but this is better.

Second, since Shrine::Attachment is now a class, other Shrine plugins can extend it with more behaviour. So remote_url plugin adds the #<attachment>_remote_url accessor, data_uri plugin adds the #<attachment>_data_uri accessor etc.

like image 158
janko-m Avatar answered Oct 18 '25 13:10

janko-m



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!