在轨道中处理多个推车

时间:2014-08-18 16:19:20

标签: ruby-on-rails ruby-on-rails-4

我想知道我的问题是否有更短的解决方案。

我目前正在建立一个多店铺系统,这意味着一个拥有不同商店的网站,每个商店都有一个开放式购物车。

此购物车可以来自访客或用户。

只有在添加项目时才能创建购物车/订单。

以下定义放在应用程序控制器中。

 def find_order_by_shop(shop)
   shop = Shop.find(shop.to_i) if !(shop.class == Shop)
   if session["order_id_for_shop_#{shop.try(:id)}"]
     order = Order.find_by_id_and_state(session["order_id_for_shop_#{shop.id}"],'open')
     if order
       if current_user
         current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => session["order_id_for_shop_#{shop.id}"]).delete_all
         order.update_attribute(:user_id, current_user.id)
         order = current_user.orders.where(:shop_id => shop.id , :state => 'open').first
         if order
           # delete all exept first
           current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => order.id).delete_all
         else
           # create new
           order = current_user.orders.new(:shop_id => shop.id , :state => 'open')
         end
       end
     else
       order = Order.new(:shop_id => shop.id, :state => 'open')
     end
   else
     if current_user
        order = current_user.orders.where(:shop_id => shop.id , :state => 'open').first
        if order
          current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => order.id).delete_all
        else
          order = current_user.orders.new(:shop_id => shop.id , :state => 'open')
        end
      else
        # guest
        order = Order.new(:shop_id => shop.id , :state => 'open')
      end
   end

   session["order_id_for_shop_#{shop.try(:id)}"] = order.id
   return order
 end

在将商品添加到购物车/订单时更新会话

 ...
   def create # add item to order
     @order = find_order_by_shop(params[:shop_id].to_i)
     @order.save # save cart
     session["order_id_for_shop_#{params[:shop_id]}"] = @order.id
 ...

这似乎不是正确的轨道方式。

有什么建议吗?

1 个答案:

答案 0 :(得分:1)

我不知道'更短',但只是看一下你的代码,我会提出一些建议。

您所做的每项更改都会测试代码。希望你有单元和功能测试,如果没有,那么在浏览器中检查预期的行为。

尝试将您的逻辑划分为合理,小型,紧凑的单位,并使用合理的名称。把它们变成方法。当您这样做时,您可能会发现一些可以优化的问题或点。例如:

if order
  if current_user
    current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => session["order_id_for_shop_#{shop.id}"]).delete_all
    order.update_attribute(:user_id, current_user.id)
    order = current_user.orders.where(:shop_id => shop.id , :state => 'open').first
    if order
      # delete all exept first
      current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => order.id).delete_all
    else
      # create new
      order = current_user.orders.new(:shop_id => shop.id , :state => 'open')
    end
  end
else
  order = Order.new(:shop_id => shop.id, :state => 'open')
end

由于围绕整个块有一个'if order'条件,内部'if order'是多余的,整个'else'分支可以删除:

if order
  if current_user
    current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => session["order_id_for_shop_#{shop.id}"]).delete_all
    order.update_attribute(:user_id, current_user.id)
    order = current_user.orders.where(:shop_id => shop.id , :state => 'open').first
    # delete all except first
    current_user.orders.where(:shop_id => shop.id , :state => 'open').where.not(:id => order.id).delete_all
  end
else
  order = Order.new(:shop_id => shop.id, :state => 'open')
end

现在,您可以将功能拆分为可重复使用的小块:

def order_from_shop(shop_id)
  Order.new(:shop_id => shop_id, :state => 'open')
end

查看代码,您至少可以看到两个可以使用此方法的地方。

请注意,没有return语句。 Ruby / Rails'方式'是允许自动返回 - 在返回方法的最后一个语句的结果而不显式声明它。您可以将其应用于主方法的末尾:

  ...
  session["order_id_for_shop_#{shop.try(:id)}"] = order.id
  order
end

回到其余代码,开始提取更多方法,例如:

def user_order_from_shop(shop_id)
  current_user.orders.where(:shop_id => shop.id , :state => 'open').first
end

也有一些地方可以使用它。

以小方法封装您的if语句,格式为:

def method
  if xxx
    a_method
  else
    a_different_method
  end
end

根据Sandi Metz的说法,任何方法都不应超过5行。你不必那么极端,但这样做很有用。

最终你会看到更像英语的东西,所以更容易阅读和确定一个glace的行为。那时你可能会注意到更多的重复或不必要的,无法访问的代码。对两者都无情。

最后,整个事情看起来需要它自己的类。

# app/services/shop_order_query.rb
class ShopOrderQuery
  attr_accessor :shop, :order, :current_user

  def initialize(shop, order, current_user)
    self.shop = shop
    self.order = order
    self.current_user = current_user
  end

  def find_order_by_shop
    ...
    ...
  end

  private

  # support methods for main look-up

  def order_from_shop(shop_id)
    ...
  end
  ...
  ...
end

然后用

调用它
ShopOrderQuery.new(shop, order, current_user).find_order_by_shop

然后它完全隐藏起来,并且可以在任何可以通过商店,订单和当前用户的地方使用......并且它不会使控制器混乱。

如需进一步阅读,请查看有关制作瘦控制器,提取服务对象和Sandi Metz规则的博客文章。另外,购买Sandi关于Ruby OO编程的书。