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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With