Rails开发:购物车(4)

第10章  付账

就目前而言,我们只须获取买主的详细联系信息和付款方式,并用这些信息在数据库里构造一份订单即可。

 

收集订单信息

 订单(order)是由一组订单项(line item)、外加购买交易的详细信息构成的。

ruby script/generate scaffold order name:string address:text email:string pay_type:string

ruby script/generate scaffold line_item product_id:integer order_id:integer quantity:integer total_price:decimal

 

depot_p/db/migrate/20080601000005_create_orders.rb 

t.string :pay_type, :limit => 10 

 

depot_p/db/migrate/20080601000006_create_line_items.rb 

→ t.integer :product_id, :null => false, :options => "CONSTRAINT fk_line_item_products REFERENCES products(id)"
→ t.integer :order_id, :null => false, :options => "CONSTRAINT fk_line_item_orders REFERENCES orders(id)"
→ t.integer :quantity, :null => false
→ t.decimal :total_price, :null => false, :precision => 8, :scale => 2
 

 

这张表包含两个外键。这是因为line_items 表中的每条记录都需要同时与“订单”和“货品”关联。 这里有几个问题,首先, Rails 迁移任务并未提供一种独立于数据库的方式指定外键约束,其次,不会真正执行外键约束;最后,这些自定义的约束不会被储存在db/schema.rb 文件中,因此也不会被复制到测试数据库。

 

rake db:migrate 

 

模型之间的关系 

 数据库已经知道订单、订单项与货品之间的关系了,但Rails 应用还不知道。所以,我们还需要在模型类中增加一些声明,指定它们之间的关系。(Rails为什么要知道?知道了之后可以做什么事情?)

 

注意:需要指明关系的模型是那些需要保存在数据库中的,像cart_item就不必指明belongs_to :cart这种关系

 

class Order < ActiveRecord::Base
  has_many :line_items
end 

 

class Product < ActiveRecord::Base
  has_many :line_items
#......
end 

 

class LineItem < ActiveRecord::Base
  belongs_to :order
  belongs_to :product
end 

 

这些声明到底管什么用?简单说来,它们会给模型对象加上彼此导航的能力。由于在LineItem模型中添加了belongs_to 声明,现在我们就可以直接得到与之对应的Order 对象了。 (每一种关系都会增加相应的attr或者method)

 

li = LineItem.find(...)
puts "This line item was bought by #{li.order.name}" 

 

order = Order.find(...)
puts "This order has #{order.line_items.size} line items" 

 

创建表单搜集订单信息 

现在数据库表和模型类都已经到位,我们可以开始处理付账的流程了。首先,我们需要在购物车里加上一个Checkout 按钮,并使其连接到store 控制器的checkout 方法。 

 

depot_p/app/views/store/_cart.html.erb 

 

<%= button_to "Checkout" , :action => 'checkout' %> 

 

depot_p/app/controllers/store_controller.rb
  def checkout
    @cart = find_cart
    if @cart.items.empty ?
      redirect_to_index("Your cart is empty" )
    else
      @order = Order.new
    end
  end

随后,当用户点击“提交”按钮时,我们希望在控制器中将来自表单的新数据取回到模型对象中。
幸运的是, Rails 使这一任务变得易如反掌:它提供了一组用于处理表单的辅助方法(就是helper模块?),这些辅助方法会与控制器和模型对象交互,实现了完整的表单处理功能。  

 

例子

<% form_for :order, :url => { :action => :save_order } do |form| %>
<p>
<label for="order_name" >Name:</label>
<%= form.text_field :name, :size => 40 %>
</p>
<% end %> 

 

传入的第一个参数:order 告诉该方法:它正在处理的是来自order实例变量的对象。辅助方法会根据这一信息给字段命名,并安排如何将字段的输入值传回给控制器。 由于这个文本字段是在form_for 的上下文环境中构造出来的,因此它就自动地与@order 对象中的数据建立起关联了。

 

Rails 需要知道每个字段的名称和值,这样才能将其与模型对象关联;而form_for 和各种字段层面的辅助方法( 例如text_field)正是用于提供这些信息的。

depot_p/app/views/store/checkout.html.erb

<div class="depot-form">
 <%= error_messages_for 'order' %>
 <% form_for :order, :url => { :action => :save_order } do |form| %>
  <fieldset>
   <legend>Please Enter Your Details</legend>
   <div>
    <%= form.label :name, "Name:" %>
    <%= form.text_field :name, :size => 40 %>
   </div>
   <div>
    <%= form.label :address, "Address:" %>
    <%= form.text_area :address, :rows => 3, :cols => 40 %>
   </div>
   <div>
    <%= form.label :email, "E-Mail:" %>
    <%= form.text_field :email, :size => 40 %>
   </div>
   <div>
    <%= form.label :pay_type, "Pay with:" %>
    <%=
     form.select :pay_type,
     Order::PAYMENT_TYPES,
     :prompt => "Select a payment method"
    %>

   </div>
   <%= submit_tag "Place Order" , :class => "submit" %>
  </fieldset>
 <% end %>
</div>

 

depot_p/app/models/order.rb 
 
 PAYMENT_TYPES = [
     # Displayed stored in db
      [ "Check" , "check" ],
      [ "Credit card" , "cc" ],
      [ "Purchase order" , "po" ]
     ]

第一个元素是一个字符串,代表将在下拉列表框中显示的文本;第二个元素是将被提交并最终存入数据库的值

 

depot_p/app/models/order.rb
class Order < ActiveRecord::Base
validates_presence_of :name, :address, :email, :pay_type
validates_inclusion_of :pay_type, :in => PAYMENT_TYPES.map {|disp, value| value}

# ... 

 

搜集订单详细信息 

depot_p/app/controllers/store_controller.rb
  def save_order
    @cart = find_cart
    @order = Order.new(params[:order])
    @order.add_line_items_from_cart(@cart)
    if @order.save
      session[:cart] = nil
      redirect_to_index("Thank you for your order" )
    else
      render :action => :checkout
    end
  end

 

我们新建了一个Order 对象,并用表单数据对其进行初始化。在这里,我们希望所有的表单数据都与Order 对象关联,所以我们直接从参数中取出:order 这个hash( 这也正是我们传递给form_for 的第一个参数的名字) ,用它来构造Order 对象。 

 

depot_p/app/models/order.rb
 def add_line_items_from_cart(cart)
  cart.items.each do |item|
   li = LineItem.from_cart_item(item)
   line_items << li
  end
 end

 

depot_p/app/models/line_item.rb
  def self.from_cart_item(cart_item)
    li = self.new
    li.product = cart_item.product
    li.quantity = cart_item.quantity
    li.total_price = cart_item.price
    li
  end

 

Ajax 修改 

 在接受订单之后,我们将用户重定向回到首页,并显示一条flash 信息:“ Thank you for your order .”这条flash 信息会一直显示在页面上。要在选购物品之后把包含flash 信息的<div> 隐藏起来即可。

 

首先想到的办法是在add_to_cart.js.rjs 中加上

page[:notice].hide

 

但是当我们首次进入在线商店时, flash 中什么都没有,所以id 叫notice 的这个<div> 也不会显示出来,最终的解决方案有些类似于hack :只有当这个<div> 存在时,我们才想运行.hide 方法。但RJS 并没有提供这种“检查div 是否存在”的功能。不过,它允许我们遍历页面上所有与指定的CSS selector 模式匹配的元素,所以我们不妨遍历所有id 为notice 的<div> 标记。

depot_p/app/views/store/add_to_cart.js.rjs
page.select('div#notice' ).each { |div| div.hide } 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值