Cách xây dụng một API đơn giản trong ứng dụng Rails của bạn <Part 2>

Như đã nói ở bài trước Ở đây mình đã giới thiệu các bước cơ bản để xây dựng API cho Ruby on Rails (RoR). Bài viết hôm nay sẽ giới thiệu tiếp về cách xây dụng API trong RoR.

Hàm Authentication

Bất cứ viết code cho một chương trình xử lý nào việc bảo mật đều vô cùng quan trọng. Chính vì vậy xây dựng API trong RoR cũng vô cùng quan trọng và cần thiết.

Trong API bạn không thể sử dụng các tệp tin cookiessessions. Thay vào đó, khi người dùng muốn đăng nhập và gửi yêu cầu bằng phương thức HTTP POST với tên truy cập và mật khẩu của mình đến API lúc này API sẽ gửi lại cho người dùng một token, token này sẽ xác định được người dùng ấy là ai, và người dùng đã được chứng thực.

Trong mỗi yêu cầu xác thực của người dùng yêu cầu API xác thực, API sẽ trả về một mã token để xác thực. Nếu tìm thấy người dùng thì xác thực trả về true và người dùng được xác thực thành công, ngược lại không tìm thấy người dùng nào thì API trả về lỗi 401, lỗi này thông báo cho người dùng biết việc xác thực của bạn đã thất bại.

Đầu tiên chúng ta xây dựng một hàm callback để tạo ra token khi người dùng gọi đến hàm này, token sẽ được tạo ra. Hàm callback được viết như sau:

before_create :generate_authentication_token
def generate_authentication_token
  loop do self.authentication_token = SecureRandom.base64(64)
    break unless User.find_by(authentication_token: authentication_token)
  end
end

Sau đó chúng ta sẽ thêm xử lý sessions.

class Api::V1::SessionsController < Api::V1::BaseController
  def create
    user = User.find_by(email: create_params[:email])
    if user && user.authenticate(create_params[:password])
      self.current_user = user
      render(json: Api::V1::SessionSerializer.new(user, root: false).to_json, status: 201 )
    else
      return api_error(status: 401)
    end
  end

  private
  def create_params
    params.require(:user).permit(:email, :password)
  end
end

Sau đó chúng ta sẽ serializer sessions:

class Api::V1::SessionSerializer < Api::V1::BaseSerializer
  attributes :id, :email, :name, :admin, :token
  def token
    object.authentication_token
  end
end

Tiếp theo chúng ta sẽ xây dựng hàm xác thực người dùng, hàm được viết như sau:

def authenticate_user!
  token, options = ActionController::HttpAuthentication::Token.token_and_options(request)
  user_email = options.blank?? nil : options[:email]
  user = user_email && User.find_by(email: user_email)

  if user && ActiveSupport::SecurityUtils.secure_compare(user.authentication_token, token)
    @current_user = user
  else
    return unauthenticated!
  end
end

ActionController::HttpAuthentication::Token sẽ phân tích request gửi lên bởi người dùng thành tokenemail, request sẽ được phân tích ra trông như code bên dưới:

Authorization: Token token="VCiPlgG9fbQHkpjzp4JnVcDm2KR5zpu39xY2lx6kkMYXhkvIkTRGSfLAeaQH1aDls548d05a4QS4uJTOIYJ3/g==", email="[email protected]"

Lúc này current_user sẽ là biến lưu giá trị xác thực.

Hàm Authorization

Authorization giống như là giấy phép cho mà người dùng có thể xác thực được. Nói cách khác người dùng muốn xác thực được thì API đưa ra các yêu cầu và người dùng phải thực hiện đúng các yêu cầu đó.

Chúng ta sẽ xây dụng giấy phép yêu cầu người dùng phải tuân thủ, hàm này được viết như sau:

class UserPolicy < ApplicationPolicy
  def show?
    return true
  end

  def create?
    return true
  end

  def update?
    return true if user.admin?
    return true if record.id == user.id
  end

  def destroy?
    return true if user.admin?
    return true if record.id == user.id
  end

  class Scope < ApplicationPolicy::Scope
    def resolve
      scope.all
    end
  end
end

Trông nó rất đơn giản phải không các bạn. Tất nhiên rồi, đối với các lập trình viên đơn giản hiệu quả và tính bảo mật cao là vấn đề cốt lõi để giải quyết các vấn đề. Khi ta sử dụng API việc đơn giản hóa là rất quan trọng.

Hàm Phân Trang (pagination)

Phân trang là điều rất cần thiết với 1 API

  • Giúp ta giảm thiểu số lượng bản ghi hiển thị
  • Việc truy vấn giới hạn số lượng bản ghi giúp cho tốc độ load nhanh hơn.
def paginate resource
  resource = resource.page(params[:page] || 1)
  if params[:per_page]
    resource = resource.per_page(params[:per_page])
  end

  return resource
end

#expects pagination!
def meta_attributes(object)
  { current_page: object.current_page, next_page: object.next_page, prev_page: object.previous_page, total_pages: object.total_pages, total_count: object.total_entries }
end

Chúng ta vào file config/application.rb để đặt giá trị cần phân trang.

config.middleware.use Rack::RedisThrottle::Daily, max: 10000

Chúng ta thêm middleware vào file config.rb

config.middleware.insert_before 0, "Rack::Cors" do
  allow do origins '*'
    resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

Như vậy bạn có thể truy cập các method từ bất cứ nơi nào bạn muốn gọi.

Spec

Để kiểm tra các method trong API viết chuẩn hay chưa. Chúng ta lên viết Spec để kiểm tra các trường hợp dữ liệu đầu vào, đầu ra trả về đúng yêu cầu hay chưa. Phần code phía dưới là ví dụ về các viết Spec cho API của bạn:

describe Api::V1::UsersController, type: :api
  context :show do
    before do
      create_and_sign_in_user
      @user = FactoryGirl.create(:user)

      get api_v1_user_path(@user.id), format: :json
    end

    it 'returns the correct status' do
      expect(last_response.status).to eql(200)
    end

    it 'returns the data in the body' do
      body = HashWithIndifferentAccess.new(MultiJson.load(last_response.body))
      expect(body[:user][:name]).to eql(@user.name)
      expect(body[:user][:updated_at]).to eql(@user.updated_at.iso8601)
    end
  end
end

Trong Spec tôi có viết hàm create_and_sign_in_user để kiểm tra dữ liệu trả về đúng hay chưa. Đầu vào là tokenemail, đầu ra trả về người dùng đó xác thực thành công.

module AuthenticationHelper
  def sign_in user
    header('Authorization', "Token token=\"#{user.authentication_token}\", email=\"#{user.email}\"")
  end

  def create_and_sign_in_user
    user = FactoryGirl.create(:user)
    sign_in(user)
    return user
  end

  alias_method :create_and_sign_in_another_user, :create_and_sign_in_user

  def create_and_sign_in_admin
    admin = FactoryGirl.create(:admin)
    sign_in(admin)
    return admin
  end
end

RSpec.configure do |config|
  config.include AuthenticationHelper, :type=>:api
end

Vâng, vậy là qua 2 bài viết trên tôi đã hướng dẫn các bạn các bạn làm thế nào để xây dựng một API trong RoR là như thế nào. Mong rằng qua bài viết này sẽ giúp các bạn hiểu thế nào là xây dựng một API tốt cho các dự án của mình. Mọi ý kiến đóng góp cho bài viết, các bạn có thể để lại comment bên dưới. (thankyou)

Tài liệu tham khảo

All Rights Reserved