Renderizar JSON con Rails y Active Model Serializer
Entreguemos la data desde nuestra API de una manera más custom.
Las API simplifican el desarrollo de las aplicaciones, lo cual permite ahorrar tiempo a los desarrolladores y dinero a las empresas. Cuando diseña herramientas y productos nuevos (o maneja otros actuales), las API le otorgan flexibilidad; simplifican el diseño, la administración y el uso; y proporcionan oportunidades para la innovación.
Hoy en día existen diversas maneras cuando se trata de servir data desde un API. Podría realizarse en Node con ExpressJS, en Go con Martini, entre otros. Pero en muchos casos se requiere desarrollar algo tan rápido y personalizado como sea posible, para esos casos mi elección es Ruby on Rails.
Esto debido, entre otras cosas, a que renderizar data en formato JSON es tan fácil como realizar un render :json. Por cierto, ¿que es JSON?.
Sin embargo, fuera de la caja, Rails sirve la data de una manera “fea”. En este artículo vamos a aprender a crear respuestas personalizadas JSON con ActiveModel::Serializer. Creando respuestas JSON en Rails es sumamente fácil, pero utilizando las ventajas por defecto del framework no es suficiente, además de no poder probarse de una manera sencilla.
Esta gema nos permite darle formato y organización a nuestra data JSON. Podemos seleccionar solo la información que queremos, además de tener acceso a las asociaciones de un objeto, todo al resolver una simple solicitud HTTP. Veamos tan solo este simple ejemplo y comparemos los resultados:
Acá podemos ver como se muestra data que puede no ser relevante como lo son “created_at”, “updated_at” e incluso un atributo que podría ser sensible como lo es el “id”.
Y acá podemos observar como se filtra la data innecesaria de estos objetos, para solo mostrar el atributo que nos interesa.
Creando los archivos comunes
Para la utilización de un Serializer, debes tener definidos esto por cada recurso que lo requieras:
- Rutas
- Migraciones y Modelos
- Controladores
- Algunos registros desde seeds
Instalación
Luego que termines de configurar tu proyecto Rails, solo debes añadir la gema serializer a tu Gemfile, gem 'active_model_serializers', '~> 0.10.0'
, y luego correr bundle install
. Gracias a Rails y su magia, eso es todo lo que se necesita.
Generando un archivo serializer
Podemos utilizar el comando rails g serializer customer
, o podemos crear la carpeta app/serializers y luego el archivo app/serializers/customer_serializer.rb, adentro debemos tener algo como esto:
class CustomerSerializer < ActiveModel::Serializer attributes :name, :lastname, :address, :phone, :country_idend
Ya que instalamos Serializer, Rails ahora buscará dicha carpeta antes de renderizar un recurso. Cada modelo necesita su archivo serializer correspondiente. Esto es debido a que cuando se le pase un objeto a render :json
en el controller, ahora dependerá del Serializer cuales atributos realmente se van a mostrar.
De esta manera, al recibir una solicitud del listado de clientes, al renderizar los objetos en formato JSON, solo se mostrarán los atributos que acá se definan. Por defecto este será el mismo comportamiento en los otros métodos definidos en el controller(show, create, update, destroy).
¿Que tal si realizas el serializer del recurso Book?
¿Eso resulto bastante bien o no?. Sin embargo, si nos fijamos detenidamente en el JSON resultado de Customer, tenemos el atributo "country_id": 3
, lo cual es bastante ambiguo si continuamos con nuestro objetivo de organizar y mostrar la data de una mejor manera.
Trayendo las asociaciones de un recurso
En el serializer se puede definir mostrar los datos de los recursos asociados:
class CustomerSerializer < ActiveModel::Serializerattributes :name, :lastname, :address, :phonebelongs_to :countryend
Se buscarán y mostrarán los atributos que estén definidos en el serializer de Country, por lo cual debemos crearlo.
class CountrySerializer < ActiveModel::Serializer attributes :nameend
Así se muestra ahora:
De esta manera se puede crear un objeto JSON “anidado” dentro de otro objeto JSON, los cuales están relacionados a través de la clave foránea.
Tal como acabamos de observar como obtener los datos de un recurso asociado a través de belongs_to
, se puede realizar con has_one
o con has_many
, por ejemplo, en el caso contrario:
class CountrySerializer < ActiveModel::Serializer attributes :name has_many :customersend
En este caso obtenemos una colección representada por un arreglo de objetos JSON, cada uno de esos objetos tiene sus atributos(name). Uno de esos atributos es “customers” cuyo valor es un arreglo de objetos.
Definiendo métodos propios
En muchas ocasiones queremos mostrar la data a nuestra propia manera, esto debido a lógica de negocios. Para ello podemos realizar métodos en cada uno de los serializers. Por ejemplo, si queremos mostrar el nombre completo de un “customer”:
class CustomerSerializer < ActiveModel::Serializer attributes :full_name def full_name
"{#{object.name}#{object.lastname}}"
end
end
Definimos el método full_name, y dentro de el se retorna un string con llaves donde se está interpolando el nombre y el apellido del objeto. Tenemos acceso a el a través de object
.
¿Sin embargo, que es ese objeto?
Ese objeto hace referencia a la instancia de CustomerSerializer la cual tiene un atributo llamado object, el cual es el objeto JSON que se esté serializando en ese momento. Digo esto por que el Serializer itera sobre cada objeto que se le haya dado al ser una colección(index) y hace lo mismo al ser solo uno(show).
¿Que tal si realizas un método propio donde se muestre la data como tu quieras?
Para finalizar, debemos saber que la gema ActiveModelSerializers permite una serie de opciones que pueden ser utilizadas dependiendo de lo que se requiera, las podemos encontrar aquí.