一本被人反复提及的书《哪儿来的天才》中说过,大部分所谓的天才都是通过反复的刻意练习得到的。当你的练习时间达到10000小时以后,你就会成为该领域的专家。
最近在学习rails如何实现RESTful Web Service。自己想给自己设计一个练习的模板,进行反复练习。开发过程采用的是TDD的方式进行开发。
练习背景:
我们涉及三个Domain对象,Products、Orders、Payment
1.新建工程rails-rest-practice
rails new rails-rest-practice
cd !$
bundle install
2.安装rspec-rails
在Gemfile中添加
gem "rspec-rails", :group => [:development, :test]
然后
bundle install
rails g rspec:install
在.rspec 中去掉 --warnings
3.GET /products => user get list of products
step 1:创建controller,返回HTTP Status 200
user products api如下:
GET /products user get list of products
创建文件:spec/products/products_controller_spec.rb
require ‘rails_helper‘
describe ProductsController, :type => :controller do
   describe ‘products controller‘ do
      it ‘get index of products‘ do
         get :index
         expect(response).to have_http_status(200)
      end
   end
end
have_http_status:http://rubydoc.info/gems/rspec-rails/RSpec/Rails/Matchers#have_http_status-instance_method
创建文件:app/controllers/products_controller.rb
class ProductsController < ApplicationController
   def index
   end
end
运行rake spec,得到错误:
     ActionController::UrlGenerationError:
       No route matches {:action=>"index", :controller=>"products"}
配置相关config/routes.rb
resources :products do
   collection do
      get :index
   end
end
运行rake spec,得到错误:
Failure/Error: get :index
     ActionView::MissingTemplate:
修改app/controllers/products_controller.rb
class ProductsController < ApplicationController
   def index
      render :nothing => true
   end
end
这样就完成我们的第一个步骤,虽然看似这个步骤什么都没测,其实不然,在这一步中,我们搭建好了routes,同时创建了必要的controller类和其对应的方法。
step 2:返回JSON
安装rabl
在Gemfile中添加rabl
gem ‘rabl‘
bundle install
修改测试:spec/products/products_controller_spec.rb
render_views
describe ‘products controller‘ do
   before(:all) do
      @products = [
         Product.new({:id => 1, :name => ‘apple juice‘, :description => ‘good‘}),
         Product.new({:id => 2, :name => ‘banana juice‘, :description => ‘just so so‘})
      ]
   end
   it ‘get index of products‘ do
      expect(Product).to receive(:all).and_return(@products).once
      get :index, {:format => :json}
      expect(response).to have_http_status(200)
      products_json = JSON.parse(response.body)
      expect(products_json.size).to eq(2)
   end
end
运行测试rake spec
得到错误:
NameError:
       uninitialized constant Product
创建model Product:
rails g model Product name:string description:text
rake db:migrate
运行测试rake spec
得到错误:
    Failure/Error: products_json = JSON.parse(response.body)
     JSON::ParserError:
       A JSON text must at least contain two octets!
这是因为我们的response不对,并且我们没有配置怎么获取json格式的输出。
创建文件: app/views/products/index.json.rabl
collection @products, :object_root => false
attributes :name
再次运行测试rake spec,测试通过
step3: 添加更多的字段
在 spec/products/products_controller_spec.rb中
products_json = JSON.parse(response.body)
expect(products_json.size).to eq(2)
expect(products_json[0][‘id‘]).to eq(1)
expect(products_json[1][‘id‘]).to eq(2)
expect(products_json[0][‘name‘]).to eq(‘apple juice‘)
expect(products_json[1][‘name‘]).to eq(‘banana juice‘)
expect(products_json[0][‘description‘]).to eq(‘good‘)
expect(products_json[1][‘description‘]).to eq(‘just so so‘)
expect(products_json[0][‘uri‘]).to end_with(‘/products/1‘)
expect(products_json[1][‘uri‘]).to end_with(‘/products/2‘)
在app/views/products/index.json.rabl中
collection @products, :object_root=>false
attributes :id, :name, :description
node :uri do |product|
   product_url product
end
4.GET /products => user get a product of specified id
step 1: 创建对应的controller方法,返回HTTP 200
添加测试:spec/products/products_controller_spec.rb
it ‘get product by product id‘ do
   get :show, {:id => 1}
   expect(response).to have_http_status(200)
end
对应修改:app/controllers/products_controller.rb
def show
   render :nothing => true
end
对应修改:config/routes.rb
resources :products do
   collection do
      get :index
   end
   member do
      get :show
   end
end
rake spec测试通过
step 2:创建对应的JSON显示
添加测试:spec/products/products_controller_spec.rb
before(:all) do
   #... ...
   @product = Product.new({:id => 1, :name => ‘apple juice‘, :description => ‘good‘})
end
it ‘get product by product id‘ do
   expect(Product).to receive(:find).with(1).and_return(@product).once
   get :show, {:id => 1, :format => :json}
   expect(response).to have_http_status(200)
   product_json = JSON.parse(response.body)
   expect(product_json[‘id‘]).to eq(1)
   expect(product_json[‘name‘]).to eq(‘apple juice‘)
   expect(product_json[‘description‘]).to eq(‘good‘)
   expect(product_json[‘uri‘]).to end_with(‘/products/1‘)
end
对应修改:app/controllers/products_controller.rb
def show
   @product = Product.find(params[:id].to_i)
end
Q:params[:id].to_i,为什么这里从测试代码过来的params[:id]它使一个string类型呢
添加JSON显示:app/views/products/show.json.rabl
object false
node(:id) { |product| @product.id }
node(:name) { |product| @product.name }
node(:description) { |product| @product.description }
node(:uri) { |product| product_url @product }
运行测试,通过
step 3:重构rabl
修改app/views/products/show.json.rabl
object @product
attributes :id, :name, :description
node(:uri) { |product| product_url product }
修改app/views/products/index.json.rabl
collection @products
extends ‘products/show‘
配置rabl:创建文件config/initializers/rabl_config.rb
Rabl.configure do |config|
   config.include_json_root = false
end
运行测试,通过,这样减少了rabl间的重复代码
step 4:HTTP 404
添加测试:spec/products/products_controller_spec.rb
it ‘get 404 when product not found‘ do
   expect(Product).to receive(:find).with(100).and_raise(ActiveRecord::RecordNotFound)
   get :show, {:id => 100, :format => :json}
   expect(response).to have_http_status(404)
end
对应修改:
class ProductsController < ApplicationController
   rescue_from ActiveRecord::RecordNotFound, with: :product_not_found
   #... ... 
   def show
      @product = Product.find(params[:id])
   end
   protected
      def product_not_found
         response.status = :not_found
      end
end
(更新中,欢迎指教)
将会要修改的部分是如何写好rspec,参考:http://betterspecs.org/
刻意练习--Rails RESTful(一),布布扣,bubuko.com
原文:http://blog.csdn.net/kiwi_coder/article/details/36057491