Agile Web Development with Rails 翻译(十七)
第九章 任务 D: 结算!
迄今为止,我们已经建立了一个基本的产品管理系统,我们实现一个分类目录,并有一个很好看的商店购物车。现在我们需要让买方能够真正地用购物车中购买些东西。再继续之前让我们先实现结算功能。
我们不打算走太远。目前我们所要做的是获取用户的联系细节和付款方式。利用这些我们将在数据库中构建一个订单。顺着它我们可以多看看model,确认,表单处理以及组件。
----------------------------------------------------------
Joe 问. . .
信用卡的处理在哪儿?
在这一点上,我们的教程应用程序与事实有些脱离。现实中,我们或许需要我们的应用程序能处理商业付款。我们可能甚至想整合信用卡处理(或许使用Payment模块)。但是,整合一个付款处理系统要求很多人的工作和至始至终的工作。这会转移我们对Rails的注意力,所以我们继续这个不完全的练习。
----------------------------------------------------------------
9.1 Iteration D1: 捕获定单
订单是一组商品项目,与购买交易的细节。我们已经有了商品项目--当我们在前一章创建购物车时我们已经定义了它们。我们还没有用于保存订单的表。基于47页的图表,并且与客户简单交流之后,我们可以创建orders表。
create table orders (
id int not null auto_increment,
name varchar(100) not null,
email varchar(255) not null,
address text not null,
pay_type char(10) not null,
primary key (id)
);
我们知道当我们创建新订单时,它必然要和一个或多个商品项目联系在一起。在数据库术语中,这意味着我们需要从line_items表到orders表增加一个外键引用,所以我们利用这个机会来也更新line items的DDL(查看487页的create.sql清单,看看如何添加drop table语句。)
create table line_items (
id int not null auto_increment,
product_id int not null,
order_id int not null,
quantity int not null default 0,
unit_price decimal(10,2) not null,
constraint fk_items_product foreign key (product_id) references products(id),
constraint fk_items_order foreign key (order_id) references orders(id),
primary key (id)
);
记得更新这个计划(这会清空你数据库内包含的所有数据)并使用Rails的产生器来创建Order model。我们没有重新生成line items的model,因为目前这个很好。
depot> mysql depot_development <db/create.sql
depot> ruby script/generate model Order
这告诉数据库外键的事。这是件好事,因为许多数据库都将检查外键约束,以保持我们的代码的正确性。但我们同样要告诉Rails一个定单有很多商品项目,并且一个商品项目属于一个定单。首先,我们打开app/models目录下新创建的order.rb文件,并添加一个对has_many()的调用。
class Order < ActiveRecord::Base
has_many :line_items
end
下面,我们指定一个反向的链接,在line_item.rb文件中添加belongs_to()方法的调用(记住在我们设置cart时已经声明一个line item属于一个product。)
class LineItem < ActiveRecord::Base
belongs_to :product
belongs_to :order
# . . .
我们还需要一个action来处理获取订单详情。在前一章我们在cart view中设置了一个链接给称为checkout的动作,所以现在我们必须在store controller中实现一个checkout()方法。
def checkout
@cart = find_cart
@items = @cart.items
if @items.empty?
redirect_to_index("There's nothing in your cart!")
else
@order = Order.new
end
end
注意我们如何首先检查以确保购物车中有东西。这阻止人们直接通过导航到达付款操作,并创建一个空的订单。
假设我们已经有了一个有效的cart,我们创建个新Order对象以用来填充view。注意这个order还没有保存到数据库--它只是用view来组装checkout表单。
Checkout view在app/views/store目录下的checkout.rhtml文件内。让我们构建些简单的东西来展示如何将表单数据和Rails model对象联系起来。然后我们添加确认和错误处理,对于Rails来说,以基本与重复开始有助于使事情变得容易。
Rails 和表单
Rails对从关系数据库获取数据并将其传入到Ruby对象中提供了强大的支持。所以你也期望,在model 对象和用户的浏览器之间传递数据,它也有同样的支持。
我们已经看到了这个例子。当我们创建我们的产品管理controller时,我们使用scaffold generator创建一个表单以为新产品获得所有数据。如果你查看那个view的代码(app/views/admin/new.rhtml),你将看到下面内容:
<h1>New product</h1>
<%= start_form_tag :action => 'create' %>
<%= render_partial "form" %>
<%= submit_tag "Create" %>
<%= end_form_tag %>
<%= link_to 'Back', :action => 'list' %>
这涉及到使用render_partial('form')的子表单。[render_partial( ) is a deprecated form of render(:partial=>...). The scaffold generators had not been updated to create code using the newer form at the time this book was written.]在文件_form.rhtml中的该子表单可获取产品的有关信息:
<%= error_messages_for 'product' %>
<!--[form:product]-->
<p><label for="product_title">Title</label><br/>
<%= text_field 'product', 'title' %></p>
<p><label for="product_description">Description</label><br/>
<%= text_area 'product', 'description', :rows => 5 %></p>
<p><label for="product_image_url">Image url</label><br/>
<%= text_field 'product', 'image_url' %></p>
<p><label for="product_price">Price</label><br/>
<%= text_field 'product', 'price' %></p>
<p><label for="product_date_available">Date available</label><br/>
<%= datetime_select 'product', 'date_available' %></p>
<!--[eoform:product]-->
我们也可以使用scaffold generator来为orders表创建一个表单,但是Rails生成的表单并不总是那么漂亮。我们要生成一些更好看的。让我们在创建我们自己的数据输入表单之前,更多地了解那些自动生成表单的方法。
Rails对于所有那些标准的HTML输入标记都有对应的model相关的helper方法。例如,我们需要创建一个HTML<input>标记来允许买者输入他们的名字。在Rails中,我们可以在view中写出如下语句:
<%= text_field("order", "name", :size => 40 ) %>
这里,text_field()将创建一个带有type="text"的HTML <input>标记。这个的语句会用@order model内的name字段的内容组装那个字段。甚至当最终用户最后提交表单时,model能够获取浏览器响应给这个字段的新值,并保存它,然后在需要时写入数据库。
有很多用于表单的helper 方法(我们将在332页详细讨论)。除了text_field(),我们可以使用text_area()来获取买者的地址,并用select()来创建一个付款方式的选择列表。
当然,为了让Rails从浏览器取得一个响应,我们需要将表单链接到Rails的动作上。我们可以通过指定一个链接给我们的application,controller,和<form>标记的action=attribute内的动作来做到这点。但使用form_tag()方法更容易,另一个Rails helper方法也做同样的事。
因此,在有了这些背景资料后,我们准备创建获取定单信息的view。我们首先试试app/views/store目录中的checkout.rhtml文件。
<% @page_title = "Checkout" -%>
<%= start_form_tag(:action => "save_order") %>
<table>
<tr>
<td>Name:</td>
<td><%= text_field("order", "name", "size" => 40 ) %></td>
</tr>
<tr>
<td>EMail:</td>
<td><%= text_field("order", "email", "size" => 40 ) %></td>
</tr>
<tr valign="top">
<td>Address:</td>
<td><%= text_area("order", "address", "cols" => 40, "rows" => 5) %></td>
</tr>
<tr>
<td>Pay using:</td>
<td><%=
options = [["Select a payment option", ""]] + Order::PAYMENT_TYPES
select("order", "pay_type", options)
%></td>
</tr>
<tr>
<td></td>
<td><%= submit_tag(" CHECKOUT ") %></td>
</tr>
</table>
<%= end_form_tag %>