Agile Web Development with Rails 4E 学习笔记2
=========zqhxuyuan@gmail.com 2011.10=========
声明:本文根据英文电子版Rails3 4E 2011-03-29 重要的地方有中文注释.
采用Notepad++编辑(语言:YAML),发布在ItEye上可能看起来不是很美观.
PS:只是RoR新手(而且尼玛还在win7上编程,羡慕Mac..),如有错误,欢迎指正.
===========================================
Chapter 10. Task E. A Smarter Cart
5.1 相同购物项显示Product的数量:
> rails generate migration add_quantity_to_line_items quantity:integer
-> db/migrate/add_quantity_to_line_items.rb
class AddQuantityToLineItems < ActiveRecord::Migration
def self.up
add_column :line_items, :quantity, :integer, :default => 1
end
end
> rake db:migrate
create_table "line_items"
t.integer "product_id"
t.integer "cart_id"
t.integer "quantity", :default => 1
end
给购物项LineItems添加字段quantity,检查购物车中的购物项是否存在将要添加的Product
-> app/models/cart.rb
def add_product(product_id)
current_item = line_items.find_by_product_id(product_id)
if current_item
current_item.quantity += 1
else
current_item = line_items.build(:product_id => product_id)
end
current_item
end
-> 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)
@line_item = @cart.add_product(product.id)
end
-> app/views/carts/show.html.erb
<%= item.quantity %> × <%= item.product.title %>
重新访问http://localhost:3000 添加Product到购物车中.如果添加的是相同的Product,
则购物车中显示的是数量,而不是一个一个Product列举出来.比如:http://localhost:3000/carts/5
Your Pragmatic Cart
2 × Programming Ruby 1.9
4 × Debug It!
5.2 数据库提交和回滚操作:
当访问http://localhost:3000/carts/2时 因为cart_id=2的那些操作在5.1之前,所以显示是还是一个一个列举Product.
为了使所有的购物车都运用到5.1中的操作.即看到的都是:如果有相同Product,则显示相同的数量.
我们要做的是migrate the data:
> rails generate migration combine_items_in_cart
-> db/migrate/combine_items_in_cart.rb
def self.up
# replace multiple items for a single product in a cart with a single item
Cart.all.each do |cart|
# count the number of each product in the cart
sums = cart.line_items.group(:product_id).sum(:quantity)
sums.each do |product_id, quantity|
if quantity > 1
# remove individual items
cart.line_items.where(:product_id=>product_id).delete_all
# replace with a single item
cart.line_items.create(:product_id=>product_id, :quantity=>quantity)
end
end
end
end
> rake db:migrate
访问http://localhost:3000/carts/2 这时cart_id=2的购物车中显示的不再是一个一个相同的Product,也是有数量的.
def self.down
# split items with quantity>1 into multiple items
LineItem.where("quantity>1").each do |line_item|
# add individual items
line_item.quantity.times do
LineItem.create :cart_id=>line_item.cart_id, :product_id=>line_item.product_id, :quantity=>1
end
# remove original item
line_item.destroy
end
end
> rake db:rollback
在此访问http://localhost:3000/carts/2 则又回到5.1之前的显示.
5.3 错误处理及flash内容显示:
当访问如http://localhost:3000/carts/wibble会报错ActiveRecord::RecordNotFound in CartsController#show
Rails处理错误一种方便的方式是使用flash这种类似hash的结构.(下面的:notice => 'Invalid cart'即hash结构)
A hash is a bucket in which you can store stuff as you process a request.
The contents of the hash are available to the next request in this session before being deleted automatically.
flash中的内容在同一个Session中被删除之前 可以被下一个URL请求所获取.flash常用于错误信息收集.
比如访问/carts/wibble,show方法检测到传递一个无效的cartid,flash可以存储错误消息,
在重定向到index页面时(Controller的Action:index,对应view的index.html.erb),
可以提起出错误消息显示出来.这里next request即show的下一个请求index可以获取到放置到show里的flash的内容.
The flash information is accessible within the views by using the flash accessor method.
-> app/controllers/carts_controller.rb
def show
begin
@cart = Cart.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error "Attempt to access invalid cart #{params[:id]}"
redirect_to store_url, :notice => 'Invalid cart'
else
#...
end
end
-> views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
CartsController#show 中放置在flash的错误消息:notice => 'Invalid cart'
重定向到store_url即StoreController#index,在views/store/index.html.erb中可以取出来:<%= notice %>
为什么不使用Controller中存在的实例变量来存储错误消息.
因为在我们的应用中(show方法里),使用的是浏览器重定向.重定向:浏览器发送一个新的请求给我们的应用即
StoreController#index.当我们接收另外的这个请求时,之前的请求中存在的实例变量(CartController#show)都会消失.
而flash中的数据是存储在Session中的.在多次不同的请求中数据是都可以获取到的.
5.4 清空购物车:
deleting their own (current) cart,and to remove the cart from the session.
-> app/views/carts/show.html.erb
<%= button_to 'Empty cart', @cart, :method => :delete, :confirm => 'Are you sure?' %>
-> app/controllers/carts_controller.rb
def destroy
@cart = current_cart
@cart.destroy
session[:cart_id] = nil
respond_to do |format|
format.html { redirect_to(store_url, :notice => 'Your cart is currently empty') }
format.xml { head :ok }
end
end
测试时,由于测试环境是木有Session的,所以初始时我们把当前的购物车
(cart_id:@cart.id)放到Session中来模拟Controller中的current_cart
-> test/functional/carts_controller_test.rb
test "should destroy cart" do
assert_difference('Cart.count', -1) do
session[:cart_id] = @cart.id
delete :destroy, :id => @cart.to_param
end
assert_redirected_to store_path
end
remove the flash message that is automatically generated when a line item is added.
-> app/controllers/line_items_controller.rb
format.html { redirect_to(@line_item.cart) }
5.5 显示价格信息:
add a method to both the LineItem and Cart models that returns
the total price for the individual line item and entire cart
-> app/views/carts/show.html.erb
<% @cart.line_items.each do |item| %>
<%= number_to_currency(item.total_price) %>
<% end %>
<%= number_to_currency(@cart.total_price) %>
-> app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :product
belongs_to :cart
def total_price
product.price * quantity
end
end
-> app/models/cart.rb
class Cart < ActiveRecord::Base
has_many :line_items, :dependent => :destroy
def total_price
line_items.to_a.sum {|item| item.total_price}
end
end
关于Model中方法访问变量:LineItem belongs_to的内容相当于定义了一个变量.
不同的是belongs_to定义的内容是Symbol类型,而在方法中使用的应该是去掉Symbol之前的:的变量.
比如belongs_to :product,则total_price方法中可以访问product.
*quantity.因为在5.1中add_quantity_to_line_items给line_items添加了一个quantity字段.
相当于给Model:LineItem添加了一个属性.所以在Model对象里是可以访问自己的属性的.
也就是说Model方法里可以访问的属性包括:
数据库定义的字段=>对应Model添加的属性.
Model中定义的依赖关系: belongs_to has_many 定义的Symbol对应的变量(去掉Symbol前的冒号)