Last active
February 7, 2018 16:42
-
-
Save sfcgeorge/bdf4ab6a95f74f75a502bb7d8d3155df to your computer and use it in GitHub Desktop.
Tweaked ActiveSupport::Concern to work around Opal issue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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 | |
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 | |
false | |
else | |
return false if base < self | |
@_dependencies.each { |dep| base.include(dep) } | |
super | |
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) | |
# if instance_variable_defined?(:@_included_block) | |
# base.class_eval(&@_included_block) | |
# end | |
if @_included_block | |
base.class_eval(&@_included_block) | |
end | |
end | |
end | |
def included(base = nil, &block) | |
if base.nil? | |
if instance_variable_defined?(:@_included_block) | |
raise MultipleIncludedBlocks | |
end | |
@_included_block = block | |
else | |
super | |
end | |
end | |
def class_methods(&class_methods_module_definition) | |
mod = if const_defined?(:ClassMethods, false) | |
const_get(:ClassMethods) | |
else | |
const_set(:ClassMethods, Module.new) | |
end | |
mod.module_eval(&class_methods_module_definition) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment