rails 返回json_使用Rails 5构建RESTful JSON API-第一部分

本文是关于如何使用Rails 5构建仅API应用程序的教程,讲解了从项目设置、依赖管理到模型、控制器的创建,以及测试框架的配置。教程以一个待办事项API为例,介绍了如何实现RESTful端点,并提供了手动测试的方法。
摘要由CSDN通过智能技术生成

rails 返回json

Rails is popularly known for building web applications. Chances are if you're reading this you've built a traditional server-rendered web application with Rails before. If not, I'd highly recommend going through the Getting Started with Rails page to familiarize yourself with the Rails framework before proceeding with this tutorial.

Rails以构建Web应用程序而闻名。 如果您正在阅读本文,那么您之前可能已经使用Rails构建了传统的服务器渲染Web应用程序。 如果不是这样,我强烈建议您在继续本教程之前,先阅读Rails 入门页面以熟悉Rails框架。

As of version 5, Rails core now supports API only applications! In previous versions, we relied on an external gem: rails-api which has since been merged to core rails.

从版本5开始,Rails 核心现在支持仅API应用程序! 在以前的版本中,我们依赖于外部gem: rails-api ,此后已合并到核心rails中。

API only applications are slimmed down compared to traditional Rails web applications. According to Rails 5 release notes, generating an API only application will:

与传统的Rails Web应用程序相比,仅API的应用程序精简了。 根据Rails 5发行说明 ,生成仅API的应用程序将:

  • Start the application with a limited set of middleware

    使用一组有限的中间件启动应用程序
  • Make the ApplicationController inherit from ActionController::API instead of ActionController::Base

    使ApplicationController继承自ActionController::API而不是ActionController::Base
  • Skip generation of view files

    跳过视图文件的生成

This works to generate an API-centric framework excluding functionality that would otherwise be unused and unnecessary.

这可以生成一个以API为中心的框架,其中排除了本来不会使用和不必要的功能。

In this three-part tutorial, we'll build a todo list API where users can manage their to-do lists and todo items.

在这个分为三部分的教程中,我们将构建一个待办事项列表API,用户可以在其中管理自己的待办事项列表和待办事项。

先决条件 ( Prerequisites )

Before we begin, make sure you have ruby version >=2.2.2 and rails version 5.

在我们开始之前,请确保您具有的Ruby版本> = 2.2.2和Rails版本5。

$ ruby -v# ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin16]
$ rails -v # Rails 5.0.1

If your ruby version is not up to date, you can update it with a ruby version manager like rvm or rbenv.

如果您的Ruby版本不是最新的,则可以使用rvmrbenv之类的Ruby版本管理器对其进行 更新

# when using rbenv
$ rbenv install 2.3.1
# set 2.3.1 as the global version
$ rbenv global 2.3.1
# when using rvm
$ rvm install 2.3.1
# set 2.3.1 as the global version
$ rvm use 2.3.1

If your rails version is not up to date, update to the latest version by running:

如果您的rails版本不是最新的,请通过运行以下命令更新到最新版本:

$ gem update rails

All good? Let's get started!

都好? 让我们开始吧!

API端点 (API Endpoints)

Our API will expose the following RESTful endpoints.

我们的API将公开以下RESTful端点。

EndpointFunctionality
POST /signupSignup
POST /auth/loginLogin
GET /auth/logoutLogout
GET /todosList all todos
POST /todosCreate a new todo
GET /todos/:idGet a todo
PUT /todos/:idUpdate a todo
DELETE /todos/:idDelete a todo and its items
GET /todos/:id/itemsGet a todo item
PUT /todos/:id/itemsUpdate a todo item
DELETE /todos/:id/itemsDelete a todo item
终点 功能性
开机自检/注册 注册
POST /身份验证/登录 登录
GET / auth /登出 登出
GET /待办事项 列出所有待办事项
POST /待办事项 创建一个新的待办事项
GET / todos /:id 得到一个待办事项
放置/ todos /:id 更新待办事项
删除/ todos /:id 删除待办事项及其项目
GET / todos /:id / items 获取待办事项
放置/ todos /:id / items 更新待办事项
删除/ todos /:id / items 删除待办事项

Part One will Cover:

第一部分将涵盖:

  • Project setup

    项目设置
  • Todos API

    Todos API
  • TodoItems API

    TodoItems API

项目设置 ( Project Setup )

Generate a new project todos-api by running:

通过运行以下命令生成一个新项目todos-api

$ rails new todos-api --api -T

Note that we're using the --api argument to tell Rails that we want an API application and -T to exclude Minitest the default testing framework. Don't freak out, we're going to write tests. We'll be using RSpec instead to test our API. I find RSpec to be more expressive and easier to start with as compared to Minitest.

请注意,我们使用--api参数来告诉Rails我们希望API应用程序和-T排除Minitest默认测试框架。 不要害怕,我们要编写测试。 我们将改用RSpec来测试我们的API。 与Minitest相比,我发现RSpec更具表现力,并且更容易入门。

依存关系 (Dependencies)

Let's take a moment to review the gems that we'll be using.

让我们花点时间回顾一下我们将要使用的宝石。

  • rspec-rails - Testing framework.

    rspec-rails-测试框架。
  • factory_bot_rails - A fixtures replacement with a more straightforward syntax. You'll see.

    factory_bot_rails-使用更简单的语法替换灯具。 你会看到的。
  • shoulda_matchers - Provides RSpec with additional matchers.

    shoulda_matchers-为 RSpec提供附加的匹配器。
  • database_cleaner - You guessed it! It literally cleans our test database to ensure a clean state in each test suite.

    database_cleaner-您猜对了! 它从字面上清理我们的测试数据库,以确保每个测试套件中的清理状态。
  • faker - A library for generating fake data. We'll use this to generate test data.

    faker-用于生成伪造数据的库。 我们将使用它来生成测试数据。

All good? Great! Let's set them up. In your Gemfile:

都好? 大! 让我们设置它们。 在您的Gemfile

Add rspec-rails to both the :development and :test groups.

rspec-rails添加到:development:test组。

# Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.5'
end

This is a handy shorthand to include a gem in multiple environments.

这是在多个环境中包含宝石的便捷方式。

Add factory_bot_rails, shoulda_matchers, faker and database_cleaner to the :test group.

添加factory_bot_railsshoulda_matchersfakerdatabase_cleaner:test组。

# Gemfile
group :test do
  gem 'factory_bot_rails', '~> 4.0'
  gem 'shoulda-matchers', '~> 3.1'
  gem 'faker'
  gem 'database_cleaner'
end

Install the gems by running:

通过运行以下命令安装gem:

$ bundleinstall

Initialize the spec directory (where our tests will reside).

初始化spec目录(我们的测试将驻留在其中)。

$ rails generate rspec:install

This adds the following files which are used for configuration:

这将添加以下用于配置的文件:

  • .rspec

    .rspec
  • spec/spec_helper.rb

    spec/spec_helper.rb
  • spec/rails_helper.rb

    spec/rails_helper.rb

Create a factories directory (factory bot uses this as the default directory). This is where we'll define the model factories.

创建一个factories目录(工厂bot使用它作为默认目录)。 这是我们定义模型工厂的地方。

$mkdir spec/factories

组态 (Configuration)

In spec/rails_helper.rb

spec/rails_helper.rb

# require database cleaner at the top level
require 'database_cleaner'

# [...]
# configure shoulda matchers to use rspec as the test framework and full matcher libraries for rails
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

# [...]
RSpec.configure do |config|
  # [...]
  # add `FactoryBot` methods
  config.include FactoryBot::Syntax::Methods

  # start by truncating all the tables but then use the faster transaction strategy the rest of the time.
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    DatabaseCleaner.strategy = :transaction
  end

  # start the transaction strategy as examples are run
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end
  # [...]
end

Phew! That was a rather long. Good thing is, it's a smooth ride from here on out.

! 那是相当长的时间。 好东西是,从现在开始,一切都很顺利

楷模 ( Models )

Let's start by generating the Todo model

让我们从生成Todo模型开始

$ rails g model Todo title:string created_by:string

Notice that we've included the model attributes in the model generation command. This way we don't have to edit the migration file. The generator invokes active record and rspec to generate the migration, model, and spec respectively.

注意,我们已经在模型生成命令中包括了模型属性。 这样,我们不必编辑迁移文件。 生成器调用active recordrspec分别生成迁移,模型和规范。

# db/migrate/[timestamp]_create_todos.rb
class CreateTodos < ActiveRecord::Migration[5.0]
  def change
    create_table :todos do |t|
      t.string :title
      t.string :created_by

      t.timestamps
    end
  end
end

And now the Item model

现在是Item模型

$ rails g model Item name:string done:boolean todo:references

By adding todo:references we're telling the generator to set up an association with the Todo model. This will do the following:

通过添加todo:references我们告诉生成器建立与Todo模型的关联。 这将执行以下操作:

  • Add a foreign key column todo_id to the items table

    items表中添加外键列todo_id
  • Setup a belongs_to association in the Item model

    在Item模型中设置一个belongs_to关联
# db/migrate/[timestamp]_create_items.rb
class CreateItems < ActiveRecord::Migration[5.0]
  def change
    create_table :items do |t|
      t.string :name
      t.boolean :done
      t.references :todo, foreign_key: true

      t.timestamps
    end
  end
end

Looks good? Let's run the migrations.

看起来挺好的? 让我们运行迁移。

$ rails db:migrate

We're Test Driven, let's write the model specs first.

我们是测试驱动的,让我们首先编写模型规格。

# spec/models/todo_spec.rb
require 'rails_helper'

# Test suite for the Todo model
RSpec.describe Todo, type: :model do
  # Association test
  # ensure Todo model has a 1:m relationship with the Item model
  it { should have_many(:items).dependent(:destroy) }
  # Validation tests
  # ensure columns title and created_by are present before saving
  it { should validate_presence_of(:title) }
  it { should validate_presence_of(:created_by) }
end

RSpec has a very expressive DSL (Domain Specific Language). You can almost read the tests like a paragraph. Remember our shoulda matchers gem? It provides RSpec with the nifty association and validation matchers above.

RSpec具有非常有表现力的DSL(特定于域的语言)。 您几乎可以像阅读段落一样阅读测试。 还记得我们的平底锅配对宝石吗? 它为RSpec提供了上面的漂亮关联和验证匹配器。

# spec/models/item_spec.rb
require 'rails_helper'

# Test suite for the Item model
RSpec.describe Item, type: :model do
  # Association test
  # ensure an item record belongs to a single todo record
  it { should belong_to(:todo) }
  # Validation test
  # ensure column name is present before saving
  it { should validate_presence_of(:name) }
end

Let's execute the specs by running:

让我们通过运行以下规范来执行:

$ bundleexec rspec

And to no surprise, we have only one test passing and four failures. Let's go ahead and fix the failures.

毫不奇怪,我们只有一个测试通过和四个失败。 让我们继续修复故障。

# app/models/todo.rb
class Todo < ApplicationRecord
  # model association
  has_many :items, dependent: :destroy

  # validations
  validates_presence_of :title, :created_by
end
# app/models/item.rb
class Item < ApplicationRecord
  # model association
  belongs_to :todo

  # validation
  validates_presence_of :name
end

At this point run the tests again and...

此时,再次运行测试,然后...

voila! All green.

瞧! 全绿色。

控制器 ( Controllers )

Now that our models are all setup, let's generate the controllers.

现在我们的模型都已设置好,让我们生成控制器。

$ rails g controller Todos
$ rails g controller Items

You guessed it! Tests first... with a slight twist. Generating controllers by default generates controller specs. However, we won't be writing any controller specs. We're going to write request specs instead.

你猜到了! 首先进行测试...稍加扭转。 默认情况下,生成控制器会生成控制器规格。 但是,我们不会编写任何控制器规范。 我们将改为编写请求规范

Request specs are designed to drive behavior through the full stack, including routing. This means they can hit the applications' HTTP endpoints as opposed to controller specs which call methods directly. Since we're building an API application, this is exactly the kind of behavior we want from our tests.

请求规范旨在驱动整个协议栈(包括路由)中的行为。 这意味着它们可以访问应用程序的HTTP端点,而不是直接调用方法的控制器规范。 由于我们正在构建API应用程序,因此这正是我们想要从测试中获得的行为。

According to RSpec, the official recommendation of the Rails team and the RSpec core team is to write request specs instead.

根据RSpec的说法,Rails团队和RSpec核心团队的官方建议是编写请求规范。

Add a requests folder to the spec directory with the corresponding spec files.

requests文件夹和相应的规范文件添加到spec目录。

$mkdir spec/requests && touch spec/requests/{todos_spec.rb,items_spec.rb} 

Before we define the request specs, Let's add the model factories which will provide the test data.

在定义请求规范之前,让我们添加将提供测试数据的模型工厂。

Add the factory files:

添加出厂文件:

$touch spec/factories/{todos.rb,items.rb}

Define the factories.

定义工厂。

# spec/factories/todos.rb
FactoryBot.define do
  factory :todo do
    title { Faker::Lorem.word }
    created_by { Faker::Number.number(10) }
  end
end

By wrapping faker methods in a block, we ensure that faker generates dynamic data every time the factory is invoked. This way, we always have unique data.

通过将伪造者方法包装在一个块中,我们确保伪造者在每次调用工厂时都生成动态数据。 这样,我们始终拥有唯一的数据。

# spec/factories/items.rb
FactoryBot.define do
  factory :item do
    name { Faker::StarWars.character }
    done false
    todo_id nil
  end
end
待办事项API (Todo API)
# spec/requests/todos_spec.rb
require 'rails_helper'

RSpec.describe 'Todos API', type: :request do
  # initialize test data 
  let!(:todos) { create_list(:todo, 10) }
  let(:todo_id) { todos.first.id }

  # Test suite for GET /todos
  describe 'GET /todos' do
    # make HTTP get request before each example
    before { get '/todos' }

    it 'returns todos' do
      # Note `json` is a custom helper to parse JSON responses
      expect(json).not_to be_empty
      expect(json.size).to eq(10)
    end

    it 'returns status code 200' do
      expect(response).to have_http_status(200)
    end
  end

  # Test suite for GET /todos/:id
  describe 'GET /todos/:id' do
    before { get "/todos/#{todo_id}" }

    context 'when the record exists' do
      it 'returns the todo' do
        expect(json).not_to be_empty
        expect(json['id']).to eq(todo_id)
      end

      it 'returns status code 200' do
        expect(response).to have_http_status(200)
      end
    end

    context 'when the record does not exist' do
      let(:todo_id) { 100 }

      it 'returns status code 404' do
        expect(response).to have_http_status(404)
      end

      it 'returns a not found message' do
        expect(response.body).to match(/Couldn't find Todo/)
      end
    end
  end

  # Test suite for POST /todos
  describe 'POST /todos' do
    # valid payload
    let(:valid_attributes) { { title: 'Learn Elm', created_by: '1' } }

    context 'when the request is valid' do
      before { post '/todos', params: valid_attributes }

      it 'creates a todo' do
        expect(json['title']).to eq('Learn Elm')
      end

      it 'returns status code 201' do
        expect(response).to have_http_status(201)
      end
    end

    context 'when the request is invalid' do
      before { post '/todos', params: { title: 'Foobar' } }

      it 'returns status code 422' do
        expect(response).to have_http_status(422)
      end

      it 'returns a validation failure message' do
        expect(response.body)
          .to match(/Validation failed: Created by can't be blank/)
      end
    end
  end

  # Test suite for PUT /todos/:id
  describe 'PUT /todos/:id' do
    let(:valid_attributes) { { title: 'Shopping' } }

    context 'when the record exists' do
      before { put "/todos/#{todo_id}", params: valid_attributes }

      it 'updates the record' do
        expect(response.body).to be_empty
      end

      it 'returns status code 204' do
        expect(response).to have_http_status(204)
      end
    end
  end

  # Test suite for DELETE /todos/:id
  describe 'DELETE /todos/:id' do
    before { delete "/todos/#{todo_id}" }

    it 'returns status code 204' do
      expect(response).to have_http_status(204)
    end
  end
end

We start by populating the database with a list of 10 todo records (thanks to factory bot). We also have a custom helper method json which parses the JSON response to a Ruby Hash which is easier to work with in our tests. Let's define it in spec/support/request_spec_helper.

我们首先用10个待办事项记录的列表填充数据库(这要归功于工厂bot)。 我们还有一个自定义的辅助方法json ,用于解析对Ruby Hash的JSON响应,这在我们的测试中更易于使用。 让我们在spec/support/request_spec_helper定义它。

Add the directory and file:

添加目录和文件:

$mkdir spec/support && touch spec/support/request_spec_helper.rb
# spec/support/request_spec_helper
module RequestSpecHelper
  # Parse JSON response to ruby hash
  def json
    JSON.parse(response.body)
  end
end

The support directory is not autoloaded by default. To enable this, open the rails helper and comment out the support directory auto-loading and then include it as shared module for all request specs in the RSpec configuration block.

默认情况下,不会自动加载支持目录。 为此,请打开rails助手,并注释掉支持目录的自动加载,然后将其作为RSpec配置块中所有请求规范的共享模块。

# spec/rails_helper.rb
# [...]
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# [...]
RSpec.configuration do |config|
  # [...]
  config.include RequestSpecHelper, type: :request
  # [...]
end

Run the tests.

运行测试。

We get failing routing errors. This is because we haven't defined the routes yet. Go ahead and define them in config/routes.rb.

我们收到失败的路由错误。 这是因为我们尚未定义路线。 继续并在config/routes.rb定义它们。

# config/routes.rb
Rails.application.routes.draw do
  resources :todos do
    resources :items
  end
end

In our route definition, we're creating todo resource with a nested items resource. This enforces the 1:m (one to many) associations at the routing level. To view the routes, you can run:

在路线定义中,我们使用嵌套项资源创建待办事项资源。 这将在路由级别强制执行1:m(一对多)关联。 要查看路线,可以运行:

$ rails routes

When we run the tests we see that the routing error is gone. As expected we have controller failures. Let's go ahead and define the controller methods.

运行测试时,我们发现路由错误已消失。 不出所料,我们出现控制器故障。 让我们继续定义控制器方法。

# app/controllers/todos_controller.rb
class TodosController < ApplicationController
  before_action :set_todo, only: [:show, :update, :destroy]

  # GET /todos
  def index
    @todos = Todo.all
    json_response(@todos)
  end

  # POST /todos
  def create
    @todo = Todo.create!(todo_params)
    json_response(@todo, :created)
  end

  # GET /todos/:id
  def show
    json_response(@todo)
  end

  # PUT /todos/:id
  def update
    @todo.update(todo_params)
    head :no_content
  end

  # DELETE /todos/:id
  def destroy
    @todo.destroy
    head :no_content
  end

  private

  def todo_params
    # whitelist params
    params.permit(:title, :created_by)
  end

  def set_todo
    @todo = Todo.find(params[:id])
  end
end

More helpers. Yay! This time we have:

更多帮手。 好极了! 这次我们有:

  • json_response which does... yes, responds with JSON and an HTTP status code (200 by default). We can define this method in concerns folder.

    json_response可以...是的,以JSON和HTTP状态代码(默认为200)响应。 我们可以在关注文件夹中定义此方法。
# app/controllers/concerns/response.rb
module Response
  def json_response(object, status = :ok)
    render json: object, status: status
  end
end
  • set_todo - callback method to find a todo by id. In the case where the record does not exist, ActiveRecord will throw an exception ActiveRecord::RecordNotFound. We'll rescue from this exception and return a 404 message.

    set_todo通过id查找待办事项的回调方法。 在记录不存在的情况下,ActiveRecord将引发异常ActiveRecord::RecordNotFound 。 我们将从这个异常中解救出来并返回404消息。
# app/controllers/concerns/exception_handler.rb
module ExceptionHandler
  # provides the more graceful `included` method
  extend ActiveSupport::Concern

  included do
    rescue_from ActiveRecord::RecordNotFound do |e|
      json_response({ message: e.message }, :not_found)
    end

    rescue_from ActiveRecord::RecordInvalid do |e|
      json_response({ message: e.message }, :unprocessable_entity)
    end
  end
end

In our create method in the TodosController, note that we're using create! instead of create. This way, the model will raise an exception ActiveRecord::RecordInvalid. This way, we can avoid deep nested if statements in the controller. Thus, we rescue from this exception in the ExceptionHandler module.

TodosController create方法中,请注意,我们正在使用create! 而不是create 。 这样,模型将引发异常ActiveRecord::RecordInvalid 。 这样,我们可以避免在控制器中嵌套深层的if语句。 因此,我们从ExceptionHandler模块中的此异常中ExceptionHandler

However, our controller classes don't know about these helpers yet. Let's fix that by including these modules in the application controller.

但是,我们的控制器类尚不了解这些帮助器。 让我们通过在应用程序控制器中包含这些模块来解决此问题。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include Response
  include ExceptionHandler
end

Run the tests and everything's all green!

运行测试,一切都变成绿色!

Let's fire up the server for some good old manual testing.

让我们启动服务器以进行一些良好的旧的手动测试。

$ rails s

Now let's go ahead and make requests to the API. I'll be using httpie as my HTTP client.

现在,让我们继续向API发出请求。 我将使用httpie作为HTTP客户端。

# GET /todos
$ http :3000/todos
# POST /todos
$ http POST :3000/todos title=Mozart created_by=1
# PUT /todos/:id
$ http PUT :3000/todos/1 title=Beethoven
# DELETE /todos/:id
$ http DELETE :3000/todos/1

You should see similar output.

您应该看到类似的输出。

TodoItems API (TodoItems API)
# spec/requests/items_spec.rb
require 'rails_helper'

RSpec.describe 'Items API' do
  # Initialize the test data
  let!(:todo) { create(:todo) }
  let!(:items) { create_list(:item, 20, todo_id: todo.id) }
  let(:todo_id) { todo.id }
  let(:id) { items.first.id }

  # Test suite for GET /todos/:todo_id/items
  describe 'GET /todos/:todo_id/items' do
    before { get "/todos/#{todo_id}/items" }

    context 'when todo exists' do
      it 'returns status code 200' do
        expect(response).to have_http_status(200)
      end

      it 'returns all todo items' do
        expect(json.size).to eq(20)
      end
    end

    context 'when todo does not exist' do
      let(:todo_id) { 0 }

      it 'returns status code 404' do
        expect(response).to have_http_status(404)
      end

      it 'returns a not found message' do
        expect(response.body).to match(/Couldn't find Todo/)
      end
    end
  end

  # Test suite for GET /todos/:todo_id/items/:id
  describe 'GET /todos/:todo_id/items/:id' do
    before { get "/todos/#{todo_id}/items/#{id}" }

    context 'when todo item exists' do
      it 'returns status code 200' do
        expect(response).to have_http_status(200)
      end

      it 'returns the item' do
        expect(json['id']).to eq(id)
      end
    end

    context 'when todo item does not exist' do
      let(:id) { 0 }

      it 'returns status code 404' do
        expect(response).to have_http_status(404)
      end

      it 'returns a not found message' do
        expect(response.body).to match(/Couldn't find Item/)
      end
    end
  end

  # Test suite for PUT /todos/:todo_id/items
  describe 'POST /todos/:todo_id/items' do
    let(:valid_attributes) { { name: 'Visit Narnia', done: false } }

    context 'when request attributes are valid' do
      before { post "/todos/#{todo_id}/items", params: valid_attributes }

      it 'returns status code 201' do
        expect(response).to have_http_status(201)
      end
    end

    context 'when an invalid request' do
      before { post "/todos/#{todo_id}/items", params: {} }

      it 'returns status code 422' do
        expect(response).to have_http_status(422)
      end

      it 'returns a failure message' do
        expect(response.body).to match(/Validation failed: Name can't be blank/)
      end
    end
  end

  # Test suite for PUT /todos/:todo_id/items/:id
  describe 'PUT /todos/:todo_id/items/:id' do
    let(:valid_attributes) { { name: 'Mozart' } }

    before { put "/todos/#{todo_id}/items/#{id}", params: valid_attributes }

    context 'when item exists' do
      it 'returns status code 204' do
        expect(response).to have_http_status(204)
      end

      it 'updates the item' do
        updated_item = Item.find(id)
        expect(updated_item.name).to match(/Mozart/)
      end
    end

    context 'when the item does not exist' do
      let(:id) { 0 }

      it 'returns status code 404' do
        expect(response).to have_http_status(404)
      end

      it 'returns a not found message' do
        expect(response.body).to match(/Couldn't find Item/)
      end
    end
  end

  # Test suite for DELETE /todos/:id
  describe 'DELETE /todos/:id' do
    before { delete "/todos/#{todo_id}/items/#{id}" }

    it 'returns status code 204' do
      expect(response).to have_http_status(204)
    end
  end
end

As expected, running the tests at this point should output failing todo item tests. Let's define the todo items controller.

不出所料,此时运行测试应输出失败的待办事项测试。 让我们定义待办事项控制器。

# app/controllers/items_controller.rb
class ItemsController < ApplicationController
  before_action :set_todo
  before_action :set_todo_item, only: [:show, :update, :destroy]

  # GET /todos/:todo_id/items
  def index
    json_response(@todo.items)
  end

  # GET /todos/:todo_id/items/:id
  def show
    json_response(@item)
  end

  # POST /todos/:todo_id/items
  def create
    @todo.items.create!(item_params)
    json_response(@todo, :created)
  end

  # PUT /todos/:todo_id/items/:id
  def update
    @item.update(item_params)
    head :no_content
  end

  # DELETE /todos/:todo_id/items/:id
  def destroy
    @item.destroy
    head :no_content
  end

  private

  def item_params
    params.permit(:name, :done)
  end

  def set_todo
    @todo = Todo.find(params[:todo_id])
  end

  def set_todo_item
    @item = @todo.items.find_by!(id: params[:id]) if @todo
  end
end

Run the tests.

运行测试。

Run some manual tests for the todo items API:

对待办事项API运行一些手动测试:

# GET /todos/:todo_id/items
$ http :3000/todos/2/items
# POST /todos/:todo_id/items
$ http POST :3000/todos/2/items name='Listen to 5th Symphony' done=false
# PUT /todos/:todo_id/items/:id
$ http PUT :3000/todos/2/items/1 done=true
# DELETE /todos/:todo_id/items/1
$ http DELETE :3000/todos/2/items/1

结论 ( Conclusion )

That's it for part one! At this point you should have learned how to:

这就是第一部分! 此时,您应该已经学会了如何:

  • Generate an API application with Rails 5

    使用Rails 5生成API应用程序
  • Setup RSpec testing framework with Factory Bot, Database Cleaner, Shoulda Matchers and Faker.

    使用Factory BotDatabase CleanerShoulda MatchersFaker设置RSpec测试框架。
  • Build models and controllers with TDD (Test Driven Development).

    使用TDD(测试驱动开发)构建模型和控制器。
  • Make HTTP requests to an API with httpie.

    使用httpie向API 发出 HTTP请求。

In the next part, we'll cover authentication with JWT, pagination, and API versioning. Hope to see you there. Cheers!

在下一部分中 ,我们将介绍JWT的 authenticationpagination和API versioning 。 希望在那里见到你。 干杯!

翻译自: https://scotch.io/tutorials/build-a-restful-json-api-with-rails-5-part-one

rails 返回json

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值