AWDR 阅读笔记 – 5.Code – Task D

Agile Web Development with Rails 5.1 阅读笔记系列第 5 篇,继续上一篇对在线书店进行迭代,增加购物车,参考本书该部分(CH9 – Task D)。

Generating the Cart

Scaffold

$ bin/rails generate scaffold Cart

Migrate

$ bin/rails db:migrate

the Module – CurrentCart

concerns 可用于放置 modules,modules 可以嵌入到其他 class 里。

此处证明私有方法 set_cart,创建购物车并使用 session 保存 card_id,并用于添加购物车时的回调。

app/controllers/concerns/current_cart.rb

module CurrentCart

  private

    def set_cart
      @cart = Cart.find(session[:cart_id])
    rescue ActiveRecord::RecordNotFound
      @cart = Cart.create
      session[:cart_id] = @cart.id
    end
end

Connecting Products to Carts

生成 LineItem 脚手架以用于建立两表关系。

LineItem Scaffold

LineItem 的数据库作为关系表,为 carts 和 products 建立多对多关系。其中的两个字段:

  1. product;
  2. cart。

均为外键,即分别指向 product 和 carts 表。

$ bin/rails generate scaffold LineItem product:references cart:belongs_to

LineItem Migrate

$ bin/rails db:migrate

Specify the Inverse Relations

Cart

指明 Cart 与 LineItem 的一对多关系,和外键约束策略,即删除时删除。

app/models/cart.rb

class Cart < ApplicationRecord
  has_many :line_items, dependent: :destroy
end

Product

类似地,对 Product 进行相同一对多处理,并完善删除检测。

class Product < ApplicationRecord
  has_many :line_items

  before_destroy :ensure_not_referenced_by_any_line_item

  # ...

  private

    # ensure that there are no line items referencing this product
    def ensure_not_referenced_by_any_line_item
      unless line_items.empty?
        errors.add(:base, 'Line Items present')
        throw :abort
      end
    end
end

Product Delete Test

测试 Product 中的 ensure_not_referenced_by_any_line_item

test/controllers/products_controller_test.rb

test "can't delete product in cart" do
  assert_difference('Product.count', 0) do
    delete product_url products(:two)
  end
  assert_redirected_to products_url
end

测试之前,把 test/fixtures/line_items.yml 中的两个测试数据 product_id 均改为 one 或 two,即测试书籍 2 – MyString 分别在购物车 1 和 2 中。

Add a Button

the “Add to Cart" Button

添加一个“添加到购物车”按钮。

UI

在主页,即 Catalog 里,紧邻 price 添加该按钮。

app/views/store/index.html.erb

<%= number_to_currency product.price %>
<%= button_to 'Add to Cart', line_items_path(product_id: product) %>

Style

scss 亦是如此,相邻 .price 的地方添加一个绿色大按钮样式。

app/assets/stylesheets/store.scss

form, div {
  display: inline;
}
input[type="submit"] {
  background-color: #282;
  border-radius: .354em;
  border: solid thin #141;
  color: white;
  font-size: 1em;
  padding: .354em 1em;
}
input[type="submit"]:hover {
  background-color: #141;
  cursor: pointer;
}

Addition Session

Setting Cart

在 Rails 自动生成的 line_item 控制器中,将我们最初定义的回调 set_cart 添加到该控制器的 create 前,保证购物车存在且能从 session 识别。

app/controllers/line_items_controller.rb

class LineItemsController < ApplicationController
  include CurrentCart
  before_action :set_cart, only: [:create]

  # ...
end

Building LineItem & Redirection

此时,当向购物车添加项目时,我们可以确保购物车存在(set_cart)。同时,由于 product 和 cart 的多对多关系是由 line_items 建立的,对于一个用户,往往是一个购物车对应多个产品,所以将 create 方法中默认由脚手架生成的 line_item 删除,我们手动通过 cart.line_item,与 product 关联地创建 line_item。

创建完成后重定向到当前购物车,而不是默认的 line_item。

# POST /line_items
# POST /line_items.json
def create
  product = Product.find(params[:product_id])
  # @line_item = LineItem.new(line_item_params)
  @line_item = @cart.line_items.build(product: product)

  respond_to do |format|
    if @line_item.save
      format.html { redirect_to @line_item.cart, notice: 'Line item was successfully created.' } 

      # ...
    end
  end
end

After Addition

UI

添加完毕后展示购物车里的项目,这里使用简单 list 现实。

app/views/carts/show.html.erb

<% if notice %>
  <aside id="notice"><%= notice %></aside>
<% end %>

<h2>Your Pragmatic Cart</h2>
<ul>
  <% @cart.line_items.each do |item| %>
    <li><%= item.product.title %></li>
  <% end %>
</ul>

the Notice Style

这里的 notice 样式为全局样式,设置为淡黄色背景,圆弧边框(.5em)。

app/assets/stylesheets/application.scss

.notice, #notice {
  background: #ffb;
  border-radius: .5em;
  border: solid .177em #882;
  color: #882;
  font-weight: bold;
  margin-bottom: 1em;
  padding: 1em 1.414em;
  text-align: center;
}

Updating the LineItem Test

由于我们更改了由脚手架默认生成的 LineItem#create,所以测试模块也需要更新,否则无法通过测试。

这里,我们更新 create 方法参数,同时指明跟随重定向到 cart。

test/controllers/line_items_controller_test.rb

test "should create line_item" do
  assert_difference('LineItem.count') do
    post line_items_url, params: { product_id: products(:ruby).id }
  end
  follow_redirect!

  assert_select 'h2', 'Your Pragmatic Cart'
  assert_select 'li', 'Programming Ruby 1.9'
end

Comfirm the Test Passed

$ bin/rails test test/controllers/line_items_controller_test.rb

参考:

  1. Agile Web Development with Rails 5.1 . Sam Ruby. David Bryant Copeland. with Dave Thomas. 2017 The Pragmatic Programmers, LLC.
  2. 完整源码见:https://media.pragprog.com/titles/rails51/code/rails51/depot_f/*

作者: V

Web Dev

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s