Summary
- ruby can include instance methods and extend class methods, but can't do those two things together.
Rails just want do though this together(powerful language with powerful framework ...). - at first, rails use included hook,
when a class includes the instance methods, it extend class methods by theincludes
hook
so rails do those two things together. - but when a module includes a module, problem happens.
the second level module extended the first level module,
yeah, the first level's ClassMethod's methods have became the second level's class method.
but, the second level module's ClassMethods' methods do not have the first level's ClassMethods' methods,
so when include the second level module in a class, the class can not get the first level's ClassMethods' methods. Because the first level module's class methods do not in the second level module's ClassMethods module - rails solve this by overwriting the
apppend_features
it flatten all nest included modules with right order.
and let the class include all those included module in the right order.
put include and extend together
includes and extend
included is a hook, when a class includes a module, this hook will be called
def self.included(base)
base.extend ClassMethods # ...
end
nested include
Problem happens when BaseClass includes the FirstLevelModule, so the please read the comment in it and then read comment in FirstLevelModule's `included`
module SecondLevelModule
def self.included(base)
base.extend ClassMethods
end
def second_level_instance_method; 'ok'; end
module ClassMethods
def second_level_class_method; 'ok'; end
end
end
module FirstLevelModule
def self.included(base)
# called because the BaseClass me(`FirstLevelModule`)
# I let the BaseClass extend all the methods in my ClassMethods module
# but wait a minute, I include the SecondLevelModule,
# and I have second_level_class_method as my class method
# but it is not in my ClassMethods module,
# so the BassClass dose not have second_level_class_method as its class method :(
# include trick broken ...
base.extend ClassMethods
end
def first_level_instance_method; 'ok'; end
module ClassMethods
def first_level_class_method; 'ok'; end
end
include SecondLevelModule
end
class BaseClass
# the FirstLevelModule's included hook will be called
include FirstLevelModule
end
append_features - solving module includes module problem
the append_features is overwrote by rails
goal: never include a concern in another concern,
so just let the includer(class or module) 'includes' all concerns in the right order.
it has two part
a concern includes a concern,
checked by base.instance_variable_defined?("@_dependencies")
it add the included concern as a dependent and add it to its @_dependencies-
a module includes a concern
def append_features(base)
# recursive
# no need include and extend
return false if base < self
@_dependencies.each { |dep| base.send(:include, dep) }# to include class method by call original append_features super # extend class methods base.extend const_get("ClassMethods") if const_defined?("ClassMethods") base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block") end