Skip to content
This repository
branch: master
file 143 lines (136 sloc) 3.607 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
module ActiveSupport
  # A typical module looks like this:
  #
  # module M
  # def self.included(base)
  # base.extend ClassMethods
  # base.class_eval do
  # scope :disabled, -> { where(disabled: true) }
  # end
  # end
  #
  # module ClassMethods
  # ...
  # end
  # end
  #
  # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
  # written as:
  #
  # require 'active_support/concern'
  #
  # module M
  # extend ActiveSupport::Concern
  #
  # included do
  # scope :disabled, -> { where(disabled: true) }
  # end
  #
  # class_methods do
  # ...
  # end
  # end
  #
  # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
  # and a +Bar+ module which depends on the former, we would typically write the
  # following:
  #
  # module Foo
  # def self.included(base)
  # base.class_eval do
  # def self.method_injected_by_foo
  # ...
  # end
  # end
  # end
  # end
  #
  # module Bar
  # def self.included(base)
  # base.method_injected_by_foo
  # end
  # end
  #
  # class Host
  # include Foo # We need to include this dependency for Bar
  # include Bar # Bar is the module that Host really needs
  # end
  #
  # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
  # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
  #
  # module Bar
  # include Foo
  # def self.included(base)
  # base.method_injected_by_foo
  # end
  # end
  #
  # class Host
  # include Bar
  # end
  #
  # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
  # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
  # module dependencies are properly resolved:
  #
  # require 'active_support/concern'
  #
  # module Foo
  # extend ActiveSupport::Concern
  # included do
  # def self.method_injected_by_foo
  # ...
  # end
  # end
  # end
  #
  # module Bar
  # extend ActiveSupport::Concern
  # include Foo
  #
  # included do
  # self.method_injected_by_foo
  # end
  # end
  #
  # class Host
  # include Bar # works, Bar takes care now of its dependencies
  # end
  module Concern
    class MultipleIncludedBlocks < StandardError #:nodoc:
      def initialize
        super "Cannot define multiple 'included' blocks for a Concern"
      end
    end

    def self.extended(base) #:nodoc:
      base.instance_variable_set(:@_dependencies, [])
    end

    def append_features(base)
      if base.instance_variable_defined?(:@_dependencies)
        base.instance_variable_get(:@_dependencies) << self
        return false
      else
        return false if base < self
        @_dependencies.each { |dep| base.send(:include, dep) }
        super
        base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
        base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
      end
    end

    def included(base = nil, &block)
      if base.nil?
        raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)

        @_included_block = block
      else
        super
      end
    end

    def class_methods(&class_methods_module_definition)
      mod = const_defined?(:ClassMethods) ?
        const_get(:ClassMethods) :
        const_set(:ClassMethods, Module.new)

      mod.module_eval(&class_methods_module_definition)
    end
  end
end
Something went wrong with that request. Please try again.