Ruby on Rails: Xây dựng API Template hoàn chỉnh (Part 1)

Bài viết này mình gồm 2 nội dung chính:

  1. Cách xây dựng API app theo template (mình đã làm)
  2. Ý nghĩa các gem được cài đặt

I. Setting up API Rails App

Đầu tiên, khởi tạo project với API với option --api
Lưu ý option này chỉ hỗ trợ từ phiên bản Ruby >= 2.2.2 và Rails >= 5.0.0.
Trong bài viết này mình sử dụng rails 6.0.1

rails _6.0.1_ new api_app_name --api -T -d mysql

II. Using RSpec for Testing

1. Cài RSpec & Simplecov:

  • Lý do cài đặt RSpec đầu tiên vì nó sẽ giúp chúng ta tiết kiệm thời gian bằng cách sử dụng bộ RSpec generator, thì nó sẽ tiến hành generate tự động các file test controller và model khi ta sử dụng câu lệnh rails g scaffold để tự tạo các resoures nhanh chóng.
  • Để cài đặt RSpec, thêm gem rspec-rails vào Gemfile trong group :development, :test
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
  gem "shoulda-matchers"

  gem "simplecov"
  gem "simplecov-rcov"
  gem "simplecov-json"

  gem "ffaker"
end

Gem factory_bot_rails giúp mình tạo object cần thiết để test. Kết hợp với gem ffaker để tạo object với giá trị ngẫu nhiên.
Gem shoulda-matchers cung cấp phương thức giúp viết test case ngắn gọn. Xem thêm cú pháp tại đây
Gem simplecov giúp thống kê % coverage của unit test mà mình viết.

Tiến hành update bundle, sau đó cài đặt RSpec

bundle
rails g rspec:install

2. Config factory_bot

  • Để sử dụng các phương thức của factory_bot, cần phải cấu hình rspec để nhận syntax của factory_bot.
    Ở file spec/rails_helper.rb:
      RSpec.configure do |config|
        config.include FactoryBot::Syntax::Methods
      end
    

3. Config shoulda-matchers

ngay đầu file spec/rails_helper.rb thêm:

require "shoulda/matchers"

cuối file spec/rails_helper.rb thêm:

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

4. Config simplecov

ngay đầu file spec/spec_helper.rb thêm:

require "simplecov"
SimpleCov.start "rails"
require "factory_bot"

5. Config ffaker

Ở thư mục spec/factories/ tạo file có tên tương ứng với object cần tạo.
Ví dụ tạo file users.rb theo format sau:

FactoryBot.define do
  factory :user do
    name {FFaker::Name.name}
    email {FFaker::InternetSE.safe_email}
  end
end

Sau khi chạy xong test, có thể mở file coverage/index.html bằng trình duyệt để xem.
Đưa thư mục coverage vào .gitignore

echo "coverage" >> .gitignore

III. Integration Rubocop & CI

1. Thiết lập Rubocop:

1.1. Cài đặt gem rubocop vào Gemfile

Với Ruby 2.5.x trở về trước

group :development, :test do
  gem "rubocop"
end

Với Ruby 2.6.x trở đi

group :development, :test do
  gem "rubocop", require: false
  gem "rubocop-checkstyle_formatter", require: false
end

1.2. Tải tệp nén tương ứng với phiên bản rubocop đã cài đặt ở bước 1:

Với Ruby 2.5.x trở về trước Tải file
Với Ruby 2.6.x trở đi Tải file

Sau đó copy 3 file trong tệp nén vừa tải về:

.rubocop.yml
.rubocop_disabled.yml
.rubocop_enabled.yml

Paste vào thư mục dự án, ngang hàng với Gemfile.

1.3. Chạy rubocop trước mỗi lần commit gửi pull bằng lệnh:

rubocop

2. Thiết lập CI

Tùy vào yêu cầu CI riêng ở mỗi công ty, các bạn cài đặt theo yêu cầu của công ty đó nhé
2.1. Tiến hành chạy kiểm tra CI
2.2. Cấp quyền cho file report CI
2.3. Xem lại reports của CI vừa chạy ở thư mục report tương ứng với CI vừa cài đặt:
2.4. Đưa một số file Ci report vào .gitignore

echo "<file>" >> .gitignore

IV. SETUP DATABASE

Thêm gem dotenv-rails để thiết lập biến môi trường .env
Trong database.yml, config như sau:

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: <%= ENV.fetch("DATABASE_HOST", "localhost") %>
  username: <%= ENV.fetch("DATABASE_USERNAME", "root") %>
  password: <%= ENV["DATABASE_PASSWORD"] %>
  socket: <%= ENV.fetch("DATABASE_SOCKET", "/var/run/mysqld/mysqld.sock") %>

Rồi tạo DB

rails db:create

V. Building Your API

Khi app được khởi tạo với option --api, ta có thể dùng generator scaffold mặc định để generate API resources như thông thường mà không cần thêm tham số đặc biệt nào

rails g scaffold user name email

Khi chạy xong nó sẽ tạo ra các file có cấu trúc như sau:

invoke  active_record
create    db/migrate/20191107015736_create_users.rb
create    app/models/user.rb
invoke    rspec
create      spec/models/user_spec.rb
invoke      factory_girl
create        spec/factories/users.rb
invoke  resource_route
  route    resources :users
invoke  scaffold_controller
create    app/controllers/users_controller.rb
invoke    rspec
create      spec/controllers/users_controller_spec.rb
create      spec/routing/users_routing_spec.rb
invoke      rspec
create        spec/requests/users_spec.rb

Tiến hành migrate DB và chạy app:

rails db:migrate

VI. Serializing API Output

Ở view chúng ta thường dùng jbuilder để quản lý dữ liệu trả về dưới dạng JSON, nhưng ở app API ta sẽ dùng AMS(Active Model Serializers) để quản lý việc này. AMS cung cấp layer giữa model và controller bằng cách gọi to_json hoặc as_json cho object/collection ActiveRecord, trong khi vẫn xuất ra định dạng mà API mong muốn.

Add Gemfile:

gem "active_model_serializers"

Update bundle:

bundle

Tạo một serializer cho User model:

rails g serializer user

Nó sẽ tạo ra file:

class UserSerializer < ActiveModel::Serializer
  attributes :id
end

mặc định sẽ có sẵn attr :id, thêm attr nếu muốn hiển thị thêm attributes của object/collection ở API như sau:

class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :email
end

Phần này khá dài nên mình chia làm 2 phần, hẹn gặp lại các bạn ở phần sau

Nếu có gì thiếu sót hoặc góp ý thêm, bạn đọc hãy cứ comment để mình có thể hoàn thiện hơn nhé. Peace!