《Agile Web Development with Rails》抄书笔记(10):创建购物车
《Agile Web Development with Rails》抄书笔记系列
“《Agile Web Development with Rails》抄书笔记系列”目录
上一章节,我们讲解了商品的展示。这一节,我们将向大家介绍购物车的创建。同时,这一节也将向大家展示如何在Rails中创建Session?如何建立Model直接的关联关系?
D呱呱
关于这节内容的代码:
创建购物车
有过上网购物经验的朋友都知道,当我们浏览一个商品列表时,会选择一些自己喜欢的东西,然后添加到虚拟的购物车中。购物车会保存用户的选择,选择完后,进一步地支付、购买所需的商品。
这就意味着我们要保存用户上网的添加记录。为此,我们需要在数据库中保存一个购物车,同时将购物车的ID,cart.id,保存到Session中。每次访问Depot时,我们都要从Session中获取cart.id,然后根据这个cart.id从数据库中查找出相对应的购物车。
首先,创建Cart,两条指令搞定:
rails generate scaffold cart # 输出略 rake db:migrate # 输出略
同时,向%Depot%/app/controllers/application_controller.rb中添加如下代码,实现购物车的创建或查找:
class ApplicationController < ActionController::Base protect_from_forgery private def current_cart Cart.find(session[:cart_id]) rescue ActiveRecord::RecordNotFound cart = Cart.create session[:cart_id] = cart.id cart end end
current_cart()方法会根据Session的cart_id从数据库中查找Cart;如果没有,则会创建一个,并且把Cart.id的值以cart_id为名称添加到Session中去。
建立Cart与Product的关联关系
购物车Cart已经有了;产品Product也有了。Cart中可以有若干个指向Product的指针(暂且这样称呼);一个Product也可以被Cart所包含。根据关系型数据库的理论,可以很容易看出,为了保存这种关联关系,我们还需要创建一个中间表,当然这里也对应一个”关联对象”。两条相关指令如下:
rails generate scaffold line_item product_id:integer cart_id:integer # 输出略 rake db:migrate # 输出略
现在,数据库层面已经建立起了关联关系;但是Rails层面的代码还没有关联起来,下面我们对其建立起关联关系。
首先打开%Depot%/app/models/cart.rb文件,对其添加如下代码:
class Cart < ActiveRecord::Base # attr_accessible :title, :body has_many :line_items, dependent: :destroy end
has_many()方法的意义很明确:Cart中有多个关联的line items。
dependent::destroy这段代码是说明,line items的生命周期依赖于Cart,如果Cart被消亡,则line items也跟随一起消亡。
下面,我们建立起从line item到Cart,以及到Product的关联关系。打开文件%Depot%/app/models/line_item.rb,在里面添加如下代码:
class LineItem < ActiveRecord::Base attr_accessible :cart_id, :product_id belongs_to :product belongs_to :cart end
belongs_to()方法是向Rails说明,在line_items表中的数据,是关联carts表和products表的。另外,从这里也可以很容易看出,belongs_to()的用处,哪个表有外键,则就需要在相应的Model中添加belongs_to()方法。
在Product中,我们也需要建立起对line_items的关联关系。另外,由于line_items表对products有外键关系。所以,删除products表中的记录时,要先检查一下line_items中是否有关联的记录,没有可以删除;有则不能删除。具体代码如下:
class Product < ActiveRecord::Base attr_accessible :description, :image_url, :price, :title validates :description, :image_url, :title, presence: true validates :price, numericality: {greater_than_or_equal_to: 0.01} validates :title, uniqueness: true validates :image_url, allow_blank: true, format: { with: %r{\.(gif|jpg|png)$}i, message: 'must be a URL for GIF, JPG or PNG image.' } has_many :line_items before_destroy :ensure_not_referenced_by_any_line_item private def ensure_not_referenced_by_any_line_item if line_items.empty? return true else errors.add(:base, 'Line Items present') return false end end end
向购物车添加商品
购物车的准备工作都已经搞好,下面我们实现向购物车Cart中添加商品以及展示的功能。
大家也许已经注意到了,商品只展示信息,但是没有购买按钮,我们先把这个按钮加上。加上这个功能,我们就要有一个处理的Controller。需要再添加一个新的Controller吗?答案是否定的。大家可以看一下自动生成的那些Controller,一般都会包含index(), show(), new(), edit(), create(), update(), and destroy()等几个方法。create()比较贴合我们的需求。当然,也有些人认为new()看起来也可以,但是new()主要是用来和收集表单数据,添加新纪录用的。
既然决定使用哪个方法了,那么我们创建什么呢?不是Product,也不是Cart,那么只有LineItem了。那么可以打开%Depot%/app/controllers/line_items_controller.rb文件,看一下create()方法的说明,可以看出,我们的这个选择决定了我们所用的URL为/line_items,使用的HTTP方法为POST。这些决定也间接地影响了我们的页面展示。在以前的章节中,我们使用link_to()方法来创建URL,但是链接所用的HTTP方法是GET,不符合我们的要求。我们想用POST,所以,这次我们添加一个Button,添加Button就要使用button_to()方法了。
我们还需要将Button链接到一个特定的URL上。这点,Rails又可以帮我们一把,Rails”控制器名称”+”_path”的”法术”(借鉴《Ruby元编程》里面的术语)来生成URL。这里,我们使用line_items_path这个路径。
除此之外,我们还需要向line_items_path()添加一个Product的id作为标识符,来让它知道我们想要添加哪个商品。这实现起来也很简单,只需要向line_items_path()方法添加可选参数:product_id,另外我们还要传Product的一个实例对象过去,Rails会从中提取出id方便使用。
所需的业务和知识都已经说明完毕,下面我们开始修改代码。打开文件%Depot%/app/views/store/index.html.erb,在里面添加如下代码:
<h1>Your Pragmatic Catalog</h1> <% @products.each do |product| %> <div class="entry"> <%= image_tag(product.image_url) %> <h3><%= product.title %></h3> <%= sanitize(product.description) %> <div class="price_line"> <span class="price"><%= number_to_currency(product.price) %></span> <%= button_to 'Add to Cart', line_items_path(product_id: product) %> </div> </div> <% end %>
这是,可以打开http://127.0.0.1:3000/,页面已经生成了按钮。然后查看一下网页源代码,你会发现这个button_to()方法实际上是生成了一个表单,而按钮就是表单中的提交按钮。这个按钮还被包含在div标签中,而默认的div是行显示。所以,要对显示效果做个调整,打开文件%Depot%/app/assets/stylesheets/store.css.scss,在里面添加如下代码:
p, div.price_line { margin-left: 100px; margin-top: 0.5em; margin-bottom: 0.8em; } form, div{ display: inline; }
修改完了,记得使用rake assets:precompile编译一下CSS文件。
页面修改完了,下面我们来修改一下后台Controller的代码。这段代码的业务是这样的:查找出当前Session的购物车,如果没有则创建一个,然后把选择的商品添加到购物车中,然后显示购物车中内容。%Depot%/app/controllers/line_items_controller.rb代码如下:
def create @cart = current_cart product = Product.find(params[:product_id]) @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.' } format.json { render json: @line_item, status: :created, location: @line_item } else format.html { render action: "new" } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end
current_cart()方法是上面在ApplicationController中实现的方法,用于查找构造创建当前的购物车;沟通params(:product_id)从HTTP请求中,取得名为product_id的参数值,然后使用Product.find(params[:product_id])从数据库中查找出该商品信息,创建Product对象。
D呱呱
疑问:@cart.line_items.build(product: product)这段代码是什么意思?不是太明白。
功能性测试
既然修改了Controller,我们来做一个功能测试。这时就要修改%Depot%/test/functional/line_items_controller_test.rb,我们需要测试的是调整。测试代码如下:
test "should create line_item" do assert_difference('LineItem.count') do post :create, product_id: products(:ruby).id end assert_redirected_to cart_path(assigns(:line_item).cart) end
然后,运行如下指令,开始测试:
rake test:functionals
购物车展示
经过测试,没有问题。那么,我们就修改购物车的展示。修改%Depot%/app/views/carts/show.html.erb如下:
<% if notice %> <p id="notice"><%= notice %></p> <% end %> <h2>Your Pragmatic Cart</h2> <ul> <% @cart.line_items.each do |item| %> <li><%= item.product.title %></li> <% end %> </ul>
D呱呱
在做这个练习的过程中,可能会遇到如下错误:
Can't mass-assign protected attributes: product这是由于Rails修改了安全规则。只需要在类LineItem的类体代码内,增加如下代码即可:
attr_accessible :product问题原因解释如下:
- Can’t mass-assign protected attributes
- Hackers Love Mass Assignment
- Rails 3.2.3 makes mass assignment change
- Rails error: Can’t mass-assign protected attributes
- Rails Error: Can’t mass-assign protected attributes: interest_ids?
上面的帖子估计看得很无趣,前两天天又看了一篇帖子,针对这个问题的渊源讲解的更透彻,也更有意思,贴出来,希望对遇到这个问题的朋友有所帮助:
從 Github 被 Hack,談 Rails 的安全性( Mass-assignment )D瓜哥还在Ruby China上见了一个帖子,针对这个问题进行讨论,感兴趣的话请移步:遇到问题Can’t mass-assign protected attributes,麻烦帮忙解答一下
OK,我们的购物车完成了。但是,运行一下你会发现,这个购物车的现实还有点问题。这个,我们将在下一节来优化。
原文链接:https://wordpress.diguage.com/archives/16.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
哈哈,非常感谢你的这个系列文章 让我对 Ruby on Rails 感兴趣。!
从上周5 看了你的这个系列,禁不住自己动手尝试,这两三天以来还是遇到了一些问题,通过 Google 基本都解决了。
但是作为一个前端开发,还是蛮多不理解的,自己动手也仅仅只是照葫芦画瓢,该系列文档中也有很多是没有说清楚的,同时也很期待你接下来的笔记
刚才了解 关系型数据库的时候,发现了这篇文章: http://guides.ruby-china.org/getting_started.html ,里边对很多说明还算详细,分享之,希望能对你,或者其他感兴趣的朋友有所帮助。
谢谢。
对你有所帮助就行。下面的文章已经写出来一部分了,还没发。抽空发。不过,还没完,还得接着写。哈哈
辛苦了, 赞一个
哈哈,我也在学 Rails,觉得看书十遍,不如动手一遍