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

kon*_*ung 5 ruby model-view-controller design-patterns model ruby-on-rails

假设我有一个 Product 模型和 ProductsController。控制器具有所有标准的 CRUD 方法,产品进行各种验证等。

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

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

class ProductController < InheritedResources::Base
  ....
    def check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order
      @order = Order.new
      @product = Product.find(params[:legacy_alphanumeric_product_number])
      if @product.stock > 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 
Run Code Online (Sandbox Code Playgroud)

这只是为了了解某些操作的复杂性。

现在的问题是:所有这些优点都应该转移到模型中吗?我正在处理应该在控制器中的业务逻辑,但是通过尝试遵循胖模型和瘦控制器的经验法则,在我看来应该将其移开,如果是这样,那么还有什么可以移动的?

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

那么在这种情况下,Rails 最佳实践的行动方针是什么?

Moh*_*mad 6

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

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

class OrderService
  def initialize(legacy_alphanumeric_product_number)
    # do stuff
  end
  # do more stuff
end
Run Code Online (Sandbox Code Playgroud)

从控制器,您可以调用

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

查看7 种重构 Fat ActiveRecord 模型的模式。我发现它很有帮助。还有一个关于服务对象的RailsCasts 插曲(需要专业订阅)。