我应该将自定义方法从控制器移动到模型吗?

假设我有一个Product model和ProductsController。 控制器具有所有标准的CRUD方法,产品可以进行各种validation等。

这是一个问题。 我有几个自定义非常复杂的动作,也需要以多种格式(json,html,xml,csv,pdf等)进行响应。 业务逻辑原因超出了问题的范围。 让我们这样做是必须的。 我也使用了InheritedResources gem,但我不认为这个问题很重要。

例如(这是一个模拟应用程序,它非常简化 – 我删除了各种if else语句和循环和本地化等):

class ProductController  5 @po = LegacyOrder.create_po if @po if @order.save format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: {success: "Wow! Input was good!"}} format.json{ render status: 400, json: {status: :success, message: "Order created"}} else format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: {error: "Can't create order, some validations failed"}} format.json{ render status: 400, json: {status: :error, message: "Problem with order", errors: @order.errors}} end else format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: {error: "Can't create order, PO number wasn't generated"}} format.json{ render status: 400, json: {status: :error, message: "Problem with po", errors: @po.errors}} end else respond_to do |format| format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: {error: "Can't create order, stock is low"}} format.json{ render status: 400, json: {status: :error, message: "Problem with product", errors: @product.errors}} end end end .... end 

这只是为了解一些行为的复杂性。

现在的问题是:所有这些善良都应该转移到模型中吗? 我正在处理应该在控制器中的业务逻辑,但是在尝试遵守经验法则Fat Models&Thin Controllers时,在我看来它应该被移走,如果是的话那么剩下要移动的东西了?

奖金问:我正在使用我可能需要在代码中使用某些function的用例,而不是通过REST接口。 IE我需要在运行rake任务时使用check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order。 就像基于低库存或电子邮件事件等生成一些订单一样。虽然我可以使用此处描述的选项: 如何在Rails中从控制台调用控制器/视图方法? ,将此操作作为模型的一部分会使它变得更容易吗?

那么在这样的案例中,Rails最佳实践行动是什么?

考虑将您的逻辑移动到服务对象中。 我认为将控制器逻辑推入模型只是将问题移到另一个位置。 是的,您确实将逻辑隔离到单个区域,但是有些情况下您最终会因为惯例而将逻辑移动到模型而不是它真正属于那里的事实。

服务对象可以帮助您减少重复并隔离您的业务逻辑,而不会让模型过于涉及它不需要知道的事情(例如,您重复的json响应)。

 class OrderService def initialize(legacy_alphanumeric_product_number) # do stuff end # do more stuff end 

从控制器,你可以打电话

 def check_whatever @order = OrderService.new(params[:some_product_code]) @order.check_something # do more stuff end 

看一下7种模式来重构Fat ActiveRecord模型 。 我发现它非常有帮助。 服务对象上还有一个RailsCasts剧集 (需要专业版订阅)。

看看Draper gem( https://github.com/drapergem/draper )。 它提供了很好的装饰风格包装器,是将逻辑放在要显示或响应的内容上的理想场所。 我同意@mohamad它不属于模型或控制器。 服务对象绝对是一种好方法,或使用Draper创建特定于表示的逻辑方法。

正如我所看到的,你的问题不是将代码从控制器转移到模型,而是重新分解控制器方法

 check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order 

举个例子

 format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: {success: "Wow! Input was good!"}} format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: {error: "Can't create order, stock is low"}} 

是相同的,除了他们的flash消息。 所以你可能只生成flash消息,例如:

 #conditions for the flash message format.html{ render :check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order, flash: msg} 

所以尝试重新考虑你的代码,我个人不会把xml,json放在同一个控制器中。 因为像check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order这样的web api方法对我来说不太好看:)

我会将所有web api方法移动到名称空间,如:

 /api/ etc 

如果你有一个很好的测试覆盖率,你可以继续并重新考虑代码,如果不是现在你知道拥有一个好的测试套件的优势:)