11/08/2018, 22:08

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_keyprivate
  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
  endend

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/

0