加载中

A few weeks ago, an announcement was made referring to the imminent inclusion of Rails API into Rails core. Until now, Rails API has been a separated project and people have been using it through the rails-api gem.

Santiago Pastorino and I have been working on bringing Rails API into Rails for a while. After some further discussion, bug fixes and last-minute changes, the corresponding pull request was finally merged. We’re happy to confirm that all Rails API capabilities will be available once Rails 5 is released!

Rails API goal is to facilitate the implementation of API only Rails projects, where only a subset of Rails features are available. A Rails API application counts with lightweight controllers, a reduced middleware stack and customized generators. All these features were conceived looking for a better experience at the moment of building an API only application.

For more detailed information about the Rails API project, you can take a look at this Santiago Pastorino’s article about the project.

几周以前,一份声明引述了即将被引入Rails核心的Rails API。到目前为止,Rails API 还是一个独立的项目,而人们要使用它就得通过 rails-api 这个 gem

Santiago Pastorino 和我有段时间致力于将 Rails API 带到 Rails 中去。在进行了一些更加深入的讨论之后,BUG被修复并且进行了最终的修改,对应的提交请求 最终也获得了通过。我们很高兴地确认,所有 Rails API 的功能在 Rails 5 发布时都能使用了!

Rails API 的目标是促进Rails工程的上的API实现,而之前只是 Rails 功能的一个子集是能用的。Rails API 应用程序拥有轻量的控制器,简化的中间件栈以及自定义的生成器。所有这些功能都被预期能在构建应用程序的API时带来更好的体验。

更多有关于 Rails API 项目的详细信息,可以看看这篇Santiago Pastorino的文章。

How to implement a Rails API backend for a simple Backbone application.

Let’s create an API only application! The rest of this article is a step-by-step example of how to build a Rails API backend for a simple TODO list application implemented with Backbone.

Since we want to focus on the backend implementation and its integration with the client side application, we decided to borrow the Backbone TODO application from the TodoMVC project.

Generating the Rails API only application

Once Rails 5 is released, creating an API only application will be accomplished by running:

rails new <application-name> --api

However, this feature was just incorporated into the master branch at the time of writing, so we need to generate the application directly from the most recent version of the Rails source code. The easiest way to have a copy of this code is by cloning the Rails Github project in our computer:

git clone git://github.com/rails/rails.git

Now we must run the rails new command in the folder where the repo was cloned. In order to have our generated project pointing to our local copy of the Rails source code, we need to run this command in the following manner:

bundle exec railties/exe/rails new <parent-folder-path>/my_api_app --api --edge

It’s a good idea to specify a path for the generated project, so we avoid creating the Rails API application inside the Rails source code folder. That explains the <parent-folder-path> placeholder in the example below.

如何通过一个简单的backbone应用实现一个后端的Rails接口?

让我们来创建一个应用程序的接口吧!本文的其他部分是通过由Backbone实现的一个简单的TODO list应用来一步步讲述如何构建一个后端的Rails接口。

因为我们希望把重点放在后端的实现,以及它与客户端应用程序的集成,我们决定从TODO MVC项目里借鉴Backbone TODO应用

创建一个应用程序的接口

一旦Rail5发布,创建一个应用程序的接口就会在运行时完成。

rails new <application-name> --api

但是,这个功能只是在写的时候合并到主分支,因此我们需要直接的通过最新版本的Rail源代码来生成应用程序。获取代码的最简便的方法就是在我们电脑里把Rail的Github项目克隆下来。

git clone git://github.com/rails/rails.git

现在我们必须在我们之前克隆下来仓库所在的文件夹里运行`rails new`命令。为了我们生成的项目能够指向我们本地复制的Rails源码,我们需要通过以下的方式来运行这个命令:

bundle exec railties/exe/rails new <parent-folder-path>/my_api_app --api --edge

这是一个好点子来指定生成项目的一个路径,因此我们可以避免在Rail源码的文件夹里创建Rail应用程序接口。

这就解释了下面的例子提到的`<parent-folder-path>`占位符。

Now, we can explore what was generated in the new project’s folder. You will notice that almost everything looks exactly the same than a regular Rails application, and that’s certainly true. However, let’s highlight what is different.

There are some changes in the generated Gemfile:

source 'https://rubygems.org'

gem 'rails', github: "rails/rails"
gem 'sprockets-rails', github: "rails/sprockets-rails"
gem 'arel', github: "rails/arel"

# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use ActiveModelSerializers to serialize JSON responses
gem 'active_model_serializers', '~> 0.10.0.rc1'

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug'
end

group :development, :test do
  ....
end

We can notice that stuff related with asset management and template rendering is not longer present (jquery-rails and turbolinks among others). In addition, the active_model_serializers is included by default because it will be responsible for serializing the JSON responses returned by our API application.

Let’s now check out the config/application.rb file:

....
....

module TodoRailsApiBackend
  class Application < Rails::Application

    ....
    ....

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true
  end
end

The api_only config option makes posible to have our Rails application working excluding those middlewares and controller modules that are not needed in an API only application.

Last but not least, our main ApplicationController is defined slightly different:

class ApplicationController < ActionController::API
end

Please note that ApplicationController inherits from ActionController::API. Remember that Rails standard applications have their controllers inheriting from ActionController::Base instead.

In case you’re interested on turning an existent Rails application into an API only application, the differences mentioned below are the list of changes that you need to do manually in order to achieve that.

现在我们可以研究下我们在新项目文件夹里创建的东西。你就会发现几乎所有的东西看起来都和一个常规的Rails应用程序完全一样。不过,让我们来高亮出哪些是不一样的。

生成的Gemfile中有这么一些改变。

source 'https://rubygems.org'

gem 'rails', github: "rails/rails"
gem 'sprockets-rails', github: "rails/sprockets-rails"
gem 'arel', github: "rails/arel"

# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use ActiveModelSerializers to serialize JSON responses
gem 'active_model_serializers', '~> 0.10.0.rc1'

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug'
end

group :development, :test do
  ....
end

我们可以发现那个与asset 管理和模板渲染有关的东西已不再存在(jquery-rails和turbolinks等)。此外,active_model_serializers在默认情况下是包含里面的,因为它负责通过我们的API应用程序返回序列化的JSON响应。

让我们来看看config/application.rb 文件吧

....
....

module TodoRailsApiBackend
  class Application < Rails::Application

    ....
    ....

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true
  end
end

api_only配置选项使得我们的Rails应用程序有可能和那些不需要应用程序API的中间件和控制器模块一起工作。

最后但也很重要的是,我们的主应用程序的定义略有不同:

class ApplicationController < ActionController::API
end

请注意,ApplicationController是继承自ActionController::API的。同时也要记住,Rails标准应用程序的控制器是继承自ActionController::Base的。

如果你有兴趣把一个已存在的Rails应用程序变成一个应用程序的API,下面提到的差异变化的列表,你需要亲手的来实现它。

Scaffolding the Todo resource

The main purpose of our API application is to serve as a backend storage for our list of TODOs. For this reason, we need to generate a new resource in our Rails API project. After looking at the code of the Backbone TODO application, we know we will need to define a Todo model including some attributes: a stringtitle, a booleancompletedand an integerorder.

Let’s use the scaffold command:

bin/rails g scaffold todo title completed:boolean order:integer



Again, we need to make sure all rails commands run the latest Rails source code in our computer, so we must run the executables from the bin folder (otherwise, the scaffold would use the rails code from an installed rails gem in the system).

This command is the same explained in the Rails’ guides and books about the framework. Rails API does not require any change or additional options in all subsequent commands. Therails-apioption added to theconfig/application.rbis enough to alter how scaffolding and other things works in our API only project.

The generated TodoController looks like this:

class TodosController < ApplicationController
  before_action :set_todo, only: [:show, :update, :destroy]

  # GET /todos
  def index
    @todos = Todo.all

    render json: @todos
  end

  # GET /todos/1
  def show
    render json: @todo
  end

  # POST /todos
  def create
    @todo = Todo.new(todo_params)

    if @todo.save
      render json: @todo, status: :created, location: @todo
    else
      render json: @todo.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /todos/1
  def update
    if @todo.update(todo_params)
      render json: @todo
    else
      render json: @todo.errors, status: :unprocessable_entity
    end
  end

  # DELETE /todos/1
  def destroy
    @todo.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_todo
      @todo = Todo.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def todo_params
      params.require(:todo).permit(:title, :completed, :order)
    end
end




If we compare this controller with one generated for a regular Rails application, we would find some differences. First of all, the actionsnewandeditare not included. That’s because these actions are used to render the html pages containing the forms where users fill the data to be added or modified in the system. Of course, it does not make sense in an API because this application is not longer responsible for rendering html pages.

Looking at the render statements within the actions we can see more differences. The controller only responds in JSON format. The usage ofrespond_to, which allows us to handle different format responses or differentiate ajax requests, is not longer available.

The controller tests, generated by the scaffold in the filetest/controllers/todos_controller_test.rb, are consistent with the actions and format of the responses expressed in theTodoController.

No template files are generated when scaffolding a new resource in our Rails API application. We don’t have theapp/viewfolder in our project.

Theconfig/routes.rbfile includes now the following line:

resources :todos 

This is exactly the same result after running the scaffold command in a regular Rails application. However, this line defines only the routes that are necessary in our API. In other words,newandeditroutes are excluded now. We can confirm that by runningbin/rake routes:

Prefix Verb   URI Pattern          Controller#Action
 todos GET    /todos(.:format)     todos#index
       POST   /todos(.:format)     todos#create
  todo GET    /todos/:id(.:format) todos#show
       PATCH  /todos/:id(.:format) todos#update
       PUT    /todos/:id(.:format) todos#update
       DELETE /todos/:id(.:format) todos#destroy

Do not forget to runbin/rake db:migrate, so the database is ready when the time comes to test our application.

Defining how TODO items should be serialized

Our API only application will respond incoming requests in JSON format, and here is where Active Model Serializers plays an important role. It requires to define theTodoSerializerclass and provide the list of attributes from ourTodomodel to include in the responses.

Active Model Serializers offers a command to generate this serializer:

bin/rails g serializer todo title completed order

The result is the fileapp/serializers/todo_serializer.rb:

class TodoSerializer < ActiveModel::Serializer
  attributes :id, :title, :completed, :order
end

At this point, we have implemented with almost zero effort a working backend application. We can runbin/rails sto start the web server and test our API with the help ofcurlcommand.

Let’s create a new Todo

curl -H "Content-Type:application/json; charset=utf-8" -d '{"title":"something to do","order":1,"completed":false}' http://localhost:3000/todos

and it should return:

{"id":1,"title":"something to do","completed":false,"order":1}

Now, we can try to get the list of TODOs:

curl http://localhost:3000/todos

and the result should be:

[{"id":1,"title":"something todo","completed":false,"order":1}]

这跟一个标准的Rails应用在运行完scaffold命令后的结果完全一样。然而这行代码却仅仅定义了我们API所需要的那些路由,也就是说,新建的路由和修改资源的路由被排除了。我们可以通过执行bin/rake routes(译者注:Rails 5中的rake命令可完全使用rails代替,这里可以执行 bin/rails routes来证实这一点:


Prefix Verb   URI Pattern          Controller#Action
 todos GET    /todos(.:format)     todos#index
       POST   /todos(.:format)     todos#create
  todo GET    /todos/:id(.:format) todos#show
       PATCH  /todos/:id(.:format) todos#update
       PUT    /todos/:id(.:format) todos#update
       DELETE /todos/:id(.:format) todos#destroy
不要忘记执行 bin/rake db:migrate , 现在数据库已经准备好,是时候测试我们的应用了。


TODO项该如何被序列化

我们的API模式的应用会以JSON格式来响应请求,在这里,Active Model Serializers就扮演了重要的角色,它定义了一个TodoSerializer类并且提供响应内所需TODO模型的一系列属性。

Active Model Serializers 提供了一个命令来生成这个serializer

bin/rails g serializer todo title completed order

生成的文件在 app/serializers/todo_serializer.rb:

class TodoSerializer < ActiveModel::Serializer
  attributes :id, :title, :completed, :order
end

这个时候,我们就实现了一个几乎工作量为零的后端应用。我们可以执行bin/rails s来启动这个应用,并且利用curl命令来帮助我们测试API。

新建一个Todo


curl -H "Content-Type:application/json; charset=utf-8" -d '{"title":"something to do","order":1,"completed":false}' http://localhost:3000/todos

返回的结果应该是:

{"id":1,"title":"something to do","completed":false,"order":1}

我们现在可以试下去取所有的Todo:

curl http://localhost:3000/todos

结果应该是:

[{"id":1,"title":"something todo","completed":false,"order":1}]

Putting both components to work together

It’s time to integrate the Backbone application with our backend implementation!

The original implementation of this TODO list application uses the browser local storage. However, we want to indicate that our Rails API application is the new storage for the data.

Let’s replace the following lines in js/collections/todos.js:

  // Save all of the todo items under the `"todos"` namespace.
  localStorage: new Backbone.LocalStorage('todos-backbone'),

with a definition for the URL of our API endpoint for TODO items:

// Set the rails-api backend endpoint for this specific model
  url: 'http://localhost:3000/todos',

After this change, we are almost ready. Our last outstanding task is to configure the cross origin policy to make the communication between both components possible. This is necessary when the backend and the client components are in different domains. Since we are doing local testing, we will run both things in localhost but using different ports, so we will end up having CORS errors in the browser if we don’t configure any cross origin policy.

In Rails API, the handling of CORS is not enabled by default, however you can find the rack-cors gem listed in the Gemfile, but commented out.

In order to fix the CORS problem, we only need to uncomment this line and run bundle install again in the Rails API project. Also, we need to take a look at the file config/initializers/cors.rb. This file has an example of how CORS can be configured in our project.

Let’s do some changes in this file, so we can test both components, assuming that we will use port 9000 to run the client side application:

# Avoid CORS issues when API is called from the frontend app
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests

# Read more: https://github.com/cyu/rack-cors

 Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
   allow do
     origins 'localhost:9000'

     resource '*',
       headers: :any,
       methods: [:get, :post, :put, :patch, :delete, :options, :head]
   end
 end

You can read more about the rack-cors gem and how to configure the policies here.

Once we have configured the cross origin policy in our backend, we are ready to test our client side application. We should turn on the backend server (bin/rails s) but also run a separated web server for the Backbone application.

We can simply run a test server using Ruby to test locally the frontend. Run the following command in the Backbone application’s folder:

ruby -run -e httpd . -p 9000

Give it a try by browsing to localhost:9000. You will notice that TODO items are being stored by our Rails API backend now instead of the browser local storage. Therefore, both components are connected and communicated with each other.

将组件集成一起运行起来

是时候将 Backbone 应用程序同我们的后台实现集成到一起了!

这个TODO列表应用程序的原有实现使用了浏览器的本地存储。而我们想要将 Rails API  应用程序指定为数据新的存储。

让我们对 js/collections/todos.js 中下面几行代码进行替换:

  // Save all of the todo items under the `"todos"` namespace.
  localStorage: new Backbone.LocalStorage('todos-backbone'),

使用我们为TODO项准备的API端点的URL定义:

// Set the rails-api backend endpoint for this specific model
  url: 'http://localhost:3000/todos',

进行了这个修改之后,我们就几乎已经准备好了。最后一个重要的任务就是配置一下跨域策略,以使得组件之间的通信成为可能。当后台和客户端组件在不同的域时,这个就是必要的操作。因为我们是在本地做测试,因此都会是在localhost上运行,但使用的是不同的端口, 所以如果我们没有配置跨域策略的话,在浏览器中就会发生CORS错误。

在 Rails API 中, CORS 的处理默认是没有启用的,不过你可以在 Gemfile中找到 ack-cors 这个gem,不过被注释掉了。

为了修复 CORS 的问题,我们只需要取消对这一行的注释,然后再 Rails API工程中重新运行bundle install就行了。我们也会需要看一眼 config/initializers/cors.rb 这个文件。这个文件有一个关于如何在工程中配置 CORS 的示例。

让我们在这个文件中做一些修改,这样我们就能对两个组件都进行测试, 假设我们要使用端口9000来运行客户端应用程序:

# Avoid CORS issues when API is called from the frontend app
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests

# Read more: https://github.com/cyu/rack-cors

 Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
   allow do
     origins 'localhost:9000'

     resource '*',
       headers: :any,
       methods: [:get, :post, :put, :patch, :delete, :options, :head]
   end
 end

你可以在这里读到更多关于 rack-cors 这个 gem 以及如何配置策略的内容。

只要我们在后台配置好了跨域策略,就已经准备好对客户端应用程序进行测试了。我们应该启动后台服务器 (bin/rails s) 并且也为 Backbone应用程序运行一个独立的web服务端。

我们可以使用Ruby来简单地运行一个测试服务端,来对前端进行本地测试。在Backbone应用程序的目录中运行下面的命令:

ruby -run -e httpd . -p 9000

试试浏览一下 localhost:9000。你会发现TODO项现在已经被存储到我们的Rail API后台了,而不是浏览器的本地存储中。这样,两个组件相互之间就可以连接起来和通信了。

Conclusion

The aim of this article was to show how easy is to implement an API only backend for a simple Backbone application using the new Rails API feature. Rails API just landed into Rails source code, however you can count with this feature because the next Rails major release is around the corner.

Some stuff can still be improved. For instance, the file containing the TodoSerializer class should be generated as part of the scaffold command when the active_model_serializers gem is included in the project. We have fixed this specific problem and it is already merged on master and ready for the next release of active_model_serializers.

We also have other options in terms of serialization. In fact, we have prepared Rails API to play well with JBuilder. This library is an alternative within the Rails ecosystem that allows to define JSON responses using templates instead of defining a serializer class as Active Model Serializers does.

Although Rails API is just being incorporated into Rails and further improvements can certainly be done, I hope you feel more confortable and productive implementing your APIs with this new functionality to be shipped in the next Rails version.

Enjoy exploring Rails API!

Resources

You can find the backend and frontend applications presented in this article in Github:

The Backbone application was based on the Backbone example included in the TodoMVC project.

结 论

本文的目的是怎样使用新的 Rails API 实现一个简单的主干应用程序的后端 API 。Rails API 使用 Rails 的源码,你可以用这个功能计数,下一个版本的 Rails 发布,这个功能就即将来临。

一些东西仍然可以改善。例如,该文件包含 TodoSerializer 类,当 active_model_serializers gem 被包含在项目中时,应该生成脚手架命令的一部分。这个特定的问题我们已经修复,并且 它已经被合并到主分支, 下一个发布版本 ofactive_model_serializers 的发布,已经准备好了。

我们也有其他选项来序列化。实际上,我们已经做好 Rails API 与 JBuilder 的配合。在 Rails 的生态系统内,这个库是一个选择,它允许定义 JSON 响应的模板来替代特殊定义的序列化类并作为 活跃模型序列化 (Active Model Serializers)。

尽管 Rails API 只是被纳入 Rails ,当然,这可以改进,我希望你感觉到 API 的舒适与高效实现,有了这个新功能,下一个 Rails 版本也准备好了。

享受  Rails API 的探索! 

资 源

你可以找到后端,并且前端应用已经在 Github 上发布。

后端应用是基于后端例子的,它被包含在 TodoMVC project 。

返回顶部
顶部