根据这篇文章 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
Post
和 Advertisement
都需要判断这个 model 是不是 active
状态的,是否active
这个逻辑属性在各个 model 里面的表现又是一致的,这样我们就可以通过 ActiveSupport::Concern
来重构。
首先把相同的逻辑放到一个 model
里面去,称为 act_as_activable
。首先定义 scope,然后定义 ClassMethods
和 instanceMethods
。
# 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
原则啦。
虽然我们主张 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 的总结就是
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
, 就是把这些方法放到 base
的 eigenclass
里面。最后用 class_eval
打开这个类写 scope.
这个 common pattern
在不用 concern 的 gem 里面还是比较常见的。
在 rails 4 中不仅提倡在 model 中使用 concern,而且还提倡在 controller 里面也使用。最典型的一个 gem 就是这个 inherited_resources. 当然这是一个 gem,但是意思是差不多的,就是 DON'T Repeat Yourself -- 它将 controller 里面的最典型的 7 个 aciton 封装起来。极大的精简了 controller 里面的代码。
activesupport-concern-digression