Ruby on Rails 使用 draper gem 實現 decorator design pattern

Ruby on Rails 使用 draper gem 實現 decorator design pattern 的一些用法以及一些筆記,方便某天忘記時可以回過頭來看。

Ruby on Rails 使用 draper gem 實現 decorator design pattern
Photo by Elena Mozhvilo / Unsplash

在開發時常會碰到需要在 view 上撰寫 if else 邏輯的情況,我們可能會把邏輯判斷寫在 helper 內,這時可能會碰到 namespace 或是命名重複的問題,一方面又要保持 DRY 跟好維護,我會使用 draper 這個 gem 來實現 decorator design pattern。

範例

把檔案放在 app/decorators 下,命名前綴就依照 model 名稱,例如 project_decorator

安裝好 gem 之後,要 run 過

rails generate draper:install

建立 ApplicationDecorator 後續就可以用指令

rails generate decorator MODEL_NAME

快速的建立 Decorator

helper

要使用 helper 要記得加 include Draper::LazyHelpers ,不然就要用 h. 或是 helpers.

class ProjectDecorator < Draper::Decorator
  include Draper::LazyHelpers
end

取用 model

要呼叫原本的 model 就可以使用 object 例如:

def url
  object.url || object.organization.url
end

我自己都會用 delegate_all 來讓原本 model 的方法都可以被呼叫

class ProjectDecorator < Draper::Decorator
  delegate_all
  
  def project_status
    if platform?
      # ...
    else
     # ...
    end
  end
end

platform? 就是來自原本的 Project.platform?

實例化

實例化單一 object

@project = Project.first
@decorate = @project.decorate
# 同下面
@decorate = ProjectDecorator.new(@project)
@decorate = ProjectDecorator.decorate(@project)

一次實例化多個 object

@projects = ProjectDecorator.decorate_collection(Project.all)
# 如果是 ActiveRecord 的查詢條件,可以直接下
@projects = Project.all.decorate

實例化 Collection

我自己是想不太到有什麼情境是需要用到這個🤡

class ProjectsDecorator < Draper::CollectionDecorator
  #...
end

搭配 Kaminari paginate helper

class PaginatingDecorator < Draper::CollectionDecorator
    delegate :current_page, :total_pages, :limit_value, :entry_name, :total_count, :offset_value, :last_page?
end

測試

公司的專案是使用 RSpec 所以在 controller 測試內可以寫

assigns(:project).should be_decorated

# 同上
assigns(:project).should be_decorated_with ProjectDecorator

參考資料

https://ruby-china.org/topics/29142