博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
rails 返回json_使用Rails 5构建RESTful JSON API-第三部分
阅读量:2508 次
发布时间:2019-05-11

本文共 13498 字,大约阅读时间需要 44 分钟。

rails 返回json

In of this tutorial, we added token-based authentication with (JSON Web Tokens) to our todo API.

在本教程的第二中,我们向Todo API中添加了基于 (JSON Web令牌)的基于令牌的身份验证。

In this final part of the series, we'll wrap with the following:

在本系列的最后一部分,我们将包装以下内容:

  • Versioning

    版本控制
  • Serializers

    序列化器
  • Pagination

    分页

( )

When building an API whether public or internal facing, it's highly recommended that you version it. This might seem trivial when you have total control over all clients. However, when the API is public facing, you want to establish a contract with your clients. Every breaking change should be a new version. Convincing enough? Great, let's do this!

构建公共或内部API时,强烈建议您对其进行版本控制。 当您完全控制所有客户端时,这似乎微不足道。 但是,当API面向公众时,您希望与客户建立合同。 每个重大更改都应该是一个新版本。 有说服力吗? 太好了,让我们做吧!

In order to version a Rails API, we need to do two things:

为了对Rails API进行版本控制,我们需要做两件事:

  1. Add a route constraint - this will select a version based on the request headers

    添加路由约束-这将根据请求标头选择版本
  2. Namespace the controllers - have different controller namespaces to handle different versions.

    控制器的命名空间-具有不同的控制器命名空间来处理不同的版本。

Rails routing supports . Provided an object that responds to matches?, you can control which controller handles a specific route.

Rails路由支持 。 提供了对matches?做出响应的对象matches? ,您可以控制哪个控制器处理特定路由。

We'll define a class ApiVersion that checks the API version from the request headers and routes to the appropriate controller module. The class will live in app/lib since it's non-domain-specific.

我们将定义一个ApiVersion类,该类检查请求标头中的API版本,并路由到适当的控制器模块。 由于该类不是特定于域的,因此将存在于app/lib

# create the class file$ touch app/lib/api_version.rb

Implement ApiVersion

实施ApiVersion

# app/lib/api_version.rbclass ApiVersion  attr_reader :version, :default  def initialize(version, default = false)    @version = version    @default = default  end  # check whether version is specified or is default  def matches?(request)    check_headers(request.headers) || default  end  private  def check_headers(headers)    # check version from Accept headers; expect custom media type `todos`    accept = headers[:accept]    accept && accept.include?("application/vnd.todos.#{
version}+json") endend

The ApiVersion class accepts a version and a default flag on initialization. In accordance with Rails constraints, we implement an instance method matches?. This method will be called with the request object upon initialization. From the request object, we can access the Accept headers and check for the requested version or if the instance is the default version. This process is called content negotiation. Let's add some more context to this.

ApiVersion类在初始化时接受版本和默认标志。 按照Rails的约束,我们实现一个实例方法matches? 。 初始化时将与请求对象一起调用此方法。 从请求对象中,我们可以访问Accept标头并检查请求的版本或实例是否为默认版本。 此过程称为内容协商。 让我们为此添加更多的上下文。

内容协商 (Content Negotiation)

REST is closely tied to the HTTP specification. HTTP defines mechanisms that make it possible to serve different versions (representations) of a resource at the same URI. This is called .

REST与HTTP规范紧密相关。 HTTP定义了可以在同一URI上为资源的不同版本( 表示形式 )提供服务的机制。 这称为 。

Our ApiVersion class implements server-driven content negotiation where the client (user agent) informs the server what media types it understands by providing an Accept HTTP header.

我们的ApiVersion类实现了服务器驱动的内容协商,其中客户端(用户代理)通过提供Accept HTTP标头来通知服务器它理解哪种媒体类型。

According to the , you can define your own media types using the vendor tree i.e. application/vnd.example.resource+json.

根据 ,您可以使用供应商树定义自己的媒体类型,即application/vnd.example.resource+json

The is used for media types associated with publicly available products. It uses the "vnd" facet.

用于与公共可用产品关联的媒体类型。 它使用“ vnd”构面。

Thus, we define a custom vendor media type application/vnd.todos.{version_number}+json giving clients the ability to choose which API version they require.

因此,我们定义了一个定制的供应商媒体类型application/vnd.todos.{version_number}+json使客户可以选择所需的API版本。

Cool, now that we have the constraint class, let's change our routing to accommodate this. Since we don't want to have the version number as part of the URI (this is argued as an anti-pattern), we'll make use of the module scope to namespace our controllers.

很酷,现在我们有了约束类,让我们更改路由以适应这一点。 由于我们不想将版本号作为URI的一部分(这被认为是反模式),因此我们将利用模块作用域为控制器命名空间。

Let's move the existing todos and todo-items resources into a v1 namespace.

让我们将现有的待办事项和待办事项资源移动到v1命名空间中。

# config/routesRails.application.routes.draw do  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html  # namespace the controllers without affecting the URI  scope module: :v1, constraints: ApiVersion.new('v1', true) do    resources :todos do      resources :items    end  end  post 'auth/login', to: 'authentication#authenticate'  post 'signup', to: 'users#create'end

We've set the version constraint at the namespace level. Thus, this will be applied to all resources within it. We've also defined v1 as the default version; in cases where the version is not provided, the API will default to v1. In the event we were to add new versions, they would have to be defined above the default version since Rails will cycle through all routes from top to bottom searching for one that matches(till method matches? resolves to true).

我们已经在名称空间级别设置了版本约束。 因此,这将应用于其中的所有资源。 我们还将v1定义为默认版本。 如果未提供版本,则该API将默认为v1 。 如果我们要添加新版本,则它们必须在默认版本之上进行定义因为Rails将从头到尾遍历所有路由以寻找matches路由(直到方法matches?解析为true)。

Next up, let's move the existing todos and items controllers into the v1 namespace. First, create a module directory in the controllers folder.

接下来,让我们将现有的待办事项和项目控制器移至v1名称空间。 首先,在controllers文件夹中创建一个模块目录。

$mkdir app/controllers/v1

Move the files into the module folder.

将文件移到模块文件夹中。

$mv app/controllers/{
todos_controller.rb,items_controller.rb} app/controllers/v1

That's not all, let's define the controllers in the v1 namespace. Let's start with the todos controller.

不仅如此,让我们在v1名称空间中定义控制器。 让我们从todos控制器开始。

# app/controllers/v1/todos_controller.rbmodule V1  class TodosController < ApplicationController  # [...]  endend

Do the same for the items controller.

对项目控制器执行相同的操作。

# app/controllers/v1/items_controller.rbmodule V1  class ItemsController < ApplicationController  # [...]  endend

Let's fire up the server and run some tests.

让我们启动服务器并运行一些测试。

# get auth token$ http :3000/auth/login email=foo@bar.com password=foobar# get todos from API v1$ http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'ey...AWH3FNTd3T0jMB7HnLw2bYQbK0g'# attempt to get from API v2$ http :3000/todos Accept:'application/vnd.todos.v2+json' Authorization:'ey...AWH3FNTd3T0jMB7HnLw2bYQbK0g'

In case we attempt to access a nonexistent version, the API will default to v1 since we set it as the default version. For testing purposes, let's define v2.

如果我们尝试访问不存在的版本,则由于我们将其设置为默认版本,因此该API将默认为v1。 为了进行测试,让我们定义v2。

Generate a v2 todos controller

生成v2 todos控制器

$ rails g controller v2/todos

Define the namespace in the routes.

在路由中定义名称空间。

#config/routes.rbRails.application.routes.draw do  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html  # module the controllers without affecting the URI  scope module: :v2, constraints: ApiVersion.new('v2') do    resources :todos, only: :index  end  scope module: :v1, constraints: ApiVersion.new('v1', true) do    # [...]  end  # [...]end

Remember, non-default versions have to be defined above the default version.

请记住,非默认版本必须在默认版本之上定义。

Since this is test controller, we'll define an index controller with a dummy response.

由于这是测试控制器,因此我们将定义一个具有虚拟响应的索引控制器。

# app/controllers/v2/todos_controller.rbclass V2::TodosController < ApplicationController  def index    json_response({
message: 'Hello there'}) endend

Note the namespace syntax, this is shorthand in Ruby to define a class within a namespace. Great, now fire up the server once more and run some tests.

注意名称空间语法,这是Ruby中在名称空间内定义类的简写。 太好了,现在再次启动服务器并运行一些测试。

# get todos from API v1$ http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0e...Lw2bYQbK0g'# get todos from API v2$ http :3000/todos Accept:'application/vnd.todos.v2+json' Authorization:'eyJ0e...Lw2bYQbK0g'

Voila! Our API responds to version 2!

瞧! 我们的API响应版本2!

( )

At this point, if we wanted to get a todo and its items, we'd have to make two API calls. Although this works well, it's not ideal.

此时,如果我们要获取待办事项及其项目,则必须进行两次API调用。 尽管此方法效果很好,但并不理想。

We can achieve this with serializers. Serializers allow for custom representations of JSON responses. make it easy to define which model attributes and relationships need to be serialized. In order to get todos with their respective items, we need to define serializers on the Todo model to include its attributes and relationships.

我们可以使用序列化器来实现。 序列化程序允许自定义表示JSON响应。 使定义需要序列化的模型属性和关系变得容易。 为了获得待办事项及其各自的项目,我们需要在T​​odo模型上定义序列化器以包括其属性和关系。

First, let's add active model serializers to the Gemfile:

首先,让我们将活动模型序列化器添加到Gemfile中:

# Gemfile# [...]  gem 'active_model_serializers', '~> 0.10.0'# [...]

Run bundle to install it:

运行捆绑软件进行安装:

$ bundleinstall

Generate a serializer from the todo model:

从todo模型生成序列化器:

$ rails g serializer todo

This creates a new directory app/serializers and adds a new file todo_serializer.rb. Let's define the todo serializer with the data that we want it to contain.

这将创建一个新目录app/serializers ,并将一个新文件添加到todo_serializer.rb 。 让我们用我们要包含的数据定义待办事项序列化器。

# app/serializers/todo_serializer.rbclass TodoSerializer < ActiveModel::Serializer  # attributes to be serialized    attributes :id, :title, :created_by, :created_at, :updated_at  # model association  has_many :itemsend

We define a whitelist of attributes to be serialized and the model association (only defined attributes will be serialized). We've also defined a model association to the item model, this way the payload will include an array of items. Fire up the server, let's test this.

我们定义了要序列化的属性和模型关联的白名单 (仅定义的属性将被序列化)。 我们还定义了与项目模型的模型关联,这样,有效载荷将包含项目数组。 启动服务器,让我们测试一下。

# create an item for todo with id 1$ http POST :3000/todos/1/items name='Listen to Don Giovanni' Accept:'application/vnd.todos.v1+json' Authorization:'ey...HnLw2bYQbK0g'# get all todos$ http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'ey...HnLw2bYQbK0g'

This is great. One request to rule them all!

这很棒。 一个要求将它们全部统治的请求!

( )

Our todos API has suddenly become very popular. All of a sudden everyone has something to do. Our data set has grown substantially. To make sure the requests are still fast and optimized, we're going to add pagination; we'll give clients the power to say what portion of data they require.

我们的todos API突然变得非常流行。 突然每个人都有事要做。 我们的数据集已经大大增加。 为了确保请求仍然快速且经过优化,我们将添加分页; 我们将使客户有权说出他们需要哪一部分数据。

To achieve this, we'll make use of the gem.

为了实现这一点,我们将使用 gem。

Let's add it to the Gemfile:

让我们将其添加到Gemfile中:

# Gemfile# [...]  gem 'will_paginate', '~> 3.1.0'# [...]

Install it:

安装它:

$ bundleinstall

Let's modify the todos controller index action to paginate its response.

让我们修改todos控制器索引操作以分页其响应。

# app/controllers/v1/todos_controller.rbmodule V1  class TodosController < ApplicationController  # [...]  # GET /todos  def index    # get paginated current user todos    @todos = current_user.todos.paginate(page: params[:page], per_page: 20)    json_response(@todos)  end  # [...]end

The index action checks for the page number in the request params. If provided, it'll return the page data with each page having twenty records each. As always, let's fire up the Rails server and run some tests.

索引操作检查请求参数中的页码。 如果提供的话,它将返回页面数据,每个页面有二十个记录。 与往常一样,让我们​​启动Rails服务器并运行一些测试。

# request without page$ http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0...nLw2bYQbK0g'# request for page 1$ http :3000/todos page==1 Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0...nLw2bYQbK0g'# request for page 2$ http :3000/todos page==2 Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0...nLw2bYQbK0g'

The page number is part of the query string. Note that when we request the second page, we get an empty array. This is because we don't have more that 20 records in the database. Let's seed some test data into the database.

页码是查询字符串的一部分。 请注意,当我们请求第二页时,我们得到一个空数组。 这是因为我们数据库中没有超过20条记录。 让我们将一些测试数据播种到数据库中。

Add and install faker gem. Faker generates data at random.

添加并安装fakerr gem。 Faker随机生成数据。

# Gemfile# [...]  gem 'faker'# [...]

In db/seeds.rb let's define seed data.

db/seeds.rb我们定义种子数据。

# db/seeds.rb# seed 50 records50.times do  todo = Todo.create(title: Faker::Lorem.word, created_by: User.first.id)  todo.items.create(name: Faker::Lorem.word, done: false)end

Seed the database by running:

通过运行以下命令来播种数据库:

$ rake db:seed

Awesome, fire up the server and rerun the HTTP requests. Since we have test data, we're able to see data from different pages.

太棒了,启动服务器并重新运行HTTP请求。 由于我们拥有测试数据,因此我们可以查看来自不同页面的数据。

( )

Congratulations for making it this far! We've come a long way! We've gone through generating an API-only Rails application, setting up a test framework, using TDD to implement the todo API, adding token-based authentication with JWT, versioning our API, serializing with active model serializers, and adding pagination features.

恭喜! 我们已经走了很长一段路! 我们已经完成了生成纯API的Rails应用程序,设置测试框架,使用TDD来实现todo API,使用JWT添加基于令牌的身份验证,对API进行版本控制,使用活动模型序列化程序进行序列化以及添加分页功能的过程。

Having gone through this series, I believe you should be able to build a RESTful API with Rails 5. Feel free to leave any feedback you may have in the comments section below. If you found the tutorial helpful, don't hesitate to hit that share button. Cheers!

完成本系列文章后,我相信您应该能够使用Rails 5构建RESTful API。请随时在下面的评论部分中留下您的任何反馈。 如果您发现本教程对您有帮助,请立即点击该“共享”按钮。 干杯!

翻译自:

rails 返回json

转载地址:http://ohuwd.baihongyu.com/

你可能感兴趣的文章
文件转码重写到其他文件
查看>>
AC自动机模板
查看>>
python 基本语法
查看>>
git配置
查看>>
【hexo】01安装
查看>>
使用case语句给字体改变颜色
查看>>
JAVA基础-多线程
查看>>
面试题5:字符串替换空格
查看>>
JSP九大内置对象及四个作用域
查看>>
ConnectionString 属性尚未初始化
查看>>
数据结构-栈 C和C++的实现
查看>>
MySQL基本命令和常用数据库对象
查看>>
poj 1222 EXTENDED LIGHTS OUT(位运算+枚举)
查看>>
进程和线程概念及原理
查看>>
Lucene、ES好文章
查看>>
android 生命周期
查看>>
jquery--this
查看>>
MySQL 5.1参考手册
查看>>
TensorFlow安装流程(GPU加速)
查看>>
OpenStack的容器服务体验
查看>>