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

时间:2013-06-03 23:43:06

标签: ruby-on-rails ruby design-patterns model-view-controller model

假设我有一个Product model和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 

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

现在的问题是:所有的善良都应该转移到模型中吗?我正在处理应该在控制器中的业务逻辑,但是试着遵循经验法则Fat Models&amp;瘦控制器,在我看来它应该被移走,如果是这样那么还有什么可以移动?

奖金问: 我正在处理用例,我可能需要在代码中使用某些功能,而不是通过REST接口。 I.E.我需要在运行rake任务时使用check_stock_using_legacy_identifier_and_create_a_unique_po_number_and_place_an_order。就像基于低库存或电子邮件事件等生成一些订单一样。虽然我可以使用此处描述的选项:How do I call controller/view methods from the console in Rails?,将此操作作为模型的一部分会使它变得更容易吗?

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

3 个答案:

答案 0 :(得分:4)

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

服务对象可以帮助您减少重复并隔离您的业务逻辑,而不会让模型过于介入它不需要知道的事情(例如,重复的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 Patterns to Refactor Fat ActiveRecord Models。我发现它非常有帮助。服务对象上还有一个RailsCasts episode(需要专业订阅)。

答案 1 :(得分:2)

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

答案 2 :(得分:0)

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

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方法移动到名称空间,如:

<url>/api/ etc

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