0

私は多くのラインアイテムを含む次のカートモデルを持っています。これには、新しいラインアイテムを追加してその数量を増やすメソッドが1つだけ含まれています。

class Cart < ActiveRecord::Base
  has_many :line_items, dependent: :destroy
  validate :cart_total_price_cannot_be_greater_than_500

  def add_product(product_id)
    current_item = line_items.find_by_product_id(product_id)

    # Updates quantity or add a new line item
    if current_item
      current_item.quantity += 1
    else
      current_item = line_items.build(product_id: product_id)
              current_item.quantity = 1
      product = Product.find(product_id)
      current_item.product_price = product.price
    end

    current_item
  end

  def cart_total_price
    line_items.to_a.sum { |item| item.product_price * item.quantity }
  end

  def cart_total_price_cannot_be_greater_than_500
    if cart_total_price > 500 
      errors.add(:base, "has a too high price") 
    end
  end
end

ラインアイテムモデルは次のとおりです。

class LineItem < ActiveRecord::Base
  belongs_to :product
  belongs_to :cart

  def total_price
    product.price * quantity
  end
end

次のテストは正しく機能していました。

require 'test_helper'

class CartTest < ActiveSupport::TestCase
  fixtures :products

  test "add duplicated products to cart" do
    cart = Cart.create

    # :ruby is a product
    cart.add_product(products(:ruby).id).save
    cart.add_product(products(:ruby).id).save

    assert_equal 1, cart.line_items.size
    assert_equal 2, cart.line_items.first.quantity
  end
end

3行目を追加するまで、すべてうまくいきましたvalidate :cart_total_price_cannot_be_greater_than_500。これは私のテストを破っています、そして私はから次のエラーを受け取りますrake test

Finished tests in 0.294143s, 23.7979 tests/s, 84.9927 assertions/s.

  1) Failure:
test_add_duplicated_products_to_cart(CartTest) [/home/luca/Documents/Sites/depot/test/unit/cart_test.rb:14]:
<2> expected but was
<1>.

私が間違っていることは何ですか?validateメソッドをコメントアウトすると、テストは正しく合格します。

PS私の2番目の質問は、cart_total_priceメソッドでsumを呼び出す前に「to_a」メソッドを追加しないと機能しないのはなぜですか?

ありがとう!

編集: 2番目の問題について、to_aメソッドは合計を実行せずにデータベースをクエリしていませんか?サーバー側ではなく、データベースで計算を実行したいと思います。私は.NETからRailsを学んでおり、LINQでは次のものを使用できました。

int sum = dbContext.LineItems.Where(l => l.CartId == cartId).Sum(l => l.Quantity * l.ProductPrice)
4

1 に答える 1

1

これは少し複雑です。まず、検証なしの場合を考えます。

あなたが呼んでいるline_items.find_by_product_idline_items.build。これにより、実際にはline_itemsの関連付けが読み込まれることはないため、テストの最後の行で、cart.line_items.first数量== 2のデータベースから広告申込情報が新しく読み込まれるように要求すると、

2番目のケースでは、検証(Cart.createが呼び出されたときに実行されます)により、レールはデータベースから関連付けをロードしようとします(この時点では空です)。line_itemをビルドすると、このビルドされたオブジェクトがロードされた関連付けのキャッシュに追加されます(数量== 1)

次に、製品をもう一度追加します。line_items.find_by_product_iddbから製品をフェッチします。activerecordにはIDマップがないため、これは実際にはキャッシュに保持されているラインアイテムとは別のrubyオブジェクトです(同じデータベースオブジェクトを参照している場合でも)。その(現在は古くなっている)オブジェクトの数量はまだ1ですが、データベースの行の数量は2です。

railsを要求するcart.line_items.firstと、その関連付けがすでにロードされていることがわかり、古い数量値を持つadd_productへの最初の呼び出しからキャッシュされたラインアイテムオブジェクトが返されます。したがって、アサーションは失敗します。

製品を追加した後に電話をかけることで、スペックに合格することができますcart.reload

2番目の質問に答えるのは、to_a結果としてArray#sumが呼び出されるのに対し、それがないと、異なる引数のセットを必要とするラインアイテムに対してSQL合計を実行する必要があるためです。

于 2012-05-24T22:35:12.910 に答える