在我的項目中,我有一個場景,在一個控制器動作中,我需要做大量依賴的東西,不能直接移動到模型中。爲了保持控制器不變,我最終將該代碼移到幫助器中。這是一個很好的做法嗎?我應該在控制器還是助手中保留更多代碼?
例如,在一個控制器動作,我需要做第一驗證請求的校驗和,然後進行輸入在兩個表中,然後調用一些外部API和根據結果來更新一些的值。我最終將呼叫轉移到外部API給幫手。
在我的項目中,我有一個場景,在一個控制器動作中,我需要做大量依賴的東西,不能直接移動到模型中。爲了保持控制器不變,我最終將該代碼移到幫助器中。這是一個很好的做法嗎?我應該在控制器還是助手中保留更多代碼?
例如,在一個控制器動作,我需要做第一驗證請求的校驗和,然後進行輸入在兩個表中,然後調用一些外部API和根據結果來更新一些的值。我最終將呼叫轉移到外部API給幫手。
好習慣?當然不!它不僅會讓代碼維護變得一件糟糕的任務,而且還會使測試變得困難。
從未有其中的邏輯不能從一個控制器移動的場景。一般而言,對於任何面向對象的語言,項目應該基於MVC模式,至少將業務邏輯與演示分開。
根據您的例子,簡單的東西可以輕鬆地委派專人負責到專科班,從而清除控制器的邏輯。
class FooController < ApplicationController
def create
handler = FooHandler.new(params[:foos])
foo = handler.process_foo
if foo[:result]
flash[:success] = 'Foo was successful'
redirect_to foo_path
else
flash[:error] = foo[:errors]
redirect_to foo_path
end
end
end
class FooHandler
delegate :valid_checksum?, to: :checksum_klass
delegate :create_foos, to: :foo_klass
delegate :call_foo_api, to: :foo_api_klass
def initialize(params)
@params = params
end
def process_foo
return {result: false, errors: 'failed checksum'} unless valiid_checksum?
return {result: false, errors: 'failed to create the foos' unless create_foos
return {result: false, errors: 'api errors'} unless call_foo_api
{result: true}
end
private
attr_accessor :params
def checksum_klass
@checksum_klass ||= ChecksumChecker.new(params[:checksum])
end
def checksum_klass
@foo_klass ||= FooCreator.new(params[:foo_objects])
end
def checksum_klass
@foo_api_klass ||= FooApiHandler.new(params[:foo_objects])
end
end
實施上述風格是你如何可以開始每一道工序分解到自己的類,它通過單一FooHandler類運行,從控制器去耦所有的邏輯的例子。在這個例子中,所有的控制器都關心的是這個過程是否成功。
我的例子的工作原理是每個動作都包含在自己的類中,因此FooHandler可以在需要時將責任委託給該類,傳遞一些數據並清洗任何責任。它所關心的只是結果。
這一切歸結爲你對圖案的理解。我使用的兩個最有用的模式(除了Rubys鴨子打字和授權)是觀察者模式和裝飾者模式。
觀察者模式將允許你相依過程與可觀察類的狀態相關聯。因此,對於您的示例,如果API調用依賴於正在創建的對象,請嘗試設置將FooApiHandler作爲訂閱偵聽器的FooCreationHandler。如果處理程序成功創建記錄,則可以通知FooApiHandler可以調用外部API,從而將依賴關係解耦。
像裝飾者這樣的模式可以讓你在需要時用特定的行爲「裝飾」對象,而不是有一個大的,複雜的if語句和其他類的「知識」。再次,這是一個很好的模式,允許您創建具有抽象行爲的專業類,而不是包含一個類或控制器的邏輯。
希望這個簡短的例子有所幫助。
這是一個好的做法呢?
沒有。助手僅適用於視圖特定的內容。貨幣格式化,日期格式化,包裝東西在一個特定的<div>
,那種東西。默認情況下,它們甚至在控制器中不可用。你必須明確地要求他們。
組織多個運動部件的合作不應該是幫手。是的,控制器也不是一個好地方。我,我通常把這種邏輯放在一個ServiceObject或其他東西。所以我的控制器通常是這樣的:
class ProjectsController < AppicationController
def create
# this will create the project, create associated objects, push notifications to
# whatever needs to be notified, etc.
::ServiceObjects::Project::Create.new(project_params)
# render something
end
...
end
這樣,你的控制器仍然很瘦。作爲額外的獎勵,服務對象更容易測試。