Search code examples
ruby-on-railsrubymetaprogramming

What is Ruby module.included?


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?


Solution

  • 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