Note : preparing this answer took several hours. In the meantime janko-m has answered well.
The guys at Rails or Shrine have pushed the knowledge of what one can do in Ruby to a level far beyond what one can imagine by reading Ruby books, and I have read a dozen.
99 % of the time include is in the form
include SomeModule
and SomeModule
is defined in a separate file some_module.rb
which is incorporated into the current source file with a require 'some_module'
.
This
include ImageUploader::Attachment.new(:image)
is tricky for many reasons.
=== inner class ===
98 % of the time, a class is an outer object which contains mainly def methods, some includes and a pinch of class instance variables. I haven't written tons of Ruby code, but only once an inner class in a special circumstance. From the outside, it can be used only by giving the full
acces path, such as Shrine::Attachment
or Shrine::Plugins::Base::AttacherMethods
.
I didn't know that a subclass "inherits" inner classes so that one can write
ImageUploader::Attachment
=== Module.new ===
If you read enough Ruby documentation, you'll find 1'000 times that the difference between a class and a module is that we cannot instanciate a module. Modules serve only to create a namespace around one's code or (mainly) mixin methods in a class (more exactly, include SomeModule creates an anonymous superclass so that the search path for methods goes from the class to SomeModule, then to the superclass (Object if not explicitly defined)).
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.
Well, having said that, here we instantiate the classImageUploader::Attachment
, not a module, and even Module.new
instantiates the class Module
.
=== include expression ===
For the 1 % of includes which don't use a constant but an expression, the expression must return a Module. And you have your answer to why Attachment inherits from Module. Whitout such inheritance, include will complain. Run the following code, it works. But if you uncomment
# include ImageUploader::Attachment_O.new(:image)
in class Picture, there is an error :
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
Execution :
$ 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
This is all it is:
At first glance, the definition of Attachment seems curious because it is empty. I haven't studied shrine.rb in detail, but I've seen this :
# Load a new plugin into the current class ...
def plugin(plugin, *args, &block)
...
self::Attachment.include(plugin::AttachmentMethods) if defined?(plugin::AttachmentMethods)
Obviously Attachment is later populated with methods by inclusion of a module, or more exactly, include
creates an anonymous superclass to Attachment, which points to AttachmentMethods, so that the method search mechanism finds the methods in the included module.
See also How does Inheritance work in Ruby?
.