Learn Ruby on Rails API - GrapeAPI
Bài đăng này đã không được cập nhật trong 9 năm
I. API Basics and Building Your Own
1. Giới thiệu
Làm việc với APIs rất tuyệt vời nhưng cũng không kém phần khó chịu. API một mặt giao tiếp với các ứng dụng, cải thiện cách tiếp cận và đưa ra những “cool factor” cho ứng dụng của bạn, mặt khác liên quan nhiều tới viêch đọc tài liệu để tìm ra chiến lược xác thực và phân tích các thông báo lỗi(không tốt hoặc không tồn tại) bằng cách đọc lời giải thích của Skillcrush và sau đó đọc bit đầu tiên của article này để catch up.
API là 1 khái niệm rộng, ứng dụng của bạn và một ứng dụng khác hoặc các thành phần trong cùng một ứng dụng giao tiếp được với nhau là nhờ thông qua một số loại API; các thành phần trong cùng một ứng dụng ít nhiều độc lập với nhau, đóng vai trò như một ứng dụng con cùng với data cần thiết hoàn thành nhiệm vụ cụ thể của mình. Mỗi thứ như vậy trong đó là một API. Khi bạn xây dựng các ứng dụng có chức năng front-end năng động hơn (ví dụ như trang ứng dụng chuyên Javascript phức tạp hoặc đơn giản chỉ là các cuộc gọi AJAX), đơn giản chỉ cần 1-2 dòng code trong controller để trả về kiểu JSON hoặc XML thay vì HTML.
Dưới đây là cách để xây dựng 1 ứng dụng API
2. API Basics
Ứng dụng Rails về cơ bản đã là một API. Trình duyệt web đang chạy của bạn cũng là một chương trình, nó có tác dụng tạo một API request tới ứng dụng Rails của bạn khi bạn yêu cầu một trang mới. Nó mặc định render ra 1 trọng tải HTML tương ứng.
Nếu bạn muốn tạo một request mà không muốn đi qua trình tự rắc rối trên (Ví dụ như : bạn cần lấy một danh sách đối tượng nào đó mà không quan tâm tới cấu trúc HTML của trang(bỏ qua view) thay vào đó là đi thẳng tới dữ liệu) thì sự lựa chọn tốt nhất dành cho bạn là sử dụng JSON hoặc XML. Tức là sẽ submit một request tới URL tương ứng yêu cầu một JSON hoặc XML response.Nếu thiết lập controller đúng, bạn sẽ nhận lại được một mảng đối tượng JSON đơn giản chứa tất cả các đối tượng bạn yêu cầu.
Ngoài ra, với các APIs bên ngoài, ví dụ như bạn muốn lấy các tweet gần đây của người dùng từ Twitter, bạn chỉ cần chỉ cách cho ứng dụng Rails của bạn cách giao tiếp với API của Twitter (ví dụ như xác nhận người dùng), submit request và xử lý các bó tweet trả về.
3. Building APIs
3.1 The Basics
Nếu bạn muốn ứng dụng Rails của bạn trả về JSON
thay vì HTML
, bạn cần chỉ cho controller cách làm điều đó. Cùng 1 controller action có thể trả về những loại khác nhau phụ thuộc vào việc tạo request bình thường từ trình duyệt hay từ một API được gọi từ dòng lệnh. Nó quyết định loại của request đang được thực hiện dựa trên phần mở rộng của tập tin được yêu cầu, example.xml
hay example.json
Bạn có thể nhìn thấy được loại của tập tin ở trên nhật ký hệ thống :
Started GET “/posts/new” for 127.0.0.1 at 2013-12-02 15:21:08 -0800 Processing by PostsController#new as HTML
Dòng đầu tiên nói cho bạn biết loại URL được yêu cầu, dòng thứ 2 cho biết nó sẽ đi đâu và thực hiện tiến trình như thế nào. Nếu sử dụng 1 .json
extension thì log thì như sau :
Started GET “/posts.json” for 127.0.0.1 at 2013-12-04 12:02:01 -0800 Processing by PostsController#index as JSON
Nếu bạn thực hiện không đúng ở controller thì sẽ có lỗi thông báo ra cho bạn.
3.2 Rendering JSON
or XML
Sử dụng method #respond_to
để chỉ cho controller cách trả về tập tin file JSON
hoặc XML
:
class UsersController < ApplicationController
def index
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @users }
format.json { render :json => @users }
end
end
end
#respond_to
chuyển các khối vào 1 đối tượng định dạng mà bạn yêu cầu. Nếu bạn không làm gì, html sẽ render ra các view tương ứng mặc định của Rails (ví dụ như : app/views/index.html.erb
)
Chức năng #render
đủ thông minh để lấy ra được template tương ứng từ 1 tập các định dạng. Khi chạy qua từ khóa :json
, chương trình sẽ gọi #to_json
trên giá trị, trong trường hợp này là @users
. Điều này làm cho các đối tượng Ruby
trở thành chuỗi JSON
có thể chuyển tới được ứng dụng yêu cầu.
3.2.1. Chỉ định thuộc tính trả về
Trong model
: sử dụng #as_json
# app/models/user.rb
class User < ActiveRecord::Base
# Option 1: Purely overriding the #as_json method
def as_json(options={})
{ :name => self.name } # NOT including the email field
end
# Option 2: Working with the default #as_json method
def as_json(options={})
super(:only => [:name])
end
end
Trong controller, chỉ cần sử dụng render JSON
như bình thường (nó sẽ luôn trả về JSON
mặc dù có request HTML
hay không)
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
render json: User.all
end
end
3.2.2. Rendering Nothing or Errors
Giúp trả về một HTTP status code
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
render nothing: true, status: 404
end
def update
respond_to do |format|
if save!
format.html {redirect_to redirect_path}
format.json do
render json: @user, status: :ok, content_type: “text/json”
end
else
format.html do
render :show
end
format.json do
render json: @user.errors, status: :ng, content_type: “text/json”
end
end
end
end
end
Status codes
có thể dùng được theo 2 kiểu số và chữ (ví dụ như 404
hoặc :ok
)
- Một số status codes thường dùng :
200 – :ok
204 – :no_content
400 – :bad_request
403 – :forbidden
401 – :unauthorized
404 – :not_found
410 – :gone
422 – :unprocessable_entity
500 – :internal_server_error
Chi tiết có thể xem tại đây : http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
Cách tạo trang báo lỗi động trong Rails :
http://wearestac.com/blog/dynamic-error-pages-in-rails
4. Authentication
Mỗi một API
là một cổng vào ứng dụng của bạn. Nếu bạn không muốn ai cũng có thể truy cập, chỉnh sửa và xóa dữ liệu của bạn thì bạn cần chỉ định những người dùng nào sẽ được quyền làm những việc đó. Cách để bảo đảm an ninh cho API
của bạn là Access Tokens
. Chúng ta cần tạo 1 model cụ thể có tên là ApiKey
để lưu trữ token
, token
sẽ chứa 32 ký tự hexa
ví dụ như dưới đây:
class ApiKey < ActiveRecord::Base
attr_accessible :user, :token
belongs_to :user
before_create :generate_token
private
def generate_token
begin
self.token = SecureRandom.hex.to_s
end while self.class.exists?(token: token)
end
end
Class rất đơn giản, nó sẽ gọi method generate_token
, cái mà sẽ tạo 1 chuỗi ký tự hexa
duy nhất khi class
được khởi tạo. Mỗi một người dùng khi đăng ký sẽ được cấp cho một api key
.
app/models/user.rb
class User < ActiveRecord::Base
…
has_one :api_key, dependent: :destroy
after_create :create_api_key
…
private
def create_api_key
ApiKey.create user: self
end
end
Khi nhận 1 request
, chỉ cần kiểm tra xem token
có đúng hay không và tìm current user
tương ứng. Việc xác nhận này được thực hiện trong Application controller
#app/controllers/appliation_controller.rb
include ActionController::HttpAuthentication::Token::ControllerMethods
include ActionController::MimeResponds
class ApplicationController < ActionController::API
private
def restrict_access
unless restrict_access_by_params || restrict_access_by_header
render json: {message: "Invalid API Token"}, status: 401
return
end
@current_user = @api_key.user if @api_key
end
def restrict_access_by_header
return true if @api_key
authenticate_with_http_token do |token|
@api_key = ApiKey.find_by_token(token)
end
end
def restrict_access_by_params
return true if @api_key
@api_key = ApiKey.find_by_token(params[:token])
end
end
Hỗ trợ access token
cả header
và params
, để được hỗ trợ xác thực header
cần include module ActionController::HttpAuthentication::Token::ControllerMethods
(đã bị vô hiệu hóa ở Rails::API
mặc định). Để chắc chắn token
sẽ được kiểm tra mỗi khi gọi API
, cần thêm before_filter
vào controller
:
before_filter :restrict_access
5. Testing
Khi test 1 API bạn nên test cả 2 phần là nội dung response
và HTTP status codes
. Ví dụ :
#spec/api/tasks_api_spec.rb
require "spec_helper"
require "api/api_helper"
require "fakeweb"
require "timecop"
describe "Tasks API" do
before :each do
FactoryGirl.create :integration
FactoryGirl.create :project
FactoryGirl.create :task
Project.last.integrations << Integration.last
end
# GET /tasks/:id
it "should return a single task" do
api_get "tasks/#{Task.last.id}", {token: Integration.last.user.api_key.token}
response.status.should == 200
project = JSON.parse(response.body)
project["id"].should == Task.last.id
project["project_id"].should == Task.last.project_id
project["source_name"].should == Task.last.source_name
project["source_identifier"].should == Task.last.source_identifier
project["current_state"].should == Task.last.current_state
project["story_type"].should == Task.last.story_type
project["current_task"].should == Task.last.current_task
project["name"].should == Task.last.name
end
…
end
Test riêng từng action trong controller:
#spec/api/api_helper.rb
def api_get action, params = {}, version = "1"
get "/api/v#{version}/#{action}", params
JSON.parse(response.body) rescue {}
end
def api_post action, params = {}, version = "1"
post "/api/v#{version}/#{action}", params
JSON.parse(response.body) rescue {}
end
def api_delete action, params = {}, version = "1"
delete "/api/v#{version}/#{action}", params
JSON.parse(response.body) rescue {}
end
def api_put action, params = {}, version = "1"
put "/api/v#{version}/#{action}", params
JSON.parse(response.body) rescue {}
end
6. Getting Started
Trang web này là một trang web hữu ích, giúp cho bạn làm quen với việc chạy lệnh API
trên terminal
https://developer.github.com/guides/getting-started/
II. GrapeAPI
A. Tổng quan
1. Giới thiệu
Grape là một REST-like API micro-framework
cho Ruby
. Nó được thiết kế để chạy trên Rack
hoặc bổ sung cho mô hình ứng dụng web hiện có như Rails
và Sinatra
bằng việc cung cấp một DSL đơn giản
để dễ dàng phát triển các RESTful API
. Nó được xây dựng để hỗ trợ cho các giao tiếp chung (common convention
), chứa nhiều format
, giảm thiểu subdomain/prefix
, đàm phán nội dung…
2. Cài đặt
Cài đặt gem:
Cách 1 : Run $ gem install grape
Cách 2 : thêm gem "grape"
vào Gemfile
, chạy bundle install
3. Basic Usage
Grape APIs
là các ứng dụng Rack được tạo bởi subclassing Grape::API
. Dưới đây là một ví dụ đơn giản cho thấy một số tính năng phổ biến của Grape trong việc tái tạo các phần của Twitter API
.
module Twitter
class API < Grape::API
version "v1", using: :header, vendor: "twitter"
format :json
prefix :api
helpers do
def current_user
@current_user ||= User.authorize!(env)
end
def authenticate!
error!("401 Unauthorized", 401) unless current_user
end
end
resource :statuses do
desc "Return a public timeline."
get :public_timeline do
Status.limit(20)
end
desc "Return a personal timeline."
get :home_timeline do
authenticate!
current_user.statuses.limit(20)
end
desc "Return a status."
params do
requires :id, type: Integer, desc: "Status id."
end
route_param :id do
get do
Status.find(params[:id])
end
end
desc "Create a status."
params do
requires :status, type: String, desc: "Your status."
end
post do
authenticate!
Status.create!({
user: current_user,
text: params[:status]
})
end
desc “Update a status.”
params do
requires :id, type: String, desc: "Status ID."
requires :status, type: String, desc: "Your status."
end
put ":id" do
authenticate!
current_user.statuses.find(params[:id]).update({
user: current_user,
text: params[:status]
})
end
desc "Delete a status."
params do
requires :id, type: String, desc: "Status ID."
end
delete ":id" do
authenticate!
current_user.statuses.find(params[:id]).destroy
end
end
end
end
4. Grape on Rails
Hãy thay thế các file APIs thành app/api
. Rails mong muốn một thư mục con tương ứng với tên của các module Ruby
và một tên file tương ứng với tên của các class. Ví dụ tên file local và thư mục cho Twitter::API nên là app/api/twitter/api.rb
Chính sửa #application.rb
:
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
Chính sửa #config/routes
:
mount Twitter::API=> '/'
Chi tiết : https://github.com/intridea/grape#rails
B. Building RESTful API using Grape in Rails
1. Getting Started
Tạo 1 app:
$ rails new emp_api –skip-bundle
Thêm dòng sau vào Gemfile
và chạy bundle install
gem "grape"
Tạo một Employee model
cho phương thức tạo/sửa/xóa/đọc cơ bản
Bên trong emp_api/app/
tạo 1 forder api
Bên trong thư mục emp_api/app/api
tạo một forder employee, trong emp_api/app/api/employee
tạo 1 file data.rb
, file data.rb
này sẽ truy cập vào Employee model
Bên trong forder emp_api/app/api/
tạo 1 file api.rb
, là nơi mà sẽ gắn kết các lớp được định nghĩa trong emp_api/app/api/employee/data.rb
.
2. Modularizing cấu trúc thư mục API
Như đã nói ở trên thay thế các file API thành app/api
, thư mục cần được thêm vào load/autoload paths
=> Cài đặt trong config/application.rb
:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
moduleEmpApi
classApplication < Rails::Application
## Newly Added code to set up the api code
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
end
end
Creating the API. Ví dụ tạo API endpoint để lấy thông tin chi tiết của tất cả employee. Mở file app/api/employee/data.rb và tạo module class như sau :
module Employee
class Data < Grape::API
resource :employee_data do
desc "List all Employee"
get do
EmpData.all
end
end
end
end
Truy nhập Employee::Data
class bên trong API class
, sử dụng mount
để tạo Employee::Data class
có thể truy cập được bên trong API class
.
Mở #app/api/api.rb
thêm mount Employee::Data
class API < Grape::API
prefix 'api'
version 'v1', using: :path
mount Employee::Data
end
3. Mounting API under rails routes
Thêm vào app/config/routes.rb
Rails.application.routes.draw do
mount API => "/"
end
4. Customize JSON API Errors
Chỉ địnhapi raise errors
và tùy chỉnh chúng response về định dạng khi có ngoại lệ
# app/api/error_formatter.rb
module API
module ErrorFormatter
def self.call message, backtrace, options, env
{response_type: "error", response: message}.to_json
end
end
end
hoặc nhúng module này vào bên trong API::Root
# app/api/root.rb
module API
class Root < Grape::API
#…
error_formatter :json, API::ErrorFormatter
#…
end
end
5. Securing API
5.1. HTTP Basic authentication
Thêm xác thực cơ bản vào API::Root
và nó có tác dụng cho tất cả các phiên bản của API:
# app/api/root.rb
module API
class Root < Grape::API
#…
http_basic do |email, password|
user = User.find_by_email(email)
user && user.valid_password?(password)
end
#…
end
end
Yêu cầu API bằng các thông tin http auth
cơ bản:
curl http://localhost:3000/api/products -u "admin:secret"
5.2. Xác thực sử dụng email và password
Grape cung cấp cho chúng ta before block
để có thể xác thực trước khi truy cập
# app/api/root.rb
module API
class Root < Grape::API
#…
before do
error!(“401 Unauthorized”, 401) unless authenticated
end
helpers do
def authenticated
user = User.find_by_email(params[:email])
user && user.valid_password?(params[:password])
end
end
#…
end
end
6. Chạy tới action bằng lệnh trên terminal
Đánh lệnh sau vào terminal
:
curl http://localhost:3000/api/v1/employee_data.json
=> result : [ ]
6.1. Sử dụng curl để post 1 request tới endpoint (create)
Thêm đoạn code sau vào app/api/employee/data.rb
desc "create a new employee"
## This takes care of parameter validation
params do
requires :name, type: String
requires :address, type:String
requires :age, type:Integer
end
## This takes care of creating employee
post do
EmpData.create!({
name:params[:name],
address:params[:address],
age:params[:age]
})
end
Trên terminal:
curl http://localhost:3000/api/v1/employee_data.json -d name="hoa nguyen" -d address="Ha Noi" -d age="25"
=> result : {“id”:1,”name”:”hoa nguyen”,”address”:”Ha Noi”,”age”:25,”created_at”:”2015-01-31T11:55:00.356Z”,”updated_at”:”2015-01-31T11:55:00.356Z”}
Kiểm tra employee vừa tạo bằng lệnh:
curl http://localhost:3000/api/v1/employee_data.json
=> result hiện trên terminal : [{"id":1,"name":"hoa nguyen","address":"Ha Noi","age":25,"created_at":"2015-01-31T11:55:00.356Z","updated_at":"2015-01-31T11:55:00.356Z"}]
6.2. Xóa 1 bản ghi
Làm tương tự như hàm create
, thêm đoạn code sau vào trong # app/api/employee/data.rb
# app/api/employee/data.rb
desc "delete an employee"
params do
requires :id, type: String
end
delete ':id' do
EmpData.find(params[:id]).destroy!
end
Trên terminal thêm dòng sau :
curl -X DELETE http://localhost:3000/api/v1/employee_data/1.json
=> result: {“id”:1,”name”:”hoa nguyen”,”address”:”Ha Noi”,”age”:25,”created_at”:”2015-01-31T11:55:00.356Z”,”updated_at”:”2015-01-31T11:55:00.356Z”}
Đánh lệnh sau vào terminal :
curl http://localhost:3000/api/v1/employee_data.json
=> result : [ ]
6.3. Update
Thêm đoạn sau vào #app/api/employee/data.rb
để tạo bản ghi:
# app/api/employee/data.rb
desc "update an employee address"
params do
requires :id, type: String
requires :address, type:String
end
put ':id' do
EmpData.find(params[:id]).update({
address:params[:address]
})
end
Restart server và đánh lệnh sau vào :
curl http://localhost:3000/api/v1/employee_data.json -d name="hoa nguyen" -d address="Ha Noi" -d age="25"
=> result : {“id”:2,”name”:”hoa nguyen”,”address”:”Ha Noi”,”age”:25,”created_at”:”2015-01-31T12:02:01.485Z”,”updated_at”:”2015-01-31T12:02:01.485Z”}
Sử dụng lệnh sau để update name của employee:
curl -X PUT http://localhost:3000/api/v1/employee_data/2.json -d name=”hoa” -d address=”Dan Phuong”
=> result : true
Đánh lệnh sau vào terminal :
curl http://localhost:3000/api/v1/employee_data.json
=> result : [{"id":2,"name":"hoa nguyen","address":"Dan Phuong","age":25,"created_at":"2015-01-31T12:02:01.485Z","updated_at":"2015-01-31T12:03:59.679Z"}]
III. Lời kết
Ruby on Rails API có rất nhiều tính năng hay và thú vị để tìm hiểu nhưng trong phạm vi bài báo cáo chỉ có thể nói sơ lược qua về API và engine điển hình của nó là Grape và cách tạo một API đơn giản với Grape.
Source demo : https://github.com/nguyenhoa/emp_api
Để có thể tìm hiểu kỹ các bạn có thể tham khảo một số trang web sau :
https://github.com/intridea/grape http://www.theodinproject.com/ruby-on-rails/apis-and-building-your-own https://www.amberbit.com/blog/2014/2/19/building-and-documenting-api-in-rails/ https://developer.github.com/guides/getting-started/#authentication http://www.sitepoint.com/build-great-apis-grape/ http://funonrails.com/2014/03/building-restful-api-using-grape-in-rails/
All rights reserved