Learn Ruby on Rails API - GrapeAPI
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 ...
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/