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:decimal
Rails生成一个脚手架,名叫Product,包含了title类型为string等属性。SQLite的一些数据类型定义如下:
  • string: limited length
  • text: unlimited length
  • integer:
  • float
  • decimal: precision(从最高位开始的有效整数个数,多余的置0),scale(小数位数)
  • boolean 
  • :primary_key
  • date
  • time
  • timestamp
scaffold会默认生成table的integer类型的primary key,名叫id。
在数据库中新建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的内容如下:
<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 %> 注释
此时在http://localhost:3000/products/new即可创建新的product,并在首页显示出来。

运行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

在test/controllers/product_controller_test.rb文件中定义了对controller的测试:
# 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 MyApp
2. 
cd MyApprails generate scaffold Product title:string description:text image_url:string price:decimal
3. 修改db/migrate/*.rb文件
t.decimal :price, precision: 8, scale: 2
4. 
rake db:migrate
rake db:migrate RAILS_ENV=test
5.
rake test
6. 修改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
原创粉丝点击