【问题标题】:How to Refactor Controller Code To Model Code如何将控制器代码重构为模型代码
【发布时间】:2015-01-06 06:36:27
【问题描述】:

我已经使用 Rails 有一段时间了,我的控制器代码开始失控(不是双关语。)我听说你想要瘦控制器和胖模型,对我来说这不是自然而然的在我的 Rails 进程中。

我将发布我的 rails 应用程序的一个模型。

line_item #create

def create
  @cart = current_cart
  #product is built from base_product, after finding associated product
  @base_product_id = params[:line_item][:base_product_id]
  get_product_from_base_product
  @line_item = @cart.line_items.build(
    :product_id => @product_id,
    :order_id => nil,
    :weight => params[:line_item][:weight],
    :quantity => params[:line_item][:quantity]
    )
  ## Does a line item with the same product_id already exist in cart?
  if @line_item.exists_in_collect?(current_cart.line_items)
    #if so, change quantity, check if there's enough stock
      if  current_cart.where_line_item_with(@product_id).update_quantity(@line_item.quantity) == true
        @line_item.destroy
        redirect_to base_products_path
        flash[:success] = "Detected Producted In Cart,  Added #{@line_item.quantity} More to Quantity"
      else 
        redirect_to cart_path
        flash[:failure] = "Cannot Add To Cart, We Only Have #{@line_item.product.stock_qty} In Stock"
      end
  else
    if @line_item.stock_check == true
      if @line_item.save
        respond_to do |format|
        format.html { redirect_to base_products_path, 
          :notice => "(#{@line_item.quantity}) #{@line_item.product.base_product.title} Added to Cart." }
        format.xml  { render :xml => @line_item,
          :status => :created, :location => @line_item }
        end
      else
        format.xml  { render :xml => @line_item.errors,
          :status => :unprocessable_entity }
      end
    else
      redirect_to base_products_path
      if @line_item.product.stock_qty > 0
      flash[:failure] = "Sorry! We Only Have #{@line_item.product.stock_qty} In Stock"
      else
      flash[:failure] = "Sorry! That Item Is Out Stock"
      end
    end 
  end
end

控制器方法:

private 
def get_product_from_base_product
  @product_id = Product.where(:base_product_id => @base_product_id).where(:size_id => params[:line_item][:size]).first.id
end

LineItem 模型

class LineItem < ActiveRecord::Base
  belongs_to :product
  belongs_to :cart
  after_create :set_order_weight#, :set_package_dimentions
  after_update :set_order_weight#, :set_package_dimentions
  #max capactiy here
def have_enough_product?
  if self.product.stock_qty > self.quantity
    return true
  else
    self.quantity = self.quantity_was
    self.save
    return false
  end
end
def over_order_cap?
    if self.quantity > 50
        return true
    end
end
def update_quantity(qty)
  if self.have_enough_product? == true
  self.quantity += qty
  self.save
  end
end

def stock_check
  if self.product.stock_qty > self.quantity == true
    return true
  else
    return false
  end
end
def exists_in_collect?(items)
  items.each do |item|
    return true if self.product_id == item.product_id
  end
  return false
end

如您所见,这是一个有点大的控制器。这是正常的吗?所有的逻辑都在那里发生。用户可能已经在购物车中有这个项目,这个项目可能没有库存,这个项目有一个容量并且用户试图订购更多。更何况我

我知道这不是一个典型的堆栈问题。没有什么是坏的,但我只是对我的代码感觉不好。

我不想提出“家庭作业”问题,但我会很感激有关如何使用模型重构控制器代码的建议。我会很感激参考和建议。

谢谢! 更新添加了购物车.rb

 class Cart < ActiveRecord::Base
   has_many :line_items#, dependent: :destroy
   has_one :order
     def where_line_item_with(prod_id)
       line_items.where(:product_id => prod_id).first
     end
     def sub_total
       self.line_items.to_a.sum { |item| item.total_price }
     end 
 end

【问题讨论】:

  • 他们说,“Rails 控制器是每个 Rails 应用程序的汗水。”但是我确实觉得您的创建操作没有遵循单一责任原则,它担心创建行项目,还担心购物车的内容。有推车模型吗?做不需要持久化的模型是可以的。
  • 我认为这是一个很好的问题。
  • 那么 create 方法负责创建 line_items。但它还需要确保 line_item 不存在,并且数量可用。我是否只允许在保存后创建和组合相同的 line_items?
  • 啊,这很有帮助。 Cart 是一个容器对象。我推荐使用容器模式。 Cart 应该有 addLineItem() 方法,检查发生在哪里,以及调用 create(委托责任)。 find_or_create 和 find_or_create_by 活动记录方法也可能有助于防止重复。对数量是多少、客户想要的数量或您的库存数量有点困惑?购物车对象应该只管理其中的 1 个。我会创建一个 Store 对象来管理库存。购物车向商店询问有效数量; Store 是另一种容器模式。
  • 2 旁注,你可以使用 where_line_item_with 范围,范围::name, -> (prod_id) {where("product_id == ?", prod_id)} api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/… 并且你可以使用 active记录回调(after_save/before_save)做检查api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

标签: ruby-on-rails ruby refactoring


【解决方案1】:

我会回答不是因为我是专家,而是因为我自己最近才经历过这个。对我来说,当我开始将我的应用程序置于测试套件中时,我才意识到。我很难测试我的控制器动作,因为它们做了很多不同的事情。

以下是我认为您应该将控制器的逻辑移动并分离到模型方法中的几个原因:

  • 代码重用。我发现,与其在不同的控制器中编写相同的代码 3 或 4 次不同的时间,我可以在模型中编写一次(并测试一次!)并在我的控制器中调用这些方法。

  • 测试。无需在 3 或 4 个控制器操作中测试相同的逻辑,您可以在模型规范中对其进行一次测试(在那里也更容易测试)。它不仅节省了您编写测试的时间,而且消除了您在控制器之间以不同方式实现应该是相同逻辑的事情的可能性。

  • 可读性。老实说,我没有通读您的整个create 操作,因为它的可读性不高。我有几个类似的操作,我讨厌尝试调试它们,因为很难跟踪正在发生的事情。将该逻辑移动到模型中(并将其分离为更小、更具体的方法),让您可以专注于控制器应该做的事情(身份验证、指导、处理参数等)。

那么,如何重构呢?我在一个 SO question(现在找不到)中读到了一些内容,基本上说:如果逻辑处理资源关联的方式,它应该在模型中。我认为这是一个很好的一般规则。我会从那里开始。如果您没有测试套件,我会同时添加。

【讨论】:

  • “处理资源关联方式”是什么意思?例如,如果控制器中的操作处理其他与活动记录相关的模型? (比如购物车和 line_item?)
  • 如果你的动作中的逻辑是基于数据的关系,它应该在模型中。这是一种白名单方法。另一种方法是我在第三点中提到的:如果它不处理身份验证或指导传入/传出请求,它应该在模型中。
  • 我也同意 settheline。在为模型编写测试时,您需要测试验证。验证充当有效或无效对象的看门人。您的关联可以帮助执行这些规则。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多