購物車實作思路

購物車實作思路:

[TOC]

1.建立將商品加入到購物車的action

(1)在商品頁面新建“加入購物車”按鈕
(2)設定product的routes

resources :products do
member do
post :add_to_cart
end
end

(3)在products_controller中加入add_to_cart的action

def add_to_cart
@product = Product.find(params[:id])
flash[:notice] = "已將商品加入到購物車"
redirect_to :back
end

功能:上述步驟完成后舍扰,在商品詳情頁面断医,點擊“加入購物車”會提示“商品已加入購物車”哪亿,但還沒有指定商品加入到了哪臺購物車铃芦。

2.建立商品product和購物車cart之間的關(guān)系

(1)由于一種商品可以加入到不同的購物車中美澳,一個購物車也可以加入不同種商品奢人,因此商品和購物車之間是多對多關(guān)系蓬戚,這時候我們需要引入第三方model(購物欄)來建立商品product和購物車cart之間的關(guān)系
(2)新建購物車的model cart饺著,和購物欄的model cart_item
終端執(zhí)行:
rails g model cart
rails g model cart_item
打開cart_item的migration文件亚隙,在其中記錄商品product_id磁餐,購物車cart_id和購物欄自身數(shù)量quantity默認為1(商品對應購物欄的數(shù)量就是購物車中該商品的數(shù)量,通過修改購物欄自身數(shù)量來修改購物車中同一種商品的數(shù)量阿弃,進而計算購物車總價)
(3)建立購物車cart诊霹,購物欄cart_item,商品product之間的關(guān)系
在cart.rb中加入:

has_many :cart_items
has_many :products through: :cart_items, source: :product

注意上述代碼中products是購物車已加入的商品渣淳,后面的source: :product中的product是所有商品脾还,如果怕搞混了,可以將前面的products修改為其他任意合法的變量入愧,比如goods
在cart_item.rb中加入:

belongs_to :cart
belongs_to :product

這樣就可以通過購物車cart就可以通過購物欄cart_item找到對應的商品product
這里我們不在product.rb中加入對稱的關(guān)系鄙漏,因為我們不需要通過商品good來找對應的購物車cart。我們采用rails中基于cookie的session機制來分配給用戶購物車棺蛛,并追蹤用戶的購物車泥张,以防購物車錯亂。
(4)為當前在操作的用戶分配購物車
在全局的application_controller中加入代碼

helper_method :current_cart
def current_cart
@current_cart ||= find_cart
end

也加入代碼:

private

def find_cart
cart = Cart.find_by(id: session[:cart_id])
if cart.blank?
cart = Cart.create
end
session[:cart_id] = cart.id
return cart
end

之所以要寫在application_controller中是因為我們希望無論進入哪個頁面購物車的信息都保留鞠值,比如current_cart這個方法會在購物車媚创,訂單頁面都會用到,確保用戶的購物車不會錯亂
如果用戶有購物車彤恶,就通過session檢測用戶用了哪臺購物車钞钙,以確保自己的商品不會加入到別人的購物車中
如果用戶的購物車丟失,就重新為用戶分配一個購物車声离,重新登記購物車的信息
由于接下來我們要計算購物車中有多少種商品芒炼,計算購物車商品總價,都需要捕捉到用戶的購物車术徊,就需要調(diào)用current_cart方法本刽,但在視圖view中想要controller中的方法,需要聲明該controller方法為helper_method赠涮,因此這里我們將current_cart聲明為helper_method

(5)前面已經(jīng)建立了購物車cart子寓,購物欄cart_item,商品product之間的關(guān)系笋除,這里通過新建購物欄cart_item斜友,存入對應的商品product的信息,以及指定商品的購物欄數(shù)量垃它,來將商品product存入到購物車的購物欄中鲜屏,通過獲得是哪臺購物車烹看,就可以知道商品被加入到哪個購物車了
在cart.rb中加入代碼:

def add_product_to_cart(product)
ci = cart_items.build #在購物車中新建購物欄
ci.product = product #記錄當前點擊的商品信息到購物欄中
ci.quantity = 1           #默認購物欄的數(shù)量是1(用來指代商品加入購物車后默認的數(shù)量為1,以便后面計算購物車商品總價)
ci.save
end

上述代碼中:

ci = cart_items.build #在購物車中新建購物欄

由于build和new互為別名方法(參考資料篇:build和new)洛史,因此也可以寫成:

ci = cart_items.new #在購物車中新建購物欄

但不能用

ci = CartItem.new

另外根據(jù)build和new互為別名方法惯殊,我猜測:

    ci = cart_items.build
    可以等價寫成
    ci = CartItem.new
    ci.cart = current_cart

但是model不能直接引用controller中的方法,因此這里我不知道怎么在model中告知cart_item也殖,它對應的購物車cart是那一臺靠胜,具體參見:[helper方法,model方法毕源,controller方法的運用](/Users/xyy/Documents/知識專題/ruby on rails/全棧營學習/學習總結(jié)/helper方法浪漠,model方法,controller方法的運用.md)

(6)將添加商品到購物車的方法加入product_controller中霎褐,這樣在商品頁面通過點擊“加入購物車”就可以將商品加入到購物車中址愿,并且記錄了的是加入到了當前購物車
在product_controller中加入代碼:

def add_to_cart
@product = Product.find(params[:id])
+ current_cart.add_product_to_cart(@product)
flash[:notice] = "已將商品加入到購物車"
redirect_to :back
end

這里我們也可以不用cart model中建立的add_product_to_cart方法作,而是把其中的代碼放到controller中冻璃,然后刪除掉add_product_to_cart方法响谓,需要略作修改:

def add_to_cart
@product = Product.find(params[:id])
- current_cart.add_product_to_cart(@product)
+ ci = current_cart.cart_items.build
+    ci.product = @product
+    ci.quantity =1
+    ci.save
    flash[:notice] = "已將商品加入到購物車"
redirect_to :back
end

這樣也是可行的,不過為了維護方便建議還是將代碼包裝成model方法省艳,然后在controller中調(diào)用
(7)購物車圖標顯示當前購物車中有多少種商品
在導航欄視圖中加入購物車圖標娘纷,并顯示商品一共有多少種(這時候還沒有做判斷,即便是同一種商品跋炕,在多次點擊加入到購物車也會分別作為不同件商品處理)

<%= link_to "#" do %>
購物車 <i class="fa fa-shopping-cart"> </i> (<%= current_cart.products.count %>)
<% end %>

3.實作購物車詳情頁

(1)新建購物車的控制器carts_controller
終端執(zhí)行:
rails g controller carts
這里在不在carts_controller中寫index action 都可以赖晶,因為我們要撈取的資料不是購物車,而是cart_item中的資料辐烂,所以沒有通過carts_controller的index action來操作model遏插,進而操作資料庫,就可以不用寫index action.
(2)設定購物車詳情頁的路由
修改routes纠修,在其中加入代碼:

+ resources :carts

(3)新建購物車詳情頁視圖carts/index.html.erb
在carts/index.html.erb中加入關(guān)鍵代碼:

<div class="row">
  <div class="col-md-12">

    <h2> 購物車 </h2>

    <table class="table table-bordered">
      <thead>
        <tr>
          <th colspan="2">商品資訊</th>
          <th>單價</th>
          <th>數(shù)量</th>
        </tr>
      </thead>
      <tbody>

        <% current_cart.cart_items.each do |cart_item| %>
          <tr>
            <td>
              <%= link_to product_path(cart_item.product) do %>
                <% if cart_item.product.image.present? %>
                  <%= image_tag(cart_item.product.image.thumb.url, class: "thumbnail") %>
                <% else %>
                  <%= image_tag("http://placehold.it/200x200&text=No Pic", class: "thumbnail") %>
                <% end %>
              <% end %>
            </td>
            <td>
              <%= link_to(cart_item.product.title, product_path(cart_item.product)) %>
            </td>
            <td>
              <%= cart_item.product.price %>
            </td>
            <td>
              <%= cart_item.quantity %>
            </td>
          </tr>
        <% end %>
      </tbody>
    </table>

    <br>
    <div class="total clearfix">
      <span class="pull-right">
         <span> 總計 xxx RMB  </span>
      </span>
    </div>

    <hr>
    <div class="checkout clearfix">
      <%= link_to("確認結(jié)賬", "#", method: :post, class: "btn btn-lg btn-danger pull-right") %>
    </div>
  </div>
</div>

如果商品有商品圖片胳嘲,則顯示對應的商品圖片,如果沒有則顯示默認的預設圖
點擊商品圖可以跳轉(zhuǎn)到商品詳情頁
顯示商品的價格扣草,和商品數(shù)量

(4)修改購物圖標對應的路徑了牛,從而可以通過點擊購物車圖標進入購物車詳情頁

- <%= link_to "#" do %>
+ <%= link_to carts_path do %>
購物車 <i class="fa fa-shopping-cart"> </i> (<%= current_cart.products.count %>)
<% end %>

4.計算購物車中的商品總價

(1)修改購物車詳情頁carts/index.html.erb中的代碼

- 總計 xxx RMB
+ <% sum= 0 %>
+ <% current_cart.cart_items.each do |cart_item| %>
+ <if cart_item.product.price.present? %>
+ <% sum += cart_item.product.price * cart_item.quantity %>
+ <% end %>
+ <% end %>
+ 總計<%= sum %> RMB

(2)上述計算總計的代碼寫在view中不易維護和閱讀,我們將其移動到carts_helper.rb中辰妙,就便于維護和閱讀了
在carts_helper中添加代碼:

def render_cart_total_price(cart)
    sum = 0
    cart.cart_items.each do |cart_item|
    if cart_item.product.price.present?
    sum += cart_item.product.price * cart_item.quantity
    end
    end
    sum
end

修改carts/index.html.erb中計算商品總價的代碼:

- 總計 xxx RMB
- <% sum= 0 %>
- <% current_cart.cart_items.each do |cart_item| %>
- <if cart_item.product.price.present? %>
- <% sum += cart_item.product.price * cart_item.quantity %>
- <% end %>
- <% end %>
+ 總計<%= render_cart_total_price(current_cart) %> RMB

(3)其實計算商品總價的任務不應該交給helper鹰祸,而應該交給model,因此我們可以進一步重構(gòu)代碼
修改carts_helper中的render_cart_total_price為:

def render_cart_total_price(cart)
cart.total_price
end

在cart.rb中加入代碼:

def total_price
sum = 0
cart_items.each do |cart_item|
if if cart_item.product.price.present? && cart_item.quantity.present?
sum += cart_item.product.price * cart_item.quantity
end
end
sum
end

5.一鍵清空購物車

目的是清除購物車中的商品上岗,而不是刪除購物車福荸,因此要新建清空功能的clean action蕴坪,而不是使用購物車的destory action
(1)修改routes

- resources :carts
+ resources :carts do
+ collection do
+ delete :clean
+ end
+ end

(2)在carts_controller中建立清空購物車的clean action

def clean
current_cart.clean!
flash[:warning] = "已清空購物車"
redirect_to :back
end

(3)在cart.rb中加入clean!方法肴掷,把操作數(shù)據(jù)庫的任務交給model

def clean!
cart_items.destroy_all
end

你也可以使用:

def clean!
products.destroy_all
end

這里的products是購物車cart通過購物欄cart_item中保存的商品敬锐,已在文章的步驟1有所介紹
當然,你也可以不用clean!方法呆瞻,直接在carts_controller中的clean action中寫成:

def clean
current_cart.cart_items.destroy_all
flash[:warning] = "已清空購物車"
redirect_to :back
end

不過讓model方法和controller方法分別執(zhí)行各自的任務比較好
但不能寫成:

def clean!
products.destroy_all
end
def clean
current_cart.clean!
flash[:warning] = "已清空購物車"
redirect_to :back
end

這樣會提示報錯台夺,找不到clean!方法(undefined method `clean!'))
想要正常運行代碼,需要略作修改:

def clean!(cart)
cart.cart_items.destroy_all
end
def clean
clean!(current_cart)
flash[:warning] = "已清空購物車"
redirect_to :back
end

注意:
這是因為current_cart.clean!方法用的是.方法痴脾,即從屬關(guān)系颤介,因此它可以引用model中的方法,但是不能引用controller方法赞赖,因為controller中的方法不是從屬關(guān)系滚朵。
所以,我們不能用.來引用方法前域,而是包裝成一般的方法辕近,通過傳入?yún)?shù)來調(diào)用controller方法
(4)在購物車詳情頁添加"清空購物車"的按鈕

<%= link_to("清空購物車",clean_carts_path,method: :post) %>

6.刪除購物車內(nèi)的某一商品

由于購物車中的商品是存儲在購物欄cart_item中,因此我們需要建立cart_item的控制器匿垄,設定cart_item的路由移宅,通過刪除商品對應的cart_item來完成刪除某一商品功能
(1)建立cart_item的控制器
終端執(zhí)行:
rails g controller cart_items

(2)設定cart_items的路由(建立刪除購物車中某一商品按鈕對應的路徑)

resources :cart_items 

(3)在cart_items的控制器中建立destroy action

before_action :authenticate_user!
def destroy
@cart = current_cart
@cart_item = @cart.cart_items.find_by(product_id: params[:id])
@product = @cart_item.product
@cart_item.destroy
flash[:warning] = "已將'#{@product.title}'從購物車中刪除 "
redirect_to :back
end

(4)在購物車詳情頁carts/index中加入刪除商品的按鈕

<%= link_to cart_item_path(cart_item.product_id), method: :delete do %>
 <i class="fa fa-trash"></i>
 <% end %>

這里也可以使用cart_item作為路徑參數(shù),不過需要將destroy action中的代碼修改為:

- @cart_item = @cart.cart_items.find_by(product_id: params[:id])
+ @cart_item = @cart.cart_items.find(params[:id])

補充:
關(guān)于使用cart_item自身id椿疗,還是cart_item對應的product_id來作為路徑參數(shù)漏峰,主要原因是routes路徑中的id和傳入的參數(shù)有關(guān),具體內(nèi)容參考我的這篇[購物車篇(5):刪除購物車中的某一件商品](/Users/xyy/Documents/知識專題/ruby on rails/全棧營學習/"購物網(wǎng)站"復習總結(jié)/購物車篇(5):刪除購物車中的某一件商品.md)

7.限制不能重復加入同種商品

前面已經(jīng)建立了購物車cart和商品product之間的關(guān)系了届榄,要想讓重復的商品不能再次加入浅乔,需要加一個判斷條件,即:檢查用戶的購物車中是否有該種商品铝条,如果沒有則加入童擎。
在products_contoller中的add_to_cart action中加入代碼:

def add_to_cart
@product = Product.find(params[:id])
+ if !current_cart.products.include?(@product)
current_cart.add_product_to_cart
- flash[:notice] = "成功加入購物車"
+ flash[:notice]="成功將 #{@product.title} 加入購物車""
else
+ flash[:warning] ="你的購物車內(nèi)已有此物品"
+ end
redirect_to :back
end

8.用戶可以更改購物車中的商品數(shù)量

(1)在cart_items的controller中加入update action,讓用戶可以修改商品數(shù)量

def update
@cart = current_cart
@cart_item = @cart.cart_items.find_by(product_id: params[:id])
@cart_item.update(cart_item_params)
redirect_to carts_path
end

private

def cart_item_params
params.require(:cart_item).permit(:quantity)
end

(2)在購物車詳情頁carts/index加入更新商品數(shù)量的按鈕

- <%= cart_item.quantity %>
+ <%= form_for cart_item, url: cart_item_path(cart_item.product_id) do |f| %>
+ <%= f.select :quantity, [1,2,3,4,5] %>
+ <%= f.submit "更新", data: { disable_with: "Submiting..." } %>
+ <% end %>

你可以將數(shù)組[1,2,3,4,5]該成range形式攻晒,如(1..5)

9.庫存為 0 的商品不能購買

要考慮兩點顾复,一是商品庫存為0,相應的商品頁面的"加入購物車"按鈕要被替換鲁捏;二是由于很多用戶通過購物車購買會對同一種商品的數(shù)量有影響芯砸,因此要在cart_item的controller種加入條件判斷:如果用戶在購物車種設定的購買商品的數(shù)量大于當前商品的庫存,則不能更新購買數(shù)量
(1)在商品詳情頁添加條件判斷给梅,如果商品庫存為0假丧,則提示"商品已售完"

+ <% if @product.quantity.present? && @product.quantity > 0 %>
<%= link_to("加入購物車", add_to_cart_product_path(@product), method: :post,
                    class: "btn btn-lg btn-danger") %>
+ <% else %>
+ 商品已售完
+ <% end %>

(2)修改cart_items_controller中的update action,加入條件判斷

def update
@cart = current_cart
@cart_item = @cart.cart_item.find_by(product_id: params[:id])
+ if @cart_item.product.quantity.present? && @cart_item.product.quantity >=cart_item_params[:quantity].to_i
@cart_item.update(cart_item_params)
+ flash[:notice] = "成功變更數(shù)量"
+ else
+ flash[:warning] = "數(shù)量不足以加入購物車"
+ end

redirect_to carts_path
end

  private
  
  def cart_item_params
    params.require(:cart_item).permit(:quantity)
  end

這里的 cart_item_params[:quantity]是從cart_item資料庫中獲取對應的數(shù)字,但它是數(shù)組型的监憎,因此我們需要用to_i將數(shù)字數(shù)組轉(zhuǎn)換成數(shù)字所计,才可以用比較運算符進行計算

10.在購物車新增數(shù)量時,不能更新超過原有庫存的數(shù)量

修改購物車詳情頁carts/index

<%= form_for cart_item, url: cart_item_path(cart_item.product_id) do |f| %>
-               <%= f.select :quantity, [1,2,3,4,5] %>
+               <%= f.select :quantity, 1..cart_item.product.quantity %>
                <%= f.submit "更新", data: { disable_with: "Submiting..." } %>
              <% end %>

其中1..cart_item.product.quantity也可以寫成(1..cart_item.product.quantity)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渴邦,一起剝皮案震驚了整個濱河市疯趟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谋梭,老刑警劉巖信峻,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓮床,居然都是意外死亡盹舞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門隘庄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踢步,“玉大人,你說我怎么就攤上這事丑掺〖炙洌” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵吼鱼,是天一觀的道長蓬豁。 經(jīng)常有香客問我,道長菇肃,這世上最難降的妖魔是什么地粪? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮琐谤,結(jié)果婚禮上蟆技,老公的妹妹穿的比我還像新娘。我一直安慰自己斗忌,他們只是感情好质礼,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著织阳,像睡著了一般眶蕉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唧躲,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天造挽,我揣著相機與錄音,去河邊找鬼弄痹。 笑死饭入,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的肛真。 我是一名探鬼主播谐丢,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乾忱?” 一聲冷哼從身側(cè)響起讥珍,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饭耳,沒想到半個月后串述,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體执解,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡寞肖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衰腌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片新蟆。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖右蕊,靈堂內(nèi)的尸體忽然破棺而出琼稻,到底是詐尸還是另有隱情,我是刑警寧澤饶囚,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布帕翻,位于F島的核電站,受9級特大地震影響萝风,放射性物質(zhì)發(fā)生泄漏嘀掸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一规惰、第九天 我趴在偏房一處隱蔽的房頂上張望睬塌。 院中可真熱鬧,春花似錦歇万、人聲如沸揩晴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硫兰。三九已至,卻和暖如春寒锚,著一層夾襖步出監(jiān)牢的瞬間瞄崇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工壕曼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苏研,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓腮郊,卻偏偏與公主長得像摹蘑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轧飞,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容