?
Agile Web Development with Rails 4E 2011.03 ??
========zqhxuyuan@gmail.com 2011.10========
?
===========================================
Chapter6. Task A: Creating the Application
1.1 创建Rails项目depot:
> rails new depot?
1.2 切换到depot目录下:
> cd depot?
使用Rails3默认的数据库SQLite3
1.3 创建项目的脚手架:
> rails generate scaffold Product?
title:string description:text image_url:string price:decimal
生成数据库脚本: db/migrate/create_products.rb 如果想查看表结构:db/schema.rb
Model: app/models/product.rb
View: app/views/products/*.html.erb
Controller: app/controllers/products_controller.rb
test: test/unit/product_test.rb ?test/functional/products_controller_test.rb
(注意:Model是单数,以及Model的单元测试test/unit/product_test.rb
复数的有:View:products/?
Controller:products_controller.rb 以及Controller的Fucntional测试:products_controller_test.rb) ?
1.4 修改Product对应的表的某个字段的长度和精度,修改后需要和原先的数据库进行migrate:
-> db/migrate/create_products.rb
class CreateProducts < ActiveRecord::Migration
t.decimal :price, :precision => 8, :scale => 2
end
> rake db:migrate ?
1.5 启动Rails内置服务器WEBrick,访问以上URL,查看Products列表:
> rails server
@ http://localhost:3000/products
点击列表页面的New Product进入http://localhost:3000/products/new
在新增页面输入数据,提交,并返回 http://localhost:3000/products 则可以看到新增的记录
分析CRUD的流程1--页面跳转:
http://localhost:3000/products/ 满足app/controllers/products_controller.rb ProductsController?
的URL命名约定: GET /products 对应的方法是: index?
在index方法里取得所有的products: @products = Product.all
页面指向respond_to -> format.html # index.html.erb 即app/views/products/index.html.erb
(# index.html.erb是注释,因为该方法名为index,所以format.html的值为:index.html.erb)
在index.html.erb中@products.each do |product|取出所有的products循环输出每个Product的信息
数据的获取是通过Json方式,Controller中在指向index.html.erb时获取到了Json数据: format.json { render json: @products }
新增Product,路径为:link_to new_product_path 根据Rails的命名约定满足?
GET /products/new 的URL(link_to: Get; new_product_path: /products/new ). 会调用Controller的new方法并指向new.html.erb
new.html.erb 只有一行代码: <%= render 'form' %> 指向_form.html.erb
_form.html.erb 有两部分组成,一是错误信息的显示,比如提交时数据验证失败显示错误信息;第二部分即是表单的数据了
在新增页面提交表单 最终会调用到Controller的create方法,保存数据...
1.7 测试以上所写的代码是否正确:
> rake test
This is for the unit, functional, and integration tests that?
Rails generates along with the scaffolding. 注意当修改了Controller,Model中的代码时,
运行rake test可能会报错,这时候需要对test/中相应的测试文件进行修改.
1.8 在服务器启动时,进行初始化数据的导入.下面这种方式更加对象化,而不是用insert SQL语句:
-> db/seeds.rb
Product.create(:title=>'sth',:description=>'sth',:image_url=>'spath',:price=>11.22)
> rake db:seed ?
@ http://localhost:3000/products
?
1.9 关于全局Layout:
app/views/layouts/application.html.erb?
所有的views/*/*.html.erb都将继承application.html.erb的布局.可以在这里写每个页面固定的开头和结尾.
Rails keeps a separate file that is used to create a standard page environment for the entire application
?
application.html.erb, be the layout used for all views for all controllers that don’t otherwise provide a layout.
除非Controller有自己的Layout会覆盖掉全局的Layout.否则Application Layout会应用到所有的视图里.
?
We loaded some test data into the database(1.8), we rewrote the index.html.erb file
that displays the listing of products, we added a depot.css stylesheet, and that
stylesheet was loaded into our page by the layout file application.html.erb(1.9).?
?
===========================================
Chapter 7. Task B: Validation and Unit Testing
2.1 给Product Model添加字段验证:
-> app/models/product.rb
class Product < ActiveRecord::Base
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.'
}
end
访问http://localhost:3000/products/new 提交时会对输入域进行验证 如果验证失败则会在页面顶部显示错误信息.
2.2 给Model添加验证后的单元测试:
> rake test
运行测试脚本,会发现报错test/functional/products_controller_test.rb:
test_should_create_product test_should_update_product?
很显然当我们增加了Model的验证代码,测试部分也需要更改:
在测试脚本文件中ProductsControllerTest 的setup方法里初始化一个Product对象@update
并在2个报错的方法里把product: @product.attributes 改为 :product => @update指向初始化的product对象
setup方法第一行: @product = products(:one) 获取test/fixtures/products.yml中的数据
这是测试文件初始化加载的数据,正如seeds.rb是项目启动时初始化加载的数据
2.3 test类型有2种:
unit针对Model测试 test/unit/product_test.rb?
class ProductsControllerTest < ActionController::TestCase
可以运行:> rake test:units
functional针对Controller测试 test/functional/products_controller_test.rb?
class ProductTest < ActiveSupport::TestCase?
可以运行命令:>rake test:functionals
rake test 会测试整个项目是否有错误
===========================================
Chapter 8. Task C: Catalog Display
3.1 创建前台Product展示的控制器和页面:
> rails generate controller store index
创建StroeController和对应的Controller的index方法
和rails generate scaffold不同的是创建脚手架会创建Model,Controller,View,数据库脚本,测试文件等
而generate controller则只创建Controller:
app/controllers/store_controller.rb
app/views/store/index.html.erb
test/functional/store_controller_test.rb
还有一点区别是用generate scaffold创建的View和Controller是复数形式,Model是单数;
而generate controller则是单数.
3.2 上面的命令创建了StoreContoller以及index方法,通过URL:?
http://localhost:3000/store/index则会调用对应的方法.进入views/store/index.html.erb
-> config/routes.rb?
root :to => 'store#index', :as => 'store'
3.3 删除原先根目录下对应的文件:
> rm public/index.html?
当访问 http://localhost:3000 等价于上面的http://localhost:3000/store/index
进入的页面是: views/store/index.html.erb
3.4 访问规则测试:
http://localhost:3000/products 进入后台ProductList的显示页面views/products/index.html.erb
http://localhost:3000/products/index 则会报错:
ActiveRecord::RecordNotFound in ProductsController#show Couldn't find Product with id=index
http://localhost:3000/store 报错:Routing Error No route matches [GET] "/store"
由此可见通过generate scaffold,URL:/products代表访问的是ProductsController的index方法.
但是不能这样子访问:/products/index.
而通过generate controller,contoller中定义了哪些方法,
要访问该方法对应的页面则在controller后加上/方法名即可对应.
===========================================
Chapter 9. Task D: Cart Creation
4.1 创建购物车对象脚手架.但是没有给Cart指定字段:
> rails generate scaffold cart
> rake db:migrate
-> app/controllers/application_controller.rb
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
?
4.2 创建购物项的脚手架:
> rails generate scaffold line_item product_id:integer cart_id:integer
> rake db:migrate
4.3 目前为止的数据库脚本(schema.rb):
create_table "carts"
end
?
create_table "line_items"
t.integer ?"product_id"
t.integer ?"cart_id"
end
?
create_table "products"
t.string ? "title"
t.text ? ? "description"
t.string ? "image_url"
t.decimal ?"price", ? ? ? :precision => 8, :scale => 2
end
由此可见line_items可以当做carts和products的中间表.
?
4.3 Connecting Products to Carts: 将Product和Cart互相关联
-> app/models/cart.rb
Cart?
has_many :line_items
-> app/models/line_item.rb
LineItem?
belongs_to :product?
belongs_to :cart
-> app/models/product.rb
Product?
has_many :line_items
Cart 1---->N LineItem N<----1 Product ...Order
一个Cart可能有多个LineItem,一个Product也可能有多个LineItem(多个LineItem可能对应一个Product)
?
4.4 line_items涉及到的级联删除:
?
4.5 给前台的每个Product添加一个购买按钮:
-> app/views/store/index.html.erb
<%= button_to 'Add to Cart', line_items_path(:product_id => product) %>
button_to line_items_path(:product_id => product) 是如何调用到LineItemsController的create方法?
button_to : POST ; line_items_path : /line_items
LineItemsController中满足POST /line_items的只有create方法
如果是link_to line_items_path. link_to : GET line_items_path 则对应的方法为index
-> 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)
#...
end
为什么是LineItemsController.create而不是CartsController.create.
而且ApplicationController中Cart.create是在哪里定义的?(这个稍后解释)
把Product加入购物车中,即创建了一个购物项.所以使用的是LineItemsController.create.
find the shopping cart for the current session(@cart = current_cart),?
add the selected product to that cart(@cart.line_items.build), and display the cart contents.
pass that product we found into @cart.line_items.build.?
This causes a new line item relationship to be built between the @cart object and the product.
4.6 添加一个购物项后指向Cart页面,而不是购物项本身:
在LineItemsController.create方法中,
if @line_item.save #保存后
format.html { redirect_to @line_item.cart }
Since the line item object knows how to find the cart object,?
all we need to do is add .cart to the method call
那么调用的是CartsController的哪个方法index? show? 换句话说进入的是哪个页面?
@line_item.cart 通过LineItem得到Cart对象,所以实际上是传递了cart_id这个参数.
所以URL的形式应该是/carts/cart_id 类型为GET.满足的方法是show
可以这么理解:将一个购物项加入购物车中,而且这个购物车是Session中唯一的.
如果是/casts即所有的购物车List.显然在我们的应用中是说不通的.
所以app/views/carts/show.html.erb 通过Cart取出当前购物车中所有的购物项
<% @cart.line_items.each do |item| %>
<li><%= item.product.title %></li>
<% end %>
4.7 添加购物项的Functional测试:
因为LineItemsController修改的是create方法.接收参数:product_id
所以test/functional/line_items_controller_test.rb 修改的也是对应的create方法并传递需要的参数:
post :create, :product_id => products(:ruby).id?
products(:ruby)是test/fixtures/products.yml中的ruby:对应的记录.
测试重定向: assert_redirected_to cart_path(assigns(:line_item).cart)
cart_path:/carts/ assigns(:line_item).cart把购物项对应的购物车对象的id取出来并设置值,即cart_id
运行测试命令: rake test:functionals
有点不明白这里的Functional测试,在前台Product展示页面添加一个购物项到购物车中,
这里能调用到测试的代码吗??
4.8 测试Session:
在Window上跑Ruby有点慢.有时候启动服务器,刷页面,但是没有反应.
还有更奇怪的问题是,有时候点击Add To Cart,浏览器的地址不是:http://localhost:3000/carts/3
而是http://localhost:3000/line_items?product_id=1 页面会报错说没有line_items这个方法.
这时候可以对服务器Ctrl+C终止下. 然后他就会报一堆的错,但页面却恢复正常了. 这里估计没有完全中止服务器.
测试Session,可以这样子做.关掉浏览器.Session中保留的cart_id消失.
重新访问http://localhost:3000. 点击某个Product购买,URL变为: http://localhost:3000/carts/4?
这里的4是新生成的Session中的cart_id.因为如果Session中找不到cart_id?
则会重新创建一个新的Cart对象,并把cart_id放入Session中.
同时购物车中的数据只会显示当前添加的Product.
关掉浏览器之前的那次会话的数据(sessionId=3)是不会存在在当前的这个Session中(sessionId=4).
4.9 查看别的数据:
如果想看LineItems,即所有Cart对应哪些Product. 访问:http://localhost:3000/line_items
似乎有点难以理解. 因为一般来说一个浏览器在当前只会有一个Cart,即一个Session中只能有一个Cart.
不过这个结果可以理解为另一种概念: 浏览器的访问历史记录.
如果访问http://localhost:3000/carts 则是看不到数据的.
但是会有多个Show Edit Destroy链接.不过这样的URL没有什么意义了.
?