013.Making api with grape (Entity).

Thach
Written by Thach on
013.Making api with grape (Entity).

Phần này về cơ bản giúp các bạn dựng serializer, nôm na là làm sao để định dạng cục json được trả ra.

Chúng ta sẽ sử dụng gem grape-enity cho phần này.

Cách dùng cơ bản nhất

Dĩ nhiên, đầu tiên là khai báo gem 'grape-enity' vào Gemfile và bundle install

Cách sử dụng Entity cũng khá tương đồng với ActiveModelSerializer, tôi sẽ đặt tất cả entity vào thư mục app/controllers/entities/v1. Các bạn thích đặt ở đâu cũng ổn cả.

Giờ hãy viết một file entity cho user, trả về idname.

# app/controllers/api/entities/v1/user_entity.rb

module API::Entities::V1::UserEntity
  class Index < Grape::Entity
    expose :id
    expose :name
  end
end

Để có data user, hãy tạo một table users và model cho nó

rails g migration CreateUsers name:string country:string
rails db:migrate
# app/models/user.rb

class User < ApplicationRecord
end

Insert một ít data vào, và giờ thì ở controller, chúng ta đã có thể sử dụng entity phía trên để trả về dữ liệu rồi

# app/controllers/api/v1/users/index.rb

module API::V1::Users
  class Index < Grape::API
    get '' do
      status :ok
      content_type 'application/json'
      users = User.limit(2)
      API::Entities::V1::UserEntity::Index.represent(users)
    end
  end
end

Gọi api để kiểm tra kết quả

[
    {
        "id": 1,
        "name": "Nolan"
    },
    {
        "id": 2,
        "name": "Messi"
    }
]

Phân trang

Thêm gem kaminari để phân trang nhé các bro.

# Gemfile
gem 'kaminari'

Để có thể hoàn thiện chức năng paging, chúng ta cần trả thêm một số field như total_pages, current_page …

Hiện tại thì format trả về của cty tôi nó là thế này, những field cần thể hiển thị UI phân trang sẽ nằm trong metadata

{
    "metadata": {
        "total_count": 19,
        "total_pages": 10,
        "next_page": 2,
        "prev_page": 0,
        "current_page": 1,
        "current_per_page": 2
    },
    "message": "Success",
    "code": 2000,
    "status": true,
    "data": [
        {
            "name": "Nolan",
            "id": 1
        },
        {
            "name": "Messi",
            "id": 2
        }
    ]
}

Và để trả ra vừa data, vừa có metadata, giờ chúng ta sẽ viết thêm một PresenterHelper và khai báo vào trong base

# app/controller/api/helpers/presenter_helper.rb

module API::Helpers::PresenterHelper
  def response_data(data, message, metadata)
    status(200)
    {metadata:, message:, code: 2000, status: true, data:}
  end

  def present_pagination(collection)
    {
      total_count: collection.total_count,
      total_pages: collection.total_pages,
      next_page: collection.next_page || 0,
      prev_page: collection.prev_page || 0,
      current_page: collection.current_page,
      current_per_page: collection.current_per_page
    }
  end
end
#app/controller/api/v1/base.rb

module API
  module V1
    class Base < Grape::API
      include API::V1::Version

      helpers API::Helpers::PresenterHelper

      mount API::V1::Users::Base
      # mount API::V1::Posts::Base
    end
  end
end

Giờ thì trong controller, đã có thể dùng hàm response_data để trả về data kèm theo metadata

# app/controllers/api/v1/users/index.rb

module API::V1::Users
  class Index < Grape::API
    params do
      optional :page, type: String
      optional :per_page, type: String
    end

    get '' do
      status :ok
      content_type 'application/json'
      users = User.all.page(params[:page]).per(params[:per_page])
      response_data(
        API::Entities::V1::UserEntity::Index.represent(users),
        'Success',
        present_pagination(users)
      )
    end
  end
end

Errors

Tương tự response_data, chúng ta có thể viết thêm một số hàm để render error, cái này cũng gặp khá thường xuyên, khi bạn cần trả về lỗi cho FE hiển thị tới người dùng.

# app/controller/api/helpers/presenter_helper.rb

module API::Helpers::PresenterHelper
  def response_data(data, message, metadata)
    status(200)
    {metadata:, message:, code: 2000, status: true, data:}
  end

  def response_message(message, messages, status_code, code)
    status(status_code)
    {message:, messages:, code:, status: true}
  end

  def response_error(message, messages, status_code, code)
    status(status_code)
    {message:, messages:, code:, status: false}
  end
end

Comments

comments powered by Disqus