Ruby China

Rails rails 之 concern 常见用法解读 和 源码原理解读

EricWJP · 2020年02月17日 · 最后由 EricWJP 回复于 2020年02月17日 · 4342 次阅读

concern 只是一个 extend ActiveSupport::Concern 的 module。

既可以在 model 中使用,也可以在 controller 中使用。
使用方式: include ConcernName

concern 用途

一、用于把特定功能代码集合封装成一个 concern,提高代码可读性和方便代码重构;
二、把多个 model 或者 多个 controller 重复的功能集合代码封装成一个 concern。

concern 定义

#定义一
module MyConcern
 extend ActiveSupport::Concern
 ...
end
#定义二
concern MyConcern do 
 ...
end

以上两种定义 concern 的方式完全等同

常见 concern 内部定义

ActiveSupport::Concern 提供了一种约定配置:
约定 ClassMethods 用来给类添加类方法(可选)
module ClassMethods
end
定义 included 作为当当前 module(也就是子 concern) 被 include 的回调方法(可选)
def self.included
end

如果不使用 ClassMethods,concern 与普通 module 是一样的
module FirstConcern
 #把ActiveSupport::Concern中的方法 加入到 当前module模块方法(与class类方法原理相同)中
 extend ActiveSupport::Concern
 def self.included(base) 
 base.instance_eval do
 scope :jief, ->{...}
 has_many :table
 end
 # 或者
 #base.class_eval do
 # ... 
 #end
 ...
 end
 #included 也可以这样
 #included do 
 # 这里作用域是base
 # 
 # ...
 #end
 module ClassMethods
 #定义类方法
 def my_method
 end
 ...
 end
 #或者
 # class_methods do
 # ...
 # end
end

ActiveSupport::Concern 代码详解

module Concern
 # 负责当定义多个 included 抛出异常
 class MultipleIncludedBlocks < StandardError #:nodoc:
 def initialize
 super "Cannot define multiple 'included' blocks for a Concern"
 end
 end
 # extended 是回调方法,当 当前module(这里指的是ActiveSupport::Concern) 被 extend 的时候执行
 # 参数--base是extend当前模块的module或者class
 # 用途,在base的作用域初始化@dependencies为空数组 []
 #@_dependencies是base的类实例变量(类实例变量只有类可以访问,他的实例无法访问),
 #用于记录base中include哪些module,顺序---从上到下,深度--1
 def self.extended(base) #:nodoc:
 base.instance_variable_set(:@_dependencies, [])
 end
 #这里的 append_features 是 对 Module.append_features 的扩展
 # Module#include 方法最终是 通过 Module.append_features 实现的(参数顺序是相反的)
 # append_features 只有在多个concern,include嵌套(即一个concern include 若干个其他的concern)时
 #使用此时扩展后的append_features。
 # 当子concern被include时 调用append_features(可理解成 include) 和 included,
 #append_features 在 included 之前调用
 # 参数--base是include当前模块的module或者class
 def append_features(base)
 if base.instance_variable_defined?(:@_dependencies)
 # instance_variable_defined? 用于判断实例变量是否存在。
 #如果 base是module,并且 extend ActiveSupport::Concern,
 # 也就是当 base是concern(这里称其为父concern)时,
 # @_dependencies 就会在上面extended回调方法中初始化
 #否则 这里不会执行。
 #把当前concern 加入到 base(父concern)的 类实例变量 @_dependencies中
 base.instance_variable_get(:@_dependencies) << self
 return false
 else
 # 当base不是concern(base 是 class 或者 是普通 module),才会执行
 #如果base 已经继承了self(子concern) 就不再执行,
 #这也就是 多次include 同一个module时,实际上只执行了第一次的原因
 return false if base < self 
 #遍历子concern 类实例变量 @_dependencies,base include 每一个子concern include的module
 @_dependencies.each { |dep| base.include(dep) } 
 super #执行祖先链后面按序逐个查找并执行 append_features 方法
 #如果子concern 定义了 module ClassMethods,
 # base 会 extend 该module-ClassMethods
 base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) 
 #在base作用域中执行 子concern中 included 方法的内容(Proc实例)
 base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) 
 end
 end
 #这里扩展了 Module#included 方法
 def included(base = nil, &block)
 if base.nil?
 #这里的处理 实现了 included do ... end
 raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
 # 通过实例变量@_included_block,实现了 included 传入的块(这里变成了Proc对象) 共享,
 # 即append_features能够访问到
 @_included_block = block
 else
 super
 end
 end
 #这里实现了 class_methods do ... end
 def class_methods(&class_methods_module_definition)
 # mod 判断子concern中是否定义了 module ClassMethods,没有就用Module.new 初始化
 mod = const_defined?(:ClassMethods, false) ?
 const_get(:ClassMethods) :
 const_set(:ClassMethods, Module.new)
 # mod 把 传入的块(这里变成了Proc对象) 添加到 mod 中
 mod.module_eval(&class_methods_module_definition)
 end
 end

concern 用途 一、用于把特定功能代码集合封装成一个 concern,提高代码可读性和方便代码重构; 二、把多个 model 或者 多个 controller 重复的功能集合代码封装成一个 concern。

用普通的 include/module 办不到吗。

可以。这种更优雅。ruby 元编程这本书有讲到

QETHAN 回复

如何定义"优雅"?我面试的时候经常问别人 😉

concern 用处有两个

  1. 为 mixin 提供语法糖。
  2. 解决多个 module 相互依赖问题。

解决依赖问题是通过 override Module#append_featuresModule#included两个钩子,希望多介绍这两个钩子。

@piecehealth
因为 concern 定义了这些约定
ActiveSupport::Concern 提供了一种约定配置:
约定 ClassMethods 用来给类添加类方法(可选)
module ClassMethods
end
定义 included 作为当当前 module(也就是子 concern) 被 include 的回调方法(可选)
def self.included
end
如果说 concern 的用途这样的描述:
为 mixin 提供语法糖。
解决多个 module 相互依赖问题。
不够直观,太书面,好的东西写的很隐晦的话,反而达不到他的效果。如果这篇文章给初学者来学习,那一种说法更直观呢,或者初学者更容易学习呢?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号

AltStyle によって変換されたページ (->オリジナル) /