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 fromActionController::API
instead ofActionController::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版本不是最新的,则可以使用rvm或rbenv之类的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端点。
Endpoint | Functionality |
---|---|
POST /signup | Signup |
POST /auth/login | Login |
GET /auth/logout | Logout |
GET /todos | List all todos |
POST /todos | Create a new todo |
GET /todos/:id | Get a todo |
PUT /todos/:id | Update a todo |
DELETE /todos/:id | Delete a todo and its items |
GET /todos/:id/items | Get a todo item |
PUT /todos/:id/items | Update a todo item |
DELETE /todos/:id/items | Delete 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_rails
, shoulda_matchers
, faker
和database_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 record
和rspec
分别生成迁移,模型和规范。
# 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 theitems
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
![](https://scotch-res.cloudinary.com/image/upload/w_auto,q_auto:good,f_auto/media/1790/YW1BZHrBS3OEAz1h4wt7_Screen%20Shot%202017-01-12%20at%2015.10.34.jpg)
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
![](https://scotch-res.cloudinary.com/image/upload/w_auto,q_auto:good,f_auto/media/1790/r7oqTvuTPu5NjpQHGPhb_Screen%20Shot%202017-01-12%20at%2015.37.49.jpg)
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...
此时,再次运行测试,然后...
![](https://i-blog.csdnimg.cn/blog_migrate/cbf924b8a637c31814e8aaafdf3fa5b9.png)
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.
运行测试。
![](https://scotch-res.cloudinary.com/image/upload/w_auto,q_auto:good,f_auto/media/1790/D6FS2LBETfVW9eiBGVQ7_Screen%20Shot%202017-01-12%20at%2016.57.21.jpg)
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 exceptionActiveRecord::RecordNotFound
. We'll rescue from this exception and return a404
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!
运行测试,一切都变成绿色!
![](https://scotch-res.cloudinary.com/image/upload/w_auto,q_auto:good,f_auto/media/1790/7HP1X9GqShrGrvR84htw_Screen%20Shot%202017-01-12%20at%2017.18.20.jpg)
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.
您应该看到类似的输出。
![](https://i-blog.csdnimg.cn/blog_migrate/8e75b25e45f3235468a38ae5943116cd.png)
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.
运行测试。
![](https://i-blog.csdnimg.cn/blog_migrate/81fa2baa54e1d5bb6fa73efc11b0cd54.png)
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
![](https://scotch-res.cloudinary.com/image/upload/w_auto,q_auto:good,f_auto/media/1790/h6FaJ7NSQpyTC1a0p8lz_Screen%20Shot%202017-01-12%20at%2021.06.51.jpg)
结论 ( 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 withFactory Bot
,Database Cleaner
,Shoulda Matchers
andFaker
. 使用Factory Bot
,Database Cleaner
,Shoulda Matchers
和Faker
设置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的 authentication
, pagination
和API versioning
。 希望在那里见到你。 干杯!
翻译自: https://scotch.io/tutorials/build-a-restful-json-api-with-rails-5-part-one
rails 返回json