Skip to content

快速实现后端接口

快速实现读取API

get /api/v1/tags


  1. 创建model

进入ruby3的nvm use 3,然后在执行创建model,bin/rails g model tag user:references name:string sign:string deleted_at:datetimee

ruby
class CreateTags < ActiveRecord::Migration[7.0]
  def change
    create_table :tags do |t|
      # foreign_key为外键
      t.references :user, null: false, foreign_key: false
      t.string :name, null: false
      t.string :sign, null: false
      t.datetime :deleted_at

      t.timestamps
    end
  end
end

修改好表结构后在执行bin/rails db:migrate,然后可以在schema.rb文件查看该表结构。

  1. 创建controller——执行bin/rails g controller api/v1/tags_controller
  2. 写测试
ruby
require 'rails_helper'

RSpec.describe "Api::V1::Tags", type: :request do
  describe "获取标签列表" do
    it "未登录获取标签" do
      get '/api/v1/tags'
      expect(response).to have_http_status(401)
    end
    it "登录后获取标签" do
      user = User.create email: '1@qq.com'
      another_user = User.create email: '2@qq.com'
      # 创建 11 个 tag
      11.times do |i| Tag.create name: "tag#{i}", user_id: user.id, sign: 'x' end
      11.times do |i| Tag.create name: "tag#{i}", user_id: another_user.id, sign: 'x' end

      get '/api/v1/tags', headers: user.generate_auth_header
      expect(response).to have_http_status(200)
      json = JSON.parse response.body
      expect(json['resources'].size).to eq 10

      get '/api/v1/tags', headers: user.generate_auth_header, params: {page: 2}
      expect(response).to have_http_status(200)
      json = JSON.parse response.body
      expect(json['resources'].size).to eq 1

    end
  end
end
  1. 写代码
ruby
class Api::V1::TagsController < ApplicationController
  def index
    current_user = User.find request.env['current_user_id']
    return render status: :not_found if current_user.nil?
    tags = Tag.where(user_id: current_user).page(params[:page])
    render json: { resources: tags, pager: {
      page: params[:page] || 1,
      per_page: Tag.default_per_page,
      count: Tag.count
    }}
  end
end
  1. 写文档
ruby
require 'rails_helper'
require 'rspec_api_documentation/dsl'

resource "标签" do
  get "/api/v1/tags" do
    authentication :basic, :auth
    parameter :page, '页码'
    with_options :scope => :resources do
      response_field :id, 'ID'
      response_field :name, '名称'
      response_field :sign, '符号'
      response_field :user_id, '用户ID'
      response_field :deleted_at, '删除时间'
    end
    let(:current_user) { User.create email: '1@qq.com' }
    let(:auth) { "Bearer #{current_user.generate_jwt}" }
    example "获取标签列表" do
      11.times do Tag.create name: 'x', sign: 'x', user_id: current_user.id end
      do_request
      expect(status).to eq 200
      json = JSON.parse response_body
      expect(json['resources'].size).to eq 10
    end
  end
end

快速实现创建API


success post /api/v1/tags


由于tags的controller和model已经在第一个API内创建好了,无需创建,省略1,2两步

  1. 创建model

运行db:migrate

  1. 创建controller
  2. 写测试
javascript
require 'rails_helper'

RSpec.describe "Api::V1::Tags", type: :request do
  describe "创建标签" do
    it '未登录创建标签' do
      post '/api/v1/tags', params: { name: 'x', sign: 'x' }
      expect(response).to have_http_status(401)
    end
    it '登录后创建标签' do
      user = User.create email: '1@qq.com'
      post '/api/v1/tags', params: { name: 'name', sign: 'sign' }, headers: user.generate_auth_header
      expect(response).to have_http_status(200)
      json = JSON.parse response.body
      expect(json['resource']['name']).to eq 'name'
      expect(json['resource']['sign']).to eq 'sign'
    end
    it '登录后创建标签失败,因为没有填写name' do
      user = User.create email: '1@qq.com'
      post '/api/v1/tags/', params: { sign:'sign' }, headers: user.generate_auth_header
      expect(response).to have_http_status 422
      json = JSON.parse response.body
      expect(json['errors']['name'][0]).to eq "can't be blank"
    end
    it '登录后创建标签失败,因为没有填写sign' do
      user = User.create email: '1@qq.com'
      post '/api/v1/tags/', params: { name:'name' }, headers: user.generate_auth_header
      expect(response).to have_http_status 422
      json = JSON.parse response.body
      expect(json['errors']['sign'][0]).to eq "can't be blank"
    end
  end
end
  1. 写代码

由于直接Tag.create 不传参数的时候数据库会报错,分部进行——先创建,后保存。成功时候返回200,ruby中代替为:ok;失败的时候返回status状态码为422,ruby中代替为:unprocessable_entity(即不可创建的实体,详细见MDN)。

ruby
class Api::V1::TagsController < ApplicationController
  def create
    current_user = User.find request.env['current_user_id']
    return render status: 401 if current_user.nil?

    tag = Tag.new name: params[:name], sign: params[:sign], user_id: current_user.id
    # 由于直接Tag.create 不传参数的时候数据库会报错,分部进行——先创建,后保存
    if tag.save
      render json: { resource: tag }, status: :ok
    else
      render json: { errors: tag.errors }, status: :unprocessable_entity
    end
  end
end

由于tag的两个参数必须是必填项则需要在models层修改为必填项

ruby
class Tag < ApplicationRecord
  validates :name, presence: true
  validates :sign, presence: true
  belongs_to :user
end
  1. 写文档
ruby
require 'rails_helper'
require 'rspec_api_documentation/dsl'

resource "标签" do
  authentication :basic, :auth
  let(:current_user) { User.create email: '1@qq.com' }
  let(:auth) { "Bearer #{current_user.generate_jwt}" }
  post "/api/v1/tags" do
    parameter :name, '名称', required: true
    parameter :sign, '符号', required: true
    with_options :scope => :resources do
      response_field :id, 'ID'
      response_field :name, '名称'
      response_field :sign, '符号'
      response_field :user_id, '用户ID'
      response_field :deleted_at, '删除时间'
    end
    let (:name) { 'name' }
    let (:sign) { 'sign' }
    example "创建标签" do
      do_request
      expect(status).to eq 200
      json = JSON.parse response_body
      expect(json['resource']['name']).to eq 'name'
      expect(json['resource']['sign']).to eq 'sign'
    end
  end
end

快速实现更新API


color2 patch /api/v1/tags/:id


由于tags的controller和model已经在第一个API内创建好了,无需创建,省略1,2两步

  1. 创建model

运行db:migrate

  1. 创建controller
  2. 写测试
ruby
require 'rails_helper'

RSpec.describe "Api::V1::Tags", type: :request do
  describe "更新标签" do
    it '未登录修改标签' do
      user = User.create email: '1@qq.com'
      tag = Tag.create name:'x', sign:'x', user_id: user.id
      patch "/api/v1/tags/#{tag.id}", params: { name: 'x', sign: 'y' }
      expect(response).to have_http_status(401)
    end
    it '登录后修改标签' do
      user = User.create email: '1@qq.com'
      tag = Tag.create name:'x', sign:'x', user_id: user.id
      patch "/api/v1/tags/#{tag.id}", params: { name: 'y', sign: 'y' }, headers: user.generate_auth_header
      expect(response).to have_http_status(200)
      json = JSON.parse response.body
      expect(json['resource']['name']).to eq 'y'
      expect(json['resource']['sign']).to eq 'y'
    end
    it '登录后部分修改标签' do
      user = User.create email: '1@qq.com'
      tag = Tag.create name:'x', sign:'x', user_id: user.id
      patch "/api/v1/tags/#{tag.id}", params: { name: 'y' }, headers: user.generate_auth_header
      expect(response).to have_http_status(200)
      json = JSON.parse response.body
      expect(json['resource']['name']).to eq 'y'
      expect(json['resource']['sign']).to eq 'x'
    end
  end
end
  1. 写代码

find方法是会报错的,具体详情可以查看链接

ruby有一个permit方法可以取非空的值,他会返回一个Hash值。

**errors.nil?**只判空**errors.empty?**还会去查看数组长度是否为0

ruby
class Api::V1::TagsController < ApplicationController
  def update
    # find 如果找不到是会报错的
    tag = Tag.find params[:id]
    # permit即在参数查找name和sign的key和值返回一个哈希值
    tag.update params.permit(:name, :sign)
    # 如果tag能够查找到且能够更新则返回tag,反正返回错误
    if tag.errors.empty?
      render json: { resource: tag }
    else
      render json: { errors: tag.errors }, status: :unprocessable_entity
    end
  end
end
  1. 写文档
ruby
require "rails_helper"
require "rspec_api_documentation/dsl"

resource "标签" do
  authentication :basic, :auth
  let(:current_user) { User.create email: "1@qq.com" }
  let(:auth) { "Bearer #{current_user.generate_jwt}" }
  patch "/api/v1/tags/:id" do
    let (:tag) { Tag.create name: "x", sign: "x", user_id: current_user.id }
    let (:id) { tag.id }
    parameter :name, "名称"
    parameter :sign, "符号"
    with_options :scope => :resource do
      response_field :id, "ID"
      response_field :name, "名称"
      response_field :sign, "符号"
      response_field :user_id, "用户ID"
      response_field :deleted_at, "删除时间"
    end
    let (:name) { "y" }
    let (:sign) { "y" }
    example "修改标签" do
      do_request
      expect(status).to eq 200
      json = JSON.parse response_body
      expect(json["resource"]["name"]).to eq name
      expect(json["resource"]["sign"]).to eq sign
    end
  end
end

快速实现删除API


danger delete /api/v1/tags/:id


由于tags的controller和model已经在第一个API内创建好了,无需创建,省略1,2两步

  1. 创建model

运行db:migrate

  1. 创建controller
  2. 写测试

删除后要重新获取对象的信息可以使用reload。状态码403 Forbidden代表客户端错误,服务器有能力处理该请求,但是拒绝授权访问,具体内容可以查看MDN

ruby
require 'rails_helper'

RSpec.describe "Api::V1::Tags", type: :request do
  describe "删除标签" do
    it "未登录删除标签" do
      user = User.create email: '1@qq.com'
      tag = Tag.create name: 'x', sign: 'x', user_id: user.id
      delete "/api/v1/tags/#{tag.id}"
      expect(response).to have_http_status 401
    end
    it "登录后删除标签" do
      user = User.create email: '1@qq.com'
      tag = Tag.create name: 'x', sign: 'x', user_id: user.id
      delete "/api/v1/tags/#{tag.id}", headers: user.generate_auth_header
      expect(response).to have_http_status 200
      # 删除后要重新获取一下数据库的信息
      # tag = Tag.find tag.id
      tag.reload
      expect(tag.deleted_at).not_to eq nil
    end
    it "登录后删除别人的标签" do
      user = User.create email: '1@qq.com'
      other = User.create email: '2@qq.com'
      tag = Tag.create name: 'x', sign: 'x', user_id: other.id
      delete "/api/v1/tags/#{tag.id}", headers: user.generate_auth_header
      expect(response).to have_http_status 403
    end
  end
end
  1. 写代码

这里的删除是软删除,软删除又叫逻辑删除,在数据库中设置一个字段来表示删除状态;硬删除才是传统的物理删除,真正的从数据库中删除。如下代码段中软删除只是更新了tagdelete_at的字段。:forbidden表示状态码403 Forbidden

ruby
class Api::V1::TagsController < ApplicationController  
  def destroy
    tag = Tag.find params[:id]
    return head :forbidden unless tag.user_id == request.env['current_user_id']
    tag.deleted_at = Time.now
    if tag.save
      head 200
    else
      render json: { errors: tag.errors }, status: :unprocessable_entity
    end
  end
end
  1. 写文档
ruby
require "rails_helper"
require "rspec_api_documentation/dsl"

resource "标签" do
  authentication :basic, :auth
  let(:current_user) { User.create email: "1@qq.com" }
  let(:auth) { "Bearer #{current_user.generate_jwt}" }
  delete "/api/v1/tags/:id" do 
    let (:tag) { Tag.create name: "x", sign: "x", user_id: current_user.id }
    let (:id) { tag.id }
    example "删除标签" do
      do_request
      expect(status).to eq 200
    end
  end
end

快速实现单个读取API


color1 get /api/v1/tags/:id


由于tags的controller和model已经在第一个API内创建好了,无需创建,省略1,2两步

  1. 创建model

运行db:migrate

  1. 创建controller
  2. 写测试
ruby
require 'rails_helper'

RSpec.describe "Api::V1::Tags", type: :request do
  describe "获取标签" do
    it "未登录获取标签" do
      user = User.create email: '1@qq.com'
      tag = Tag.create name: 'x', sign: 'x', user_id: user.id
      get "/api/v1/tags/#{tag.id}"
      expect(response).to have_http_status 401
    end
    it "登录后获取标签" do
      user = User.create email: '1@qq.com'
      tag = Tag.create name: 'tag1', sign: 'x', user_id: user.id
      get "/api/v1/tags/#{tag.id}", headers: user.generate_auth_header
      expect(response).to have_http_status 200
      json = JSON.parse response.body
      expect(json['resource']['id']).to eq tag.id
    end
    it "登录后获取不属于自己的标签" do
      user = User.create email: '1@qq.com'
      another_user = User.create email: '2@qq.com'
      tag = Tag.create name: 'tag1', sign: 'x', user_id: another_user.id
      get "/api/v1/tags/#{tag.id}", headers: user.generate_auth_header
      expect(response).to have_http_status 403
    end
  end
end
  1. 写代码
ruby
class Api::V1::TagsController < ApplicationController
  def show
    tag = Tag.find params[:id]
    return head :forbidden unless tag.user_id == request.env['current_user_id']
    render json: { resource: tag }
  end
end
  1. 写文档
ruby
require "rails_helper"
require "rspec_api_documentation/dsl"

resource "标签" do
  authentication :basic, :auth
  let(:current_user) { User.create email: "1@qq.com" }
  let(:auth) { "Bearer #{current_user.generate_jwt}" }
  get "/api/v1/tags/:id" do
    let (:tag) { Tag.create name: "x", sign: "x", user_id: current_user.id }
    let (:id) { tag.id }
    with_options :scope => :resources do
      response_field :id, "ID"
      response_field :name, "名称"
      response_field :sign, "符号"
      response_field :user_id, "用户ID"
      response_field :deleted_at, "删除时间"
    end
    example "获取标签" do
      do_request
      expect(status).to eq 200
      json = JSON.parse response_body
      expect(json['resource']['id']).to eq tag.id
    end
  end
end