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
171
172
# 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)

    base.singleton_class.extend const_get(:SingletonClassMethods) if const_defined?(:SingletonClassMethods)
  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)



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

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.



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

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.



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

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:



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

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.



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

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

#singleton_class_methods(&singleton_class_methods_module_definition) ⇒ Object

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

module Example extend ActiveSupport::Concern

singleton_class_methods do
  def foo; puts 'foo'; end

  private
    def bar; puts 'bar'; end
end

end

class Buzz include Example end

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



294
295
296
297
298
299
300
# File 'lib/convenient_service/dependencies/extractions/active_support_concern/concern.rb', line 294

def singleton_class_methods(&singleton_class_methods_module_definition)
  mod = const_defined?(:SingletonClassMethods, false) ?
    const_get(:SingletonClassMethods) :
    const_set(:SingletonClassMethods, Module.new)

  mod.module_eval(&singleton_class_methods_module_definition)
end