Bogdanov Anton
Posted on March 12, 2024
One of the main features of GrahpQL compared to the REST api - GraphQL APIs let clients query the exact data they need, while REST APIs just returns all data described in serializers.
While I prefer using REST API for my rails apps I tried to find solution for optimizing responses. I made some attempts with multiple serializers for different controllers and for the same models, but it leds to mess.
With using jsonapi-serializer I found solution.
Attributes of serializer can be optional with if and proc
class UserSerializer < ApplicationSerializer
attribute :confirmed, if: proc { |_, params| required_field?(params, 'confirmed') }, &:confirmed?
attribute :banned, if: proc { |_, params| required_field?(params, 'banned') }, &:banned?
end
During serialization such attributes will be or will not be serialized based on provided params.
Attribute will be serialized if it presents in include_fields or does not present in exclude_fields.
class ApplicationSerializer
def self.required_field?(params, field_name)
params[:include_fields]&.include?(field_name) || params[:exclude_fields]&.exclude?(field_name)
end
end
And this is how part in controllers works
SERIALIZER_FIELDS = %w[confirmed banned].freeze
def index
render json: {
user: UserSerializer.new(
current_user, params: serializer_fields(UserSerializer, SERIALIZER_FIELDS)
).serializable_hash
}, status: :ok
end
Method serializer_fields generates hash with include/exclude attributes based on params and compares it with available attributes from serializer. Later that hash is used in serializer.
def serializer_fields(serializer_class, default_include_fields=[])
@serializer_attributes = serializer_class.attributes_to_serialize.keys.map(&:to_s)
return {} if response_include_fields.any? && response_exclude_fields.any?
return { include_fields: response_include_fields } if response_include_fields.any?
return { exclude_fields: response_exclude_fields } if response_exclude_fields.any?
return { include_fields: default_include_fields } if default_include_fields.any?
{}
end
def response_include_fields
@response_include_fields ||= params[:response_include_fields]&.split(',').to_a & @serializer_attributes
end
def response_exclude_fields
@response_exclude_fields ||= params[:response_exclude_fields]&.split(',').to_a & @serializer_attributes
end
This approach allows to achieve:
preventing over-fetching data by REST API,
tracking required attributes for responses (maybe some of them are not used at all),
usually controller could have some .includes for optimization, and based on required attributes such optimizations/additional requests to DB can be skipped.
I hope this approach will help somebody with REST API optimization their Rails apps.
Posted on March 12, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.