I'm trying to better my understanding of meta-programming in Ruby and am confused as to what Module.included
is? My current understanding is that this is a callback invoked by Ruby whenever the module is included into another module or class. Other than that, what types of (meta-)programming constructs are these used in? Any examples?
Module#included
allows modules to inject both class and instance methods, and execute associated code, from a single include.
The documentation for ActiveSupport::Concern illustrates a typical use. It's injecting class methods into the calling class, and executing code. In this case, adding a scope.
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
module ClassMethods
...
end
end
And here's the ActiveSupport::Concern version which does the same thing, but adds declarative syntax sugar.
require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
scope :disabled, -> { where(disabled: true) }
end
class_methods do
...
end
end
With included
a class simply includes the module. It allows the module to be one neat package: instance methods, class methods, and setup code.
class Thing
# Class and instance methods are injected, and the new scope is added.
include M
end
Without included
a module can only inject instance methods. Class methods would have to be added separately, as well as executing any setup code.
module M
def some_instance_method
...
end
module ClassMethods
def setup
scope :disabled, -> { where(disabled: true) }
end
end
end
class Thing
# Inject the instance methods
include M
# Inject the class methods
extend M::ClassMethods
# Run any setup code.
setup
end
Other examples might be registering a class, for example as an available plugin.
module Plugin
def self.included(base)
base.extend ClassMethods
base.class_eval do
register_as_plugin(base)
end
end
module ClassMethods
def register_as_plugin(klass)
...
end
end
end
class Thing
include Plugin
end
Or adding necessary accessors.
module HasLogger
def self.included(base)
base.class_eval do
attr_writer :logger
end
end
def logger
@logger ||= Rails.logger
end
end
class Thing
include HasLogger
end