Cách xây dụng một API đơn giản trong ứng dụng Rails của bạn <Part 2>
Bài đăng này đã không được cập nhật trong 8 năm
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 cookies
và sessions
. 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 token
và email
, request
sẽ được phân tích ra trông như code bên dưới:
Authorization: Token token="VCiPlgG9fbQHkpjzp4JnVcDm2KR5zpu39xY2lx6kkMYXhkvIkTRGSfLAeaQH1aDls548d05a4QS4uJTOIYJ3/g==", email="vo.van.do@framgia.com"
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à token
và email
, đầ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