Rails读书笔记第六章
来源:互联网 发布:淘宝魔法森林旗舰店 编辑:程序博客网 时间:2024/05/09 07:22
购物网站
Rails默认使用的是SQLite。在Rails中使用scaffold(脚手架)命令可以自动生成model、views和controller。回归一下之前的介绍,model是一个gatekeeper和data store.
在config/database.yml中的development入口处定义了Rails查找数据库的位置,即db/development.sqlite3
新建Product MVC
rails generate scaffold Product title:string description:text image_url:string price:decimalRails生成一个脚手架,名叫Product,包含了title类型为string等属性。SQLite的一些数据类型定义如下:
- string: limited length
- text: unlimited length
- integer:
- float
- decimal: precision(从最高位开始的有效整数个数,多余的置0),scale(小数位数)
- boolean
- :primary_key
- date
- time
- timestamp
在数据库中新建Product这个model,scaffold会自动生成一个名为products的table,以及view文件和controller文件。
如果想在表中添加一个属性,比如rating:
rails generate migration add_rating_to_products rating: integer
删除scaffold的话,使用下面的命令,d=destroy:
rails d scaffold Product
数据库
Product数据库生成后先存在了db/migrate/20151010000106_create_products.rb:
class CreateProducts < ActiveRecord::Migration def change create_table :products do |t| t.string :title t.text :description t.string :image_url t.decimal :price, precision: 8, scale: 2 t.timestamps null: false end endend
这个Migration类代表了对于数据库的改变,可以apply这些改变,也可以unapply来roll back。
t.decimal :price, precision: 8, scale: 2将price属性添加上precision和scale的constraints。
然后将这个数据库的更改apply到应用上:
rake db:migrate这个命令rake会在db/migrate目录下自动把未apply的migration给apply上。
在db/schema.rb 件中可以查看目前database的schema:
ActiveRecord::Schema.define(version: 20151010000106) do create_table "products", force: :cascade do |t| t.string "title" t.text "description" t.string "image_url" t.decimal "price", precision: 8, scale: 2 t.datetime "created_at", null: false t.datetime "updated_at", null: false endend
View
此时访问http://localhost:3000/products 就可以看到生成的view了。在app/views/products下有view的文件。
首页index.html.erb的内容如下:
首页index.html.erb的内容如下:
<p id="notice"><%= notice %></p><h1>Listing Products</h1><table> <thead> <!-- 表头 --> <tr> <th>Title</th> <th>Description</th> <th>Image url</th> <th>Price</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @products.each do |product| %> <!-- ruby代码, 对products这个table里所有的record,显示各个属性和操作--> <tr> <td><%= product.title %></td> <td><%= product.description %></td> <td><%= product.image_url %></td> <td><%= product.price %></td> <td><%= link_to 'Show', product %></td> <td><%= link_to 'Edit', edit_product_path(product) %></td> <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody></table><br><!-- 新建一个product --><%= link_to 'New Product', new_product_path %>show.html.erb是用来显示product的页面,其内容为:
<p id="notice"><%= notice %></p><p> <strong>Title:</strong> <%= @product.title %></p><p> <strong>Description:</strong> <%= @product.description %></p><p> <strong>Image url:</strong> <%= @product.image_url %></p><p> <strong>Price:</strong> <%= @product.price %></p><%= link_to 'Edit', edit_product_path(@product) %> |<%= link_to 'Back', products_path %>在_form.html.erb文件中定义了上传新product的page:
<%= form_for(@product) do |f| %> <% if @product.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2> <ul> <% @product.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :title %><br> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :description %><br> <%= f.text_area :description, rows: 6 %> #初始化6行 </div> <div class="field"> <%= f.label :image_url %><br> <%= f.text_field :image_url %> </div> <div class="field"> <%= f.label :price %><br> <%= f.text_field :price %> </div>
erb是embedded ruby,在html中嵌入ruby代码:
- <% ruby code %>
- <%= ruby expression %>
- <%# ruby code %> 注释
运行rake test,用来跑functional和unit的test,是由scaffold产生的test case,结果应该为0 failures, 0 errors。
Windows下如果报错,则先运行:
rake db:migrate RAILS_ENV=test
测试数据
为了测试方便,我们可以定义seed data,这样每个人都可以直接import这个seed data,而不用一个一个product的输入。编辑db/seeds.rb文件:
Product.delete_all #首先清空所有记录,下面插入创建一个记录Product.create(title: 'CoffeeScript', description: %{<p> CoffeeScript is JavaScript done right. It provides all of JavaScript's functionality wrapped in a cleaner, more succinct syntax. In the first book on this exciting new language, CoffeeScript guru Trevor Burnham shows you how to hold onto all the power and flexibility of JavaScript while writing clearer, cleaner, and safer code. </p>}, image_url: 'cs.jpg', price: 36.00)其中%{...}等价于双引号的string(“...”)。
下载所需要的图片到app/assets/images目录下,命令行下载,可以使用wget或者curl。
安装wget:sudo yum install wgetwget [-O local location] "url" curl [-o local location] “url”然后利用seed数据来覆盖填充数据库:
rake db:seed此时在网页浏览的数据就是seeds里的数据。
美观网页
scaffold命令在app/assets/stylesheets下自动生成了css和scss文件scss文件是由SASS生成的css预处理文件。SASS是一种css的开发工具,由于css是样式描述语言,通过css预处理器可以加入编程元素。
即使用一种编程语言进行网页样式设计,编译得到css文件。(products.css.scss经过编译其内容后会自动加入到application.css中?)
app/assets/stylesheets/products.css.scss内容如下:
.products { table { border-collapse: collapse; } table tr td { padding: 5px; vertical-align: top; } .list_image { width: 60px; height: 70px; } .list_description { width: 60%; dl { margin: 0; } dt { color: #244; font-weight: bold; font-size: larger; } dd { margin: 0; } } .list_actions { font-size: x-small; text-align: right; padding-left: 1em; } .list_line_even { background: #e0f8f8; } .list_line_odd { background: #f8b0f8; }}其中.定义的是类。
在views中有两个文件夹,一个是layout,一个是products。前面说了products里的是product model的view,而layout中存的是整个应用的标准view配置(standard page environment for the entire application)。
app/views/layout/application.html.erb的内容如下:
<!DOCTYPE html><html><head> <title>MyApp</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %></head><body><%= yield %></body></html>这里在body处定义让对应的controller来控制其相关的pages:
<body class='<%= controller.controller_name %>'>修改app/views/product/index.html.erb来匹配stylesheet的格式:
<h1>Listing products</h1><table><% @products.each do |product| %> <tr class="<%= cycle('list_line_odd', 'list_line_even') %>"> <!-- 这里用了cycle,odd和even轮换整体背景色 --> <td> <%= image_tag(product.image_url, class: 'list_image') %> <!-- 使用list_image定义的样式 --> </td> <td class="list_description"> <!-- 使用list_description定义的样式 --> <dl> <dt><%= product.title %></dt> <dd><%= truncate(strip_tags(product.description), length: 80) %></dd> <!-- 将product.description去除HTML的tag后截取前80个字符 --> </dl> </td> <td class="list_actions"> <!-- 使用list_actions定义的样式 --> <%= link_to 'Show', product %><br/> <%= link_to 'Edit', edit_product_path(product) %><br/> <%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %> <!-- confirm 弹出对话框--> </td> </tr><% end %></table><br /><%= link_to 'New product', new_product_path %>
Route
此时config/routes.rb中会加入捕获products的动作:
Rails.application.routes.draw do # resource route (maps HTTP verbs to controller actions automatically): resources :productsend
resources可以将HTTP的动作自动map到products的controller来处理。
Controller
此时的Product Model的controller文件(app/controllers/products_controller.rb)也产生了:=beginHelper HTTP Verb Path Controller#Actionproducts_path GET /products(.:format) products#index POST /products(.:format) products#createnew_product_path GET /products/new(.:format) products#newedit_product_path GET /products/:id/edit(.:format) products#editproduct_path GET /products/:id(.:format) products#show PATCH /products/:id(.:format) products#update PUT /products/:id(.:format) products#update DELETE /products/:id(.:format) products#destroy=endclass ProductsController < ApplicationController # before_action is called before a controller action before_action :set_product, only: [:show, :edit, :update, :destroy] # GET /products # GET /products.json def index @products = Product.all # SELECT products #puts "@@@ Index action should get 200 @@@" end # GET /products/1 # GET /products/1.json def show #puts "@@@ Show action should get 200 @@@" end # GET /products/new def new @product = Product.new #puts "@@@ New action should get 200 @@@" end # GET /products/1/edit def edit #puts "@@@ Edit action should get 200 @@@" # Then go to update end # POST /products # POST /products.json def create @product = Product.new(product_params) respond_to do |format| if @product.save # INSERT into products, must pass Model Validation format.html { redirect_to @product, notice: 'Product was successfully created.' } format.json { render :show, status: :created, location: @product }#puts "@@@ Create action should redirect_to http://localhost:3000/products/1 and call show action @@@" else format.html { render :new } format.json { render json: @product.errors, status: :unprocessable_entity }#puts "@@@ Create action should get 200 @@@" end end end # PATCH/PUT /products/1 # PATCH/PUT /products/1.json def update respond_to do |format| if @product.update(product_params) # UPDATE products, SET attributes, must pass Model Validation format.html { redirect_to @product, notice: 'Product was successfully updated.' } format.json { render :show, status: :ok, location: @product }#puts "@@@ Update action should redirect_to http://localhost:3000/products/1 and call show action @@@" else format.html { render :edit } format.json { render json: @product.errors, status: :unprocessable_entity }#puts "@@@ Update action should get 200 @@@" end end end # DELETE /products/1 # DELETE /products/1.json def destroy @product.destroy # DELETE from products respond_to do |format| format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' } format.json { head :no_content } end #puts "@@@ Destroy action should redirect_to http://localhost:3000/products and call index 200 @@@" end private # Use callbacks to share common setup or constraints between actions.# 在products table中根据id找product的record。params是从url中获得的参数:id。如果找不到会报错:ActiveRecord::RecordNotFound。 def set_product @product = Product.find(params[:id]) #puts "@@@ set_product is called. Find product " + @product.id.to_s + " @@@" end # Never trust parameters from the scary internet, only allow the white list through. # params是hash: # Parameters: {"product"=>{"title"=>"a", "description"=>"b", "image_url"=>"c.jpg", "price"=>"1"}, "commit"=>"Create Product"} def product_params params.require(:product).permit(:title, :description, :image_url, :price) endend
里面定义了如index、show、new、edit、create等等动作的处理方法。注释中也标记了HTTP的动作,如GET、POST等。
Model
在app/model/product.rb文件中定义了Product的ORM类:
# 类方法有new,find# 成员方法有save,update,destroyclass 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, allow_blank: true, format: { with: %r{\.(gif|jpg|png)\Z}i, message: 'must be a URL for GIF, JPG or PNG image.' }end
测试
在test/models/product_test.rb文件中定义了Prodcut Model的Validation测试:
# Model Test是用来对于Model中的validate做测验的,rake test:units# 将fixture中的数据写入test数据库不经过model的validation,可以通过products方法来引用record。require 'test_helper'# new方法生成的是model,并没有写进test数据库# .valid方法可以对model做validationclass ProductTest < ActiveSupport::TestCase # 直接生成,不给参数 test "product attributes must not be empty" do product = Product.new assert product.invalid? assert product.errors[:title].any? assert product.errors[:description].any? assert product.errors[:price].any? assert product.errors[:image_url].any? end # 给参数生成,价格验证 test "product price must be positive" do product = Product.new(title: "My Book Title", description: "yyy", image_url: "zzz.jpg") product.price = -1 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 0 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 1 assert product.valid? end # 给参数生成,标题验证 test "product is not valid without a unique title" do product = Product.new(title: products(:ruby).title, description: "yyy", price: 1, image_url: "fred.gif") assert product.invalid? assert_equal ["has already been taken"], product.errors[:title] end # 给参数生成,图片验证 def new_product(image_url) Product.new(title: "My Book Title", description: "yyy", price: 1, image_url: image_url) end test "image url" do ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg http://a.b.c/x/y/z/fred.gif } bad = %w{ fred.doc fred.gif/more fred.gif.more } ok.each do |name| assert new_product(name).valid?, "#{name} should be valid" end bad.each do |name| assert new_product(name).invalid?, "#{name} shouldn't be valid" end endend
# Controller Test是对URL request做测验的,rake test:functionals# 测试顺序随机# 对于每个text,将fixture中的数据写入test数据库,可以通过products方法来引用recordrequire 'test_helper'class ProductsControllerTest < ActionController::TestCase # 对于每个test,先setup setup do # 从test数据库获取一个product @product = products(:one) # 用于更新的hash@update = { title: 'Lorem Ipsum', description: 'Wibbles are fun!', image_url: 'lorem.jpg', price: 19.95 } # puts "@@@ Setup is called @@@" end # 测试index test "should get index" do get :index assert_response :success assert_not_nil assigns(:products) # fetch @products defined in controller. # puts "@@@ Test Index @@@" end test "should get new" do get :new assert_response :success assert_not_nil assigns(:product) # fetch @product defined in controller. # puts "@@@ Test New @@@" end test "should create product" do assert_difference('Product.count', +1) do # POST 动作,传参数使用了update的hash: post :create, product: @update end assert_redirected_to product_path(assigns(:product)) # puts "@@@ Test Create @@@" end test "should not create product" do assert_difference('Product.count', 0) do post :create, product: { description: @product.description, image_url: @product.image_url, price: @product.price, title: @product.title } end assert_response :success # puts "@@@ Test Not Create @@@" end test "should show product" do get :show, id: @product assert_response :success # puts "@@@ Test Show @@@" end test "should get edit" do get :edit, id: @product assert_response :success # puts "@@@ Test Edit @@@" end test "should not update product" do assert_difference('Product.count', 0) do patch :update, id: @product, product: { description: @product.description, image_url: @product.image_url, price: @product.price, title: @product.title } end assert_response :success # puts "@@@ Test Update @@@" end test "should update product" do assert_difference('Product.count', 0) do patch :update, id: @product, product: @update end assert_redirected_to product_path(assigns(:product)) # puts "@@@ Test Not Update @@@" end test "should destroy product" do assert_difference('Product.count', -1) do delete :destroy, id: @product end assert_redirected_to products_path # puts "@@@ Test Destroy @@@" endend
总结
综上所述,rake generate scaffold Product命令完成了以下功能:- 生成了名为Product 的Model Class(app/model/product.rb)
- 生成了名为producst的数据库(db/migrate/20151010000106_create_products.rb)
- 生成了Product的View(app/views/products)以及view的格式文件(app/assets/stylesheets)
- 生成了Product的Controller(app/controllers/products_controller.rb)
- 生成了Product Controller的test (test/controllers/product_controller_test.rb)
- 生成了Product Model的test (test/models/product_test.rb)
- 生成了fixture (test/fixtures/product.yml)
- 在route中捕获products动作(config/routes.rb)
数据库版本回退:
rake db:rollback
下面是不修改view的简明流程:
1.
rails new MyApp2.
cd MyApprails generate scaffold Product title:string description:text image_url:string price:decimal3. 修改db/migrate/*.rb文件
t.decimal :price, precision: 8, scale: 24.
rake db:migrate
rake db:migrate RAILS_ENV=test5.
rake test6. 修改db/seeds.rb
Product.delete_allProduct.create(title: 'CoffeeScript', description: %{<p> CoffeeScript is JavaScript done right. It provides all of JavaScript'sfunctionality wrapped in a cleaner, more succinct syntax. In the firstbook on this exciting new language, CoffeeScript guru Trevor Burnhamshows you how to hold onto all the power and flexibility of JavaScriptwhile writing clearer, cleaner, and safer code. </p>}, image_url: 'cs.jpg', price: 36.00)# . . .Product.create(title: 'Programming Ruby 1.9', description: %{<p> Ruby is the fastest growing and most exciting dynamic language out there. If you need to get working programs delivered fast, you should add Ruby to your toolbox. </p>}, image_url: 'ruby.jpg', price: 49.95)# . . .Product.create(title: 'Rails Test Prescriptions', description: %{<p> <em>Rails Test Prescriptions</em> is a comprehensive guide to testing Rails applications, covering Test-Driven Development from both a theoretical perspective (why to test) and from a practical perspective (how to test effectively). It covers the core Rails testing tools and procedures for Rails 2 and Rails 3, and introduces popular add-ons, including Cucumber, Shoulda, Machinist, Mocha, and Rcov. </p>}, image_url: 'rtp.jpg', price: 34.95)8.
rake db:seed
0 0
- Rails读书笔记第六章
- Rails读书笔记第二章
- Rails读书笔记第三章
- Rails读书笔记第四章
- Rails读书笔记第七章
- Rails读书笔记第七章
- Rails读书笔记第九章
- Python读书笔记-第六章
- 《影响力》读书笔记,第六章 权威
- 《deep learning》读书笔记------第六章
- ROR模型和数据库操作(第六章ruby on rails)
- 算法导论读书笔记 第六章 堆排序
- 长尾理论读书笔记:第六章 新市场
- C++ Primer 读书笔记 – 第六章
- C++ Primer 读书笔记 – 第六章
- C++primer读书笔记(第六,七章)
- 读书笔记:深入理解计算机系统 第六章
- learning android 读书笔记 第六章 (1)
- leetcode: Word Pattern
- 前端 ,后端 关于数据交互的问题
- Longest Palindromic Substring 最长回文子串
- Xcode7如何设置项目启动图片?
- 详解应用图标、启动图片和iPhone屏幕分辨率
- Rails读书笔记第六章
- sstream 小tips
- 设计模式-工厂模式
- C++ vector的用法小结
- leetcode 273: Integer to English Words
- ubuntu 14.04 安装scrapy
- 正则表达式口诀及教程(推荐)
- 怎样新学一门技术
- [LeetCode 289] Game of Life