Rails 项目中通常都是将业务逻辑写在 Model 中,有时候一些业务逻辑不仅仅只是在一个特定的 Model 中,有时候一些相同的业务逻辑适用于多个 Model,我们就可以利用 ActiveSupport::Concern 来做封装,也可以参考 Put chubby models on a diet with concerns 了解更多。

重复的代码

在一个学校的管理系统中,一个学校有很多个相册,一个管理员只在一个学校,如何控制管理员在查看相册时候是它所属的学校的相册,还有很多如学校公告和学校食谱也属于这种情况,下面这种做法在很多地方都会重复:

resources :school_albums do
  resources :school_album_images, :except => :show
end

def show
  @album = SchoolAlbum.find(params[:id])
  redirect_to root_path if @album.school_id != current_user.schools.first.id
  @images = @album.school_album_images.order("time DESC")
end

不重复的代码

app/models/concerns/school_visible.rb 文件中

module SchoolVisible
  extend ActiveSupport::Concern

  def visible_to(user)
    raise CanCan::AccessDenied if self.school_id != user.school.id
  end
end

app/models/school_ablum.rb 文件中

class SchoolAlbum < ActiveRecord::Base
  self.table_name = "school_album"
  belongs_to :school, :foreign_key => "school_id"
  has_many :school_album_images, :foreign_key => "album_id", :dependent => :destroy

  validates :title, :presence => true, :length => { :maximum => 14 }
  include SchoolVisible
end

在 Controller 中的代码变得更直观和简洁:

def show
  @album = SchoolAlbum.find(params[:id])
  @album.visible_to(current_user)
  @images = @album.school_album_images.order("time DESC")
end