APIS ON RAILS - Chapter 3: Presenting the users
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 ...
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
Đầ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"
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"
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"
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"
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"
Đầ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"
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"
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