+3

Object Serializer với Fast JSON API trong Rails

Object serializer là gì ?

Object serializer là công cụ để chúng ta có thể chuyển các object, cụ thể ở đây là các object trong rails, sang dạng JSON để phục vụ cho các HTTP response trả về dữ liệu dạng JSON. Trong rails, để thực hiện serialize object, chúng ta có thể sử dụng các gem như fast_jsonapi, active_model_serializers hay có thể sửa dụng chính Active Model Serializer(AMS) mà rails cung cấp. Nhưng trong khuôn khổ bài viết này, mình sẽ giới thiệu tới các bạn gem fast_jsonapi nhé 😀

Giới thiệu về gem Fast JSON API

Fast JSON API cung cấp hầu như mọi chức năng mà Active Model Serializer cung cấp. Tuy nhiên, ưu điểm của Fast JSON API nằm ở tốc độ và hiệu năng, thậm chí có thể gấp tới 25 lần về mặt thời gian so với Active Model Serializer. Chúng ta có thể thấy qua ví dụ sau đây:

$ rspec
Active Model Serializer serialized 250 records in 138.71 ms
Fast JSON API serialized 250 records in 3.01 ms

Qua ví dụ trên thì chúng ta có thể thấy tốc độ của Fast JSON API vượt trội như thế nào đúng không 👍

Các tính năng của Fast JSON API

Về cơ bản, Fast JSON API sẽ cung cấp cho chúng ta những chức năng chính sau đây:

  • Cú pháp khai báo tương tự như Active Model Serializer
  • Hộ trợ các quan hệ belongs_to, has_many, has_one
  • Hỗ trợ các document ghép
  • Caching

Cài đặt

Để cài đặt Fast JSON API, chúng ta thêm dòng này vào gem file và sau đó chạy bundle install:

gem 'fast_jsonapi'

Sau khi cài đặt xong thì chúng ta có thể sử dụng các tính năng của Fast JSON API rồi 😀 Cùng tìm hiểu nhé!

Sử dụng

Chúng ta có thể sử dụng lệnh bundled generator

rails g serializer Movie name year

Lệnh này sẽ tạo ra trong project của bạn một serializer nằm trong app/serializers/movie_serializer.rb

Định nghĩa Model

class Movie
  attr_accessor :id, :name, :year, :actor_ids, :owner_id, :movie_type_id
end

Định nghĩa Serializer

Đây là phần quan trong nhất. Chúng ta sẽ định nghĩa xem một object sẽ được serialize như thế nào:

class MovieSerializer
  include FastJsonapi::ObjectSerializer
  set_type :movie  # optional
  set_id :owner_id # optional
  attributes :name, :year
  has_many :actors
  belongs_to :owner, record_type: :user
  belongs_to :movie_type
end

Cùng xem kết quả qua ví dụ sau nhé 😀

Chúng ta sẽ tạo một object của lớp Movie như sau:

movie = Movie.new
movie.id = 232
movie.name = 'test movie'
movie.actor_ids = [1, 2, 3]
movie.owner_id = 3
movie.movie_type_id = 1
movie

Sau đó chúng ta sẽ thực hiện serialize object này :

Return hash

hash = MovieSerializer.new(movie).serializable_hash

Return JSON

json_string = MovieSerializer.new(movie).serialized_json

Kết quả sẽ cho ra output như sau:

{
  "data": {
    "id": "3",
    "type": "movie",
    "attributes": {
      "name": "Superman!",
      "year": null
    },
    "relationships": {
      "actors": {
        "data": [
          {
            "id": "1",
            "type": "actor"
          },
          {
            "id": "2",
            "type": "actor"
          }
        ]
      },
      "owner": {
        "data": {
          "id": "3",
          "type": "user"
        }
      }
    }
  }
}

Attributes

Các attributes được định nghĩa trong Fast JSON API qua method attributes. Method này còn có alias là attribute, nó sẽ giúp code của chúng ta rõ ràng hơn khi định nghĩa một attribute đơn.

Mặc định thì các attributes của object serializer được định nghĩa bằng cách lấy các attributes cùng tên từ object trong Model. Ví dụ như class Movie ở trên, các thuộc tính như name, year,.. cũng sẽ được chuyển trực tiếp vào MovieSerializer:

class MovieSerializer
  include FastJsonapi::ObjectSerializer

  attribute :name, :year
end

Với Fast JSON API, chúng ta còn có thể tự định nghĩa các attributes bên cạnh các attributes có sẵn của object như sau:

class MovieSerializer
  include FastJsonapi::ObjectSerializer

  attributes :name, :year

  attribute :name_with_year do |object|
    "#{object.name} (#{object.year})"
  end
end

Bằng việc implement thêm attribute :name_with_year, object serializer sẽ có thêm thuộc tính :name_with_year như ta đã định nghĩa ở trên.

Ngoài ra, chúng ta có thể ghi đè các attributes mặc định (các attributes được lấy trực tiếp từ Model object):

class MovieSerializer
  include FastJsonapi::ObjectSerializer

  attribute :name do |object|
    "#{object.name} Part 2"
  end
  
end

Các thuộc tính có thể sử dụng dưới một cái tên khác bằng cách truyền method gốc hoặc accessor bằng shortcut proc:

class MovieSerializer
  include FastJsonapi::ObjectSerializer

  attributes :name

  attribute :released_in_year, &:year
end

Collection serialization

Bên cạnh việc serialize một object độc lập như các ví dụ mình đã trình bày ở trên, chúng ta còn có thể serialize một collection các object. Cú pháp khai báo hoàn toàn tương tự:

hash = MovieSerializer.new([movie, movie]).serializable_hash
json_string = MovieSerializer.new([movie, movie]).serialized_json

Chúng ta có thể truyền thêm option is_collection để quản lý rõ ràng hơn việc serialize một collection hay một single object riêng lẻ. Mặc định nếu ta không truyền option này vào thì Fast JSON API sẽ tự hiểu và tự phân biệt khi nào truyền vào một collection, khi nào truyền vào một single object. Tuy nhiên, không phải lúc nào Fast JSON API cũng có thể hiểu và phân biệt đúng như chúng ta mong muốn. Chính vì vậy mà Fast JSON API đã cung cấp cho chúng ta những option để quản lý việc serialize collection hoặc object dễ dàng hơn :

options[:is_collection] = nil # or true or false
hash = MovieSerializer.new([movie, movie], options).serializable_hash
json_string = MovieSerializer.new([movie, movie], options).serialized_json

Các option của is_colletion đó là :

  • nil hoặc không truyền vào: Fast JSON API sẽ tự hiểu xem resource truyền vào là collection hay single object (Tuy nhiên, sẽ có một số hạn chế như mình đã viết ở trên)
  • true: sẽ luôn coi resource truyền vào là một collection
  • false: sẽ luôn coi resource truyền vào là một single object

Params

Trong một số trường hợp, các attribute của serializer object có thể mang nhiều thông tin hơn những attribute của object trong model. Việc truyền các params sẽ giúp chúng ta xử lý các trường hợp khác nhau một các dễ dàng hơn. Nói thì hơi trừu tượng và khó hiểu, chúng ta cùng xem qua ví dụ sau nhé 😀

class MovieSerializer
  include FastJsonapi::ObjectSerializer

  attributes :name, :year
  attribute :can_view_early do |movie, params|
    # in here, params is a hash containing the `:current_user` key
    params[:current_user].is_employee? ? true : false
  end

end

# ...
current_user = User.find(cookies[:current_user_id])
serializer = MovieSerializer.new(movie, {params: {current_user: current_user}})
serializer.serializable_hash

Ở ví dụ trên, chúng ta sẽ truyền params[:current_user] vào trong attribute :can_view_early, attribute này sẽ trả về true nếu params[:current_user].is_employee? là true, và trả về false nếu ngược lại. Chính vì thế mà giá trị của attribute can_view_early sẽ phụ thuộc vào params mà chúng ta truyền vào.

Conditional Relationship

Conditional Relationship có thể định nghĩa bằng cách truyền một Proc qua từ khóa if:. Relationship sẽ được serialize nếu Proc trả về true và ngược lại, sẽ không được serialize nếu Proc trả về false, tương tự thì chúng ta có thể truyền vào cả record cả params. Cùng xem qua ví dụ sau đây nhé :

class MovieSerializer
  include FastJsonapi::ObjectSerializer

  # Actors will only be serialized if the record has any associated actors
  has_many :actors, if: Proc.new { |record| record.actors.any? }

  # Owner will only be serialized if the :admin key of params is true
  belongs_to :owner, if: Proc.new { |record, params| params && params[:admin] == true }
end

# ...
current_user = User.find(cookies[:current_user_id])
serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? }})
serializer.serializable_hash

Lời kết

Trên đây mình đã chia sẻ những kiến thức cơ bản nhất đủ để chúng ta có thể áp dụng Fast JSON API trong dự án của mình, để tìm hiểu thêm thì các bạn có thể xem qua link tham khảo mình để dưới đây nhé. Chúc các bạn thành công! https://github.com/Netflix/fast_jsonapi


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí