本章内容:
- 会话和会话管理
- 添加模型间的关系
- 创建一个按钮,可添加产品到购物车中
迭代D1:寻找购物车
将购物车放在数据库中,并在会话中存储该购物车的唯一标识符,cart.id。每当请求出现时,可以从会话中找到该购物车的标识,并用该标识在数据库中查找购物车。
1、创建购物车
rails generate scaffold cart
2、应用迁移
rake db:migrate
3、修改控制器app/controllers/application_controller.rb
Rails的会话像是一个散列,将购物车对应的数据库id(cart.id)赋值给标识符为cart_id的session中。
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
先从session对象中得到:cart_id,试图寻找与该id对应的购物车。如果没有找到,创建新的cart,并将新购物车的id保存在会话中。
迭代D2:将产品放到购物车中
1、创建在线商品表存放在线商品、购物车和产品之间的关系。
如图所示,在线商品line_items描述了购物车carts和商品products之间的关系。
创建Rails模型:
rails generate scaffold line_item product_id:integer cart_id:integer
应用迁移来创建相应的数据库表:
rake db:migrate
2、修改模型文件
在模型文件中添加一些声明来说明在线商品、购物车和产品之间的关系。(牵扯到cart、product和line_item的模型文件)
打开app/models下的文件cart.rb,添加一个对has_many的调用:
has_many :line_items一个购物车有许多相关联的在线商品。
:dependent => :destroy在线商品的存在依赖于购物车是否存在。
从相反的方向定义关系(从在线商品到carts和products表),修改app/models/line_item.rb
注:如果一个数据库表有外键,那么在相应模型中每个外键都要有个belongs_to声明。
belongs_to告诉Rails数据库,表line_items中的数据是依赖于表carts和表products的。
因为每个产品都可以有多个在线商品引用它,故需要在模型product中添加一个has_many指令。
class Product < ActiveRecord::Base
attr_accessible :description, :image_url, :price, :title
validates :title, :description, :image_url, :presence => true
validates :price, :numericality => {:greater_than_or_equal_to => 0.01}
validates:title, :uniqueness => true
validates :image_url, :format => {
:with => %r{\.(gif|jpg|png)$}i,
:message => 'must be a URL for GIF,JPG or PNG image.'
}
default_scope :order => 'title'
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_itme
private
def ensure_not_referenced_by_any_line_itme
if line_items.empty?
return true
else
errors.add(:base,'Line Items present')
return false
end
end
end
上面的代码声明了一个产品有多个在线商品,并定义了一个hook(钩子)方法叫
ensure_not_referenced_by_any_line_itme。
hook方法就是在对象的生命周期中某个给定的地方Rails会自动调用的方法。
迭代D3:添加一个按钮
现在模型间的关系处理完毕,该给每个产品添加一个Add to Cart按钮了。
1、修改视图页面,使用line_items_path指定处理动作的控制器为在线产品控制器,向控制器传入欲加入购物车产品的id
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<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 %>
修改效果:
2、修改在线商品控制器的create方法。
将产品id传递给create方法,以便唯一的标识要添加的产品。
修改后的create方法如下:
# POST /line_items
# POST /line_items.xml
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.xml { render :xml => @line_item,
:status => :created, :location => @line_item }
else
format.html { render :action => "new" }
format.xml { render :xml => @line_item.errors,
:status => :unprocessable_entity }
end
end
end
如以上代码所示,这部分的操作可以总结为:
跳转界面模板:
<h2>Your Pragmatic Cart</h2>
<ul>
<% @cart.line_items.each do |item| %>
<li><%= item.product.title %></li>
<% end %>
</ul>
3、结果测试
完成上述步骤之后点击Add to Cart按钮,但是报错如下
百度发现大家都遇到了这个bug,应该是版本差别所致。
由于写了“@cart.line_items.build(:product => product)”这句,这里要求必须把product属性放入attr_accessible中
http://guides.rubyonrails.org/security.html#mass-assignment
为了安全考虑,如果允许mass-assignment的话,可以通过链接往数据库里插入数据
所以默认不可以直接用build来new这个新对象,除非加上
attr_accessible :product,:cart 允许mass-assignment。
修改模型层文件
重新测试,运行正常: