Module: ConvenientService::Dependencies::Extractions::ActiveSupportConcern::Concern

Defined in:
lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb

Overview

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 ActiveSupport::Concern 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 base is the Bar module, not the Host class. With ActiveSupport::Concern, 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 # It works, now Bar takes care of its dependencies
end

Prepending concerns

Just like include, concerns also support prepend with a corresponding prepended do callback. module ClassMethods or class_methods do are prepended as well.

prepend is also used for any dependencies.

Defined Under Namespace

Classes: MultipleIncludedBlocks, MultiplePrependBlocks

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object

:nodoc:



146
147
148
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 146

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

Instance Method Details

#append_features(base) ⇒ Object

:nodoc:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 150

def append_features(base) # :nodoc:
  if base.instance_variable_defined?(:@_dependencies)
    base.instance_variable_get(:@_dependencies) << self
    false
  else
    return false if base < self
    @_dependencies.each { |dep| base.include(dep) }
    super

    base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)

    ##
    # NOTE: Customization compared to original `Concern` implementation.
    # TODO: Why original `Concern` implementation uses `const_defined?(:ClassMethods)`, not `const_defined?(:ClassMethods, false)`?
    # NOTE: Changed order to have a way to control when to include `InstanceMethods` and `ClassMethods` from `included` block.
    #
    base.include const_get(:InstanceMethods) if const_defined?(:InstanceMethods)

    base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
  end
end

#class_methods(&class_methods_module_definition) ⇒ Object

Define class methods from given block. You can define private class methods as well.

module Example
  extend ActiveSupport::Concern

  class_methods do
    def foo; puts 'foo'; end

    private
      def bar; puts 'bar'; end
  end
end

class Buzz
  include Example
end

Buzz.foo # => "foo"
Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)


264
265
266
267
268
269
270
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 264

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

  mod.module_eval(&class_methods_module_definition)
end

#included(base = nil, &block) ⇒ Object

Evaluate given block in context of base class, so that you can write class macros here. When you define more than one included block, it raises an exception.



197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 197

def included(base = nil, &block)
  if base.nil?
    if instance_variable_defined?(:@_included_block)
      if @_included_block.source_location != block.source_location
        raise MultipleIncludedBlocks
      end
    else
      @_included_block = block
    end
  else
    super
  end
end

#instance_methods(include_private = false, &instance_methods_module_definition) ⇒ Object

NOTE: Customization compared to original ‘Concern` implementation.



231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 231

def instance_methods(include_private = false, &instance_methods_module_definition)
  ##
  # NOTE: This `if` is propably the reason why Rails team decided to create only `class_methods` in the original Concern implementation.
  #
  return super(include_private) unless instance_methods_module_definition

  mod = const_defined?(:InstanceMethods, false) ?
    const_get(:InstanceMethods) :
    const_set(:InstanceMethods, Module.new)

  mod.module_eval(&instance_methods_module_definition)
end

#prepend_features(base) ⇒ Object

:nodoc:



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 172

def prepend_features(base) # :nodoc:
  if base.instance_variable_defined?(:@_dependencies)
    base.instance_variable_get(:@_dependencies).unshift self
    false
  else
    return false if base < self
    @_dependencies.each { |dep| base.prepend(dep) }
    super

    base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)

    ##
    # NOTE: Customization compared to original `Concern` implementation.
    # TODO: Why original `Concern` implementation uses `const_defined?(:ClassMethods)`, not `const_defined?(:ClassMethods, false)`?
    # NOTE: Changed order to have a way to control when to include `InstanceMethods` and `ClassMethods` from `included` block.
    #
    base.prepend const_get(:InstanceMethods) if const_defined?(:InstanceMethods)

    base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
  end
end

#prepended(base = nil, &block) ⇒ Object

Evaluate given block in context of base class, so that you can write class macros here. When you define more than one prepended block, it raises an exception.



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 214

def prepended(base = nil, &block)
  if base.nil?
    if instance_variable_defined?(:@_prepended_block)
      if @_prepended_block.source_location != block.source_location
        raise MultiplePrependBlocks
      end
    else
      @_prepended_block = block
    end
  else
    super
  end
end