Agile Web Development with Rails 翻译(十八)
这里,唯一有趣的是和选择列表有关的代码。我们已经假定有效付款选项清单是Order model的一个属性--它将是model文件中的一个数组的数组。每个子数组的第一个元素被做为选择的一个选项而显示出来的字符串,第二个值将被存于数据库。[如果我们期望非Rails应用程序能更新orders表,我们可能想移动付款类型清单到一个单独的表中,并使orders列的一个外键引用到这个新表。在这种环境下,Rails也提供了对产生列表清单的很好支持。]我们最好在我们忘记之前在model order.rb中定义这个选项数组。
PAYMENT_TYPES = [
[ "Check", "check" ],
[ "Credit Card", "cc" ],
[ "Purchase Order", "po" ]
].freeze # freeze to make this array constant
如果在model中没有当前的选项,我们希望在浏览器的输入区域中显示一些提示文本。我们在model所返回的选项开始加入一个新的选项来实现这一点。这个新的选项有一个合适的显示字符串和一个空值。
这一切都做好了之后,我们可以打开浏览器。增加一些商品到购物车,点击checkout链接,你将看到新的付款页面如图9.1所示:
看起来不错!当然,如果你点击checkout按钮,你将被致以如下问候:
Unknown action
No action responded to save_order
让我们继续实现store controller中的save_order()动作。
该方法必须:
1、从表单捕获一个值,以组装一个新的Order model对象。
2、从我们的购物车中向该order增加商品项。
3、确认并保存order。如果失败了就显示适当的信息,并让用户纠正问题。
4、一旦order被成功保存,重新显示目录页,其中包括确认order已被保存的信息。
该方法最终看起来如下所示:
Line 1 def save_order
- @cart = find_cart
- @order = Order.new(params[:order])
- @order.line_items << @cart.items
5 if @order.save
- @cart.empty!
- redirect_to_index('Thank you for your order.')
- else
- render(:action => 'checkout')
10 end
- end
在第3行,我们创建了一个新的Order对象,并用表单数据初始化它。在这个例子中我们希望所有的表单数据都和order对象相关,因此我们选择了来自参数的:order 哈希表(我们将在341页讨论表单是如何和model相连的)。下一行将已存于购物车中的商品项增加到order中--经过最后的动作处理后,会话数据仍然在那里。注意对于那些不同的外键字段我们无需作任何特别的处理,比如设置line item中的order_id行为新创建的order行。当我们使用has_many()和belongs_to()声明之后(我们分别将它们添加到Order和LineItem model中),Rails自动完成这一切。
接着第5行,我们告诉order 对象来保存它自己(以及它的孩子,商品项目)到数据库。在此期间,order对象将完成有效性确认(但我们过一会儿就来处理它)。如果保存成功了,我们清空购物车以便为下一次订单做准备,并重新显示目录。使用我们的redirect_to_index( )方法来显示一个令人愉快的信息。相反如果保存失败了,我们重新显示checkout表单。
在我们叫客户来之前的最后一件事,记得当我们向她展示我们的第一个产品管理页面吧?她要我们增加有效性确认。我们也许也应该为我们的checkout页做同样的事。目前,我们仅检查在order中的每个字段是否都被给定了一个值。我们知道如何做--增加一个validates_presence_of( )方法到Order model:
validates_presence_of :name, :email, :address, :pay_type
----------------------------------------------------------
Joe 问. . .
你不创建Orders的复本吗?
Joe看到我们的controller在两个动作中,checkout和save_order,创建了Order model对象,他很奇怪为什么这没有导致在数据库中有重复的order。回答很简单:checkout动作在内存创建一个Order对象,并简单地传给一同工作的模板代码。一旦这个响应被发送给浏览器,这个特殊对象就会被扔掉,最后它会被Ruby的垃圾回收器回收。它从来就没有接触过数据库。
而 save_order 动作也创建一个Order 对象,由表单字段组装成的。这个对象被保存在数据库中。所以,model对象扮演了两个角色:它们映射数据进出数据库,但它们也只是个正常的持有商业数据的对象。当你典型地通过调用save()动作时,它们才会影响到数据库。
---------------------------------------------------
因此,作为对此的首次测试,点击checkout按钮而不填任何表单字段。我们希望看到checkout页面重新显示,并给出一些与空字段有关的错误信息。相反,我们仅仅看到checkout页--没有错误信息。我们忘记告诉Rails写出它们了![If you’re following along at home and you get the message No action responded to save_order, it’s possible that you added the save_order( ) method after the private declaration in the controller. Private methods cannot be called as actions.]
任何与有效性确认或保存model有关的错误都应被该model保存在一起。这里有另一个helper方法,error_messages_for( ),它在view中抽取并格式化这些错误。我们只需在checkout.rhtml文件的开始增加一行即可。
<%= error_messages_for("order") %>
正如管理页面的有效性确认,我们需要增加scaffold.css样式表到我们的store 布局文件以获得这些错误的合适格式。
<%= stylesheet_link_tag "scaffold", "depot", :media => "all" %>
我们完成之后,提交一个空的checkout页面,将会给我们显示很多高亮度错误,如图9.2所示。
如果我们填了一些数据,如图9.3所示,点击checkout,我们应该回到分类目录页,正如显示在图底部的那样。但是它工作了吗?让我们看看数据库。
depot> mysql depot_development
Welcome to the MySQL monitor. Commands end with ; or g.
mysql> select * from orders;
+----+-------------+-----------------------+-------------+----------+
| id | name | email | address | pay_type |
+----+-------------+-----------------------+-------------+----------+
| 3 | Dave Thomas | customer@pragprog.com |
+----+-------------+-----------------------+-------------+----------+
1 row in set (0.00 sec)
mysql> select * from line_items;
+----+------------+----------+----------+------------+
| id | product_id | order_id | quantity | unit_price |
+----+------------+----------+----------+------------+
| 4 | 4 | 3 | 1 | 29.95 |
+----+------------+----------+----------+------------+
1 row in set (0.00 sec)
保存了!至少,现在我们可以展示给我们的客户了。她很喜欢,除了...你是否认为我们应该在checkout页面上增加一个购物车内容的汇总?听起来我们需要一个新的重复过程了。
123 Main St | check |