APIS ON RAILS - Chapter 3: Presenting the users
Bài đăng này đã không được cập nhật trong 6 năm
Trong 2 chap trước thì chúng ta đã thiết kế được bộ khung của app rồi, thậm chí chúng ta đã thêm được phiên bản thông qua headers. Trong bài viết này thì chúng ta sẽ tạo ra products cho từng user và mỗi user có thể tạo order. Bạn có thể clone project ở 2 chap trước bằng link sau:
git clone https://github.com/kurenn/market_place_api.git -b chapter2
Chắc hẳn bạn cũng biết có rất nhiều cách để xử lý đăng nhập trong Rails như Authlogic, Clearance và Devise - Trong bài viết này thì chúng ta sẽ sử dụng Devise. Nào, giờ chúng ta cùng bắt đầu nào:
git checkout -b chapter3
3.1. User model
Đầu tiên chúng ta cần thêm gem devise vào trong Gemfile:
gem "devise"
Sau đó chạy bundle install để cài đặt gem vừa thêm, sau khi lệnh bundle chạy thành công, ta cần chạy lệnh dưới để generate devise:
rails g devise:install
Tiếp theo chúng ta chạy lệnh sau để tạo ra model User:
rails g devise User
Kể từ bây giờ, mỗi khi chúng ta tạo model thì Rails cũng sẽ tự tạo ra một file factory cho model User này. Điều này sẽ giúp chúng ta dễ dàng tạo test và chạy thử.
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
end
end
Giờ thì chạy lệnh migrate database và chuẩn bị cho việc test database nào:
rake db:migrate
rake db:test:prepare
Sau khi chạy test ok rồi thì chúng ta tạo commit cho những bước trên thôi:
git add .
git commit -m "Adds devise user model"
3.1.1. First user tests
Chúng ta sẽ thê một số spec để bảo đảm rằng model User sẽ trả về các thuộc tính email, password và password_confirmation được cung cấp bởi devise. Để tiện hơn cho việc test, chúng ta sẽ thêm những thuộc tính trên vào factory:
FactoryGirl.define do
factory :user do
email { FFaker::Internet.email }
password "12345678"
password_confirmation "12345678"
end
end
Sau khi thêm những thuộc tính trên vào model User, giờ thì chúng ta test thử nào:
require 'spec_helper'
describe User do
before { @user = FactoryGirl.build(:user) }
subject { @user }
it { should respond_to(:email) }
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should be_valid }
end
Bởi vì chúng ta đã tạo ra database để test từ trước với rake db:test:prepare rồi nên giờ chỉ cần chạy:
bundle exec rspec spec/models/user_spec.rb
Giờ thì chúng ta commit tiếp thôi:
git add .
git commit -am "Adds user firsts specs"
3.1.2. Imroving validation tests
Bây giờ chúng ta thử viết vài test cho validate email của thằng User, đầu tiên nên thêm gem shoulda-matchers vào Gemfile trước:
gem "shoulda-matchers"
Viết thử testcase nào:
describe "when email is not present" do
before { @user.email = " " }
it { should_not be_valid }
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should validate_confirmation_of(:password) }
it { should allow_value('example@domain.com').for(:email) }
end
Xong phần viết test cho User email rồi, giờ thì commit lại nào:
git add .
git commit -m "Adds shoulda matchers for spec refactors"
3.2. Building users endpoints
Hiện tại chúng ta chỉ mới thêm action show cho user để có thể hiển thị record User ở trong file json. Đầu tiên, chúng ta cần tạo ra users_controller, sau đó thêm những testcase thích hợp và tiến hành code thật sự.
rails g controller users
Câu lệnh này cũng sẽ tạo ra users_controllers_spec.rb. Trước khi chúng ta đi vào viếdt test thì cần biết thêm về 2 bước cơ bản khi test api.
- JSON sẽ được trả về từ server.
- Server sẽ trả về trạng thái của code. VD: 200, 201, 204, ...
Để giữ cho code của chúng ta dễ quản lý thì cần tạo một số thư mục bên trong thư mục spec controller để thuận tiện cho việc setup tiếp theo.
mkdir -p spec/controllers/api/v1
mv spec/controllers/users_controller_spec.rb spec/controllers/api/v1
Sau khi tạo ra thư mục tương ứng bằng câu lệnh trên thì chúng ta chuyển phần tên sau describe, từ UsersController sang Api::V1::UsersController:
require "spec_helper"
describe Api::V1::UsersController do
end
Giờ thì thêm các testcase vào nha:
require 'spec_helper'
describe Api::V1::UsersController do
before(:each) { request.headers['Accept'] = "application/vnd.marketplace.v1" }
describe "GET #show" do
before(:each) do
@user = FactoryGirl.create :user
get :show, id: @user.id, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql @user.email
end
it { should respond_with 200 }
end
end
Sau khi thêm test như vậy thì chúng ta cũng cần chỉnh sửa controller lại cho phù hợp:
require 'spec_helper'
describe Api::V1::UsersController do
before(:each) { request.headers['Accept'] = "application/vnd.marketplace.v1" }
describe "GET #show" do
before(:each) do
@user = FactoryGirl.create :user
get :show, id: @user.id, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql @user.email
end
it { should respond_with 200 }
end
end
Hiện tại khi chạy test thì sẽ báo lỗi vì thiếu routes, do đó thêm phần routes vào thôi:
require 'api_constraints'
MarketPlaceApi::Application.routes.draw do
devise_for :users
# Api definition
namespace :api, defaults: { format: :json },
constraints: { subdomain: 'api' }, path: '/' do
scope module: :v1,
constraints: ApiConstraints.new(version: 1, default: true) do
# We are going to list our resources here
resources :users, :only => [:show]
end
end
end
Nếu bạn chạy lại bundle exec rspec spec/controllers, bạn sẽ thấy tất cả các testcase đều pass lại rồi. Vậy là xong rồi, giờ chúng ta commit lại nhé:
git add .
git commit -m "Adds show action the users controller"
3.2.1. Testing endpoints with CURL
Hiện tại, base uri của chúng ta là api.market_place_api.dev
curl -H 'Accept: application/vnd.marketplace.v1' \http://api.market_place_api.dev/users/1
Khi chạy dòng trên thì nó sẽ quăng ra lỗi, có lẽ bạn cũng đoán được rồi, chúng ta chưa có tạo dữ liệu cho User nên không có User với id là 1. Vì vậy, tiến hành tạo nó trước cái đã:
rails console
User.create({email: "example@marketplace.com", password: "12345678", password_confirmation: "12345678"})
Sau khi tạo User thành công thì khi chạy lệnh vừa nãy sẽ hiển thị ra thông tin của User với id là 1. Nếu đến đây bạn chạy vẫn có lỗi thì hãy vào application_controllers để update đoạn code sau:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
end
Sau khi update application_controllers xong thì chúng ta commit chúng lại nào:
git add -A
git commit -m "Updates application controller to prevent CSRF exception from being raised"
3.2.2. Creating users
Đầu tiên cần viết test trước khi code, do đó chúng ta thêm testcase cho hàm create nhé:
describe "POST #create" do
context "when is successfully created" do
before(:each) do
@user_attributes = FactoryGirl.attributes_for :user
post :create, { user: @user_attributes }, format: :json
end
it "renders the json representation for the user record just created" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql @user_attributes[:email]
end
it { should respond_with 201 }
end
context "when is not created" do
before(:each) do
#notice I'm not including the email
@invalid_user_attributes = { password: "12345678",
password_confirmation: "12345678" }
post :create, { user: @invalid_user_attributes }, format: :json
end
it "renders an errors json" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response).to have_key(:errors)
end
it "renders the json errors on why the user could not be created" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:errors][:email]).to include "can't be blank"
end
it { should respond_with 422 }
end
end
Hiện tại khi chúng ta chạy test thì sẽ báo lỗi nên cần thêm update lại code để test có thể xanh:
[...]
def create
user = User.new(user_params)
if user.save
render json: user, status: 201, location: [:api, user]
else
render json: { errors: user.errors }, status: 422
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
[...]
Sau khi chạy test lại thì mọi test đã pass rồi, tiến hành commit nó lại nào:
git add .
git commit -m "Adds the user create endpoint"
3.2.3. Updating users
Code để update user tương tự như tạo mới vậy, do đó rất dễ dàng để hiểu đoạn code dưới:
describe "PUT/PATCH #update" do
context "when is successfully updated" do
before(:each) do
@user = FactoryGirl.create :user
patch :update, { id: @user.id,
user: { email: "newmail@example.com" } }, format: :json
end
it "renders the json representation for the updated user" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql "newmail@example.com"
end
it { should respond_with 200 }
end
context "when is not created" do
before(:each) do
@user = FactoryGirl.create :user
patch :update, { id: @user.id,
user: { email: "bademail.com" } }, format: :json
end
it "renders an errors json" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response).to have_key(:errors)
end
it "renders the json errors on whye the user could not be created" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:errors][:email]).to include "is invalid"
end
it { should respond_with 422 }
end
end
Tương tự như phần trên thì hiện tại khi chạy test thì kết quả vẫn chưa xanh được. Do đó chúng ta cần thêm một số đoạn code sau:
# routes.rb
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
# We are going to list our resources here
resources :users, :only => [:show, :create, :update]
end
# app/controllers/api/v1/users_controller.rb
def update
user = User.find(params[:id])
if user.update(user_params)
render json: user, status: 200, location: [:api, user]
else
render json: { errors: user.errors }, status: 422
end
end
Sau khi thêm đoạn code trên vào thì code đã chạy xanh được rồi, tiến hành add commit thôi:
git add .
git commit -m "Adds update action the users controller"
3.2.4. Destroying users
Tương tự như create và update thì xoá cũng trong khả năng viết được của chúng ta:
#spec/controllers/api/v1/users_controller_spec.rb
describe "DELETE #destroy" do
before(:each) do
@user = FactoryGirl.create :user
delete :destroy, { id: @user.id }, format: :json
end
it { should respond_with 204 }
end
Thêm đoạn code sau vào trong app của chúng ta:
def destroy
user = User.find(params[:id])
user.destroy
head 204
end
# app/routes.rb
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
# We are going to list our resources here
resources :users, :only => [:show, :create, :update, :destroy]
end
Sau khi chạy test báo xanh thì chúng ta add commit thôi:
git add .
git commit -m "Adds destroy action to the users controller"
Đến đây thì bài số 3 của series "API ON RAILS" đã kết thúc, cảm ơn các bạn đã theo dõi! Nguồn: http://apionrails.icalialabs.com/book/chapter_three
All rights reserved