本章内容:
- 会话和会话管理
- 添加模型间的关系
- 创建一个按钮,可添加产品到购物车中
迭代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
迭代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。
修改模型层文件
重新测试,运行正常: