新手问题 ActiveSupport::Concern 小结

lex · 2014年06月08日 · 最后由 StephenZzz 回复于 2019年03月29日 · 17601 次阅读

ActiveSupport::Concern 被引入到 rails

根据这篇文章 put-chubby-models-on-a-diet-with-concerns 中这样一段话把它的意思说的很明白了:

This concern can then be mixed into all the models that are taggable and you’ll have a single place to update the logic and reason about it.

Taggable 是一个 Module,里面用来处理跟tag相关的逻辑。这样我们就可以把这个Taggable引入到跟这个相关的models里面。这样代码的可读性和维护性就提高了不少。

举一个简单的例子,比如一个项目中有很多 model 都需要有有判断这个 model 是否是 active 的。我们一般会给这个 model 加上一个is_active字段,然后再相应的 model 里面会有如下的方法。比如

class Post < ActiveRecord::Base

  # scopes
  scope :active, -> {where(is_active: true)}

  ...

  # instances methods
  def active?
    is_active
  end

  ...
end

class Advertisement < ActiveRecord::Base

  # scopes
  scope :active, -> {where(is_active: true)}

  ...

  # instances methods
  def active?
    is_active
  end

  ...
end

PostAdvertisement 都需要判断这个 model 是不是 active 状态的,是否active这个逻辑属性在各个 model 里面的表现又是一致的,这样我们就可以通过 ActiveSupport::Concern 来重构。

首先把相同的逻辑放到一个 model 里面去,称为 act_as_activable。首先定义 scope,然后定义 ClassMethodsinstanceMethods

# app/models/act_as_activable.rb or app/modeles/concerns/act_as_activable.rb
# for models with field :is_active

module ActAsActivable
  extend ActiveSupport::Concern

  included do |base|
    scope :active, -> {where(is_active: true)}
  end

  module ClassMethods
    def all_active(reload = false)
      @all_active = nil if reload
      @all_active ||= active.all
    end
  end

  # instance methods
  def active?
    is_active
  end
end

然后我们把这个model indclude 到需要的 model 里面去。

class Post < ActiveRecord::Base

  # concerns
  include ActAsActivable
  ...
end


class Advertisement < ActiveRecord::Base

  # concerns
  include ActAsActivable
  ...
end

这里我们将 act_as_activable 这个可重用的功能抽出来,然后多个 model 可以共用。这样就有遵循了DRY原则啦。

ActiveSupport::Concern 用来规范 model 代码逻辑。

虽然我们主张 fatter model, thinner controller, 但如果model的代码太多,可读性和维护性则会大大下降。一个好的解决办法是把相关的逻辑代码放到 对应的 ActiveSupport::Concern 里面去,以提高代码的高内聚,低耦合。

#encoding: utf-8
class Event < ActiveRecord::Base

  # concerns
  include ApprovalRequired
  include OptionalChinese
  include ActAsResourceable
  ...

Event 这个 model 就引入 ApprovalRequired, OptionalChinese, ActAsResourceable 这个三个 concern,model 的代码就清晰而且易于维护。

一句话总结就是,使用 ActiveSupport::Concern 会使 model 代码简洁又可读,中看更中用。

如果用这个大神rainchen 的总结就是

  1. concern 将部分可重用的功能抽出来,然后多个 model 可以共用
  2. model 的代码太多,将其相关的逻辑代码放到不同的 concern 里
  3. 规定使用 ActiveSupport::Concern 的代码风格,是希望形成开发规约定,就如 controller 和 model 的写法。

ActiveSupport::Concern 由来

mixin 是把一个模块 (Module)Mixin 到某个对象中,以实现实现多重继承。那如果不用 concern, ActAsActivable 这个 model 会写成怎么样呢?

# app/models/act_as_activable.rb or app/modeles/concerns/act_as_activable.rb
# for models with field :is_active

module ActAsActivable
  def self.included(base)
    base.send(:include, InstanceMethods)
    base.extend ClassMethods
    base.class_eval do
      scope :active, where(is_active: true)
    end
  end

  module InstanceMethods
    def active?
      is_active
    end
  end

  module ClassMethods
    def all_active(reload = false)
      @all_active = nil if reload
      @all_active ||= active.all
    end
  end
end

这算是长久以来形成的一个 common pattern。使用这个钩子 self.included 让里面的代码在被 include 的时候调用。base.send(:include, InstanceMethods), 用 send 方法 include InstanceMethods, 然后 extend ClassMethods, 就是把这些方法放到 baseeigenclass 里面。最后用 class_eval 打开这个类写 scope.

这个 common pattern 在不用 concern 的 gem 里面还是比较常见的。

controller 的 concern

在 rails 4 中不仅提倡在 model 中使用 concern,而且还提倡在 controller 里面也使用。最典型的一个 gem 就是这个 inherited_resources. 当然这是一个 gem,但是意思是差不多的,就是 DON'T Repeat Yourself -- 它将 controller 里面的最典型的 7 个 aciton 封装起来。极大的精简了 controller 里面的代码。

TODO: ActiveSupport::Concern 源码

根据下面这篇文章研究下 源码

activesupport-concern-digression

references:

谢谢,听说这个很久了。原来是 mixin 的改进。感觉还是有些复杂。

给你 32 个赞!

controller 和 model 里面 concern 同名的话似乎有问题,提前留意。

#4 楼 @as181920 谢谢提醒。有时间研究下。

#3 楼 @jasl 恩。那个例子是很老得代码了。把 hasIsActive 更改为 act_as_activable 可能会好一点。谢谢提醒。

这个必须顶 :plus1:

好棒,看到 rubyChina 的源码 concern 这部分内容,搜索到了 David 的文章英文不好,有点看不懂,看了你的讲解这下全明白了,感谢楼主

官方没有解释 这个 mixin 的原理 , 这里解释了

整理的挺好 赞一个

如果只是混入实例方法的话,普通的模块就够用了,就算被引入的模块有多层 include 也没关系。 如果要考虑 extend, 而且还涉及多个层次的模块,这才是 Concern 的用武之地。

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