David Dacruz
David Dacruz

Reputation: 169

Iterating a collection and destroy only 1 instance Rails ActiveRecord::Associations

I'm learning ruby and rails building a shopping cart and I'm struggling trying to delete only one instance of Product with id of 3 from a cart that has a collection.

When i do current.user.cart.products.delete(product) it removes both instances from the shopping cart whats the magic trick to only remove one, I tried a few things but I think I'm not understanding something, maybe someone can help. Thanks ahead ! Sorry forgot to say hello to you ;)

how to remove sooty only once ?

current_user.cart.products.find_by(params[:id])

CACHE Product Load (0.0ms)  SELECT  "products".* FROM "products" INNER 
JOIN "carts_products" ON "products"."id" = 
"carts_products"."product_id" WHERE "carts_products"."cart_id" = ? AND 
(3) LIMIT ?  [["cart_id", 1], ["LIMIT", 1]]

#<Product id: 3, title: "Coco", description: "Voluptates ut dolor. Voluptas aperiam temporibus d...", price: 0.5278e2, created_at: "2018-03-07 16:59:42", updated_at: "2018-03-07 16:59:42">

current_user.cart.products

Product Load (0.6ms)  SELECT  "products".* FROM "products" INNER JOIN 
"carts_products" ON "products"."id" = "carts_products"."product_id" 
WHERE "carts_products"."cart_id" = ? LIMIT ?  [["cart_id", 1], 
["LIMIT", 11]]

#<ActiveRecord::Associations::CollectionProxy [#<Product id: 3, title: 
"Coco", description: "Voluptates ut dolor. Voluptas aperiam temporibus 
d...", price: 0.5278e2, created_at: "2018-03-07 16:59:42", updated_at: 
"2018-03-07 16:59:42">, #<Product id: 3, title: "Coco", 
description:"Voluptates ut dolor. Voluptas aperiam temporibus d...", 
price: 0.5278e2, created_at: "2018-03-07 16:59:42", updated_at: "2018-
03-07 16:59:42">, #<Product id: 4, title: "Oliver", description: 
"Quisquam corporis voluptatem sint ut atque veniam ...", price: 
0.6417e2, created_at: "2018-03-07 16:59:42", updated_at: "2018-03-07 
16:59:42">]>

This function deletes all products with the same product_id from the cart I would like to only destroy one

def remove_from_cart
  Product.find(params[:id]).destroy
  redirect_back fallback_location: root_path
end

Upvotes: 1

Views: 84

Answers (1)

max
max

Reputation: 102250

You need to setup an join table and intermediary model between Cart and Product:

class Cart < ApplicationRecord
  has_many :line_items
  has_many :products, through: :line_items
end

# Think lines on an order form.
# columns: 
#   - quantity [Integer] default: 1
#   - cart_id [Integer] foreign key, NOT NULL
#   - product_id [Integer] foreign key, NOT NULL
class LineItem < ApplicationRecord
  belongs_to :cart
  belongs_to :product
  validates_uniqueness_of :product_id, scope: :cart_id.
end

class Product
  has_many :line_items
  has_many :carts, through: :line_items
end

In your view you would display it like so:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Quantity</th>
      <th>Destroy</th>
    </tr>
  </thead>
  <tbody>
    <% @cart.line_items.each do |line_item| %>
    <tr>
      <td><%= line_item.product.name %></td>
      <td><%= line_item.quantity %></td>
      <td><%= link_to line_item, method: :delete %></td>
    </tr>
    <% end %>
  </tbody>
</table>

When adding an item to the cart you´re really just creating a line item. To remove an item you destroy the line item that links them:

class LineItemsController < ApplicationController

  before_action :set_cart, only: [:create, :destroy]

  # POST /line_items
  def create
     @product = Product.find(params[:line_item][:product_id])
     if @cart.products.include?(@product)
       @cart.line_items
            .find_by!(product: @product)
            .increment!(:quantity)
     else
       @cart.products << @product
     end

     redirect_to @cart
  end

  # DELETE /line_items/:id
  def destroy
    @line_item = LineItem.find(params[:id])
    @line_item.destroy
    redirect_to @cart
  end

  # ...

  def set_cart
    @cart = current_user.cart
  end
end

Upvotes: 1

Related Questions