Elaborando APIs con Rails

Hoy en día es una práctica común depender en gran medida de las API (interfaces de programación de aplicaciones). No solo los servicios grandes como Facebook y Twitter los emplean, las API son muy populares debido a la expansión de marcos del lado del cliente como React, Angular y muchos otros. Ruby on Rails sigue esta tendencia y la última versión presenta una nueva función que le permite crear aplicaciones solo para API. 

Inicialmente, esta funcionalidad se empaquetó en una gema separada llamada rails-api, pero desde el lanzamiento de Rails 5, ahora es parte del núcleo del marco. Esta característica junto con ActionCable fue probablemente la más esperada, por lo que hoy vamos a discutirla.

Este artículo trata sobre cómo crear aplicaciones Rails solo para API y explica cómo estructurar sus rutas y controladores, responder con el formato JSON, agregar serializadores y configurar CORS (Intercambio de recursos entre orígenes). También aprenderá acerca de algunas opciones para asegurar la API y protegerla contra el abuso.

La fuente de este artículo está disponible en GitHub..

Creando una aplicación solo para API

Para comenzar, ejecute el siguiente comando:

rieles nuevos RailsApiDemo --api

Va a crear una nueva aplicación Rails solo para API llamada RailsApiDemo. No olvides que el soporte para el --api La opción se agregó solo en Rails 5, así que asegúrese de tener esta o una versión más reciente instalada.

Abre el Gemfile y tenga en cuenta que es mucho más pequeño de lo habitual: gemas como barandillas de cafe, enlaces turbo, y barandillas se fueron.

los config / application.rb archivo contiene una nueva línea:

config.api_only = true

Esto significa que Rails va a cargar un conjunto más pequeño de middleware: por ejemplo, no hay cookies y soporte de sesiones. Además, si intenta generar un andamio, las vistas y los activos no se crearán. En realidad, si marca la vistas / diseños directorio, usted notará que el aplicacion.html.erb Falta el archivo también.

Otra diferencia importante es que la Controlador de aplicaciones hereda de la ActionController :: API, no ActionController :: Base.

Eso es prácticamente todo, esta es una aplicación básica de Rails que has visto muchas veces. Ahora agreguemos un par de modelos para que tengamos algo con qué trabajar:

rieles modelo g Nombre de usuario: cadena rieles modelo g Título de la publicación: cadena cuerpo: texto usuario: belongs_ to rails db: migrate

No hay nada especial aquí: una publicación con un título, y un cuerpo pertenece a un usuario.

Asegúrese de que las asociaciones correctas estén configuradas y también proporcione algunas verificaciones de validación simples:

modelos / usuario.rb

 has_many: mensajes valida: nombre, presencia: verdadero

modelos / post.rb

 belongs_to: usuario valida: título, presencia: verdadero valida: cuerpo, presencia: verdadero

¡Brillante! El siguiente paso es cargar un par de registros de muestra en las tablas recién creadas.

Cargando datos de demostración

La forma más fácil de cargar algunos datos es utilizando el semillas.rb archivo dentro de la db directorio. Sin embargo, soy perezoso (como lo son muchos programadores) y no quiero pensar en ningún contenido de muestra. Por lo tanto, ¿por qué no aprovechamos la gema falsa que puede producir datos aleatorios de varios tipos: nombres, correos electrónicos, palabras inconformistas, textos de "lorem ipsum" y mucho más?.

Gemfile

grupo: desarrollo do gema 'faker' final

Instala la gema:

instalación de paquete

Ahora pellizque el semillas.rb:

db / seeds.rb

5.times do user = User.create (name: Faker :: Name.name) user.posts.create (title: Faker :: Book.title, body: Faker :: Lorem.sentence) end

Por último, carga tus datos:

rieles db: semilla

Respondiendo con JSON

Ahora, por supuesto, necesitamos algunas rutas y controladores para diseñar nuestra API. Es una práctica común anidar las rutas de la API bajo el api / camino. Además, los desarrolladores suelen proporcionar la versión de la API en la ruta, por ejemplo api / v1 /. Más adelante, si hay que introducir algunos cambios de última hora, simplemente puede crear un nuevo espacio de nombres (v2) y un controlador separado.

Aquí es cómo pueden verse sus rutas:

config / route.rb

espacio de nombres 'api' hacer espacio de nombres 'v1' hacer recursos: publica recursos: los usuarios finalizan

Esto genera rutas como:

api_v1_posts GET /api/v1/posts(.:format) api / v1 / posts # index POST /api/v1/posts(.:format) api / v1 / posts # create api_v1_post GET / api / v1 / posts /: id (.: format) api / v1 / posts # show

Puedes usar un alcance método en lugar de espacio de nombres, pero entonces por defecto buscará el Controlador de usuarios y PostsController dentro de controladores directorio, no dentro de la controladores / api / v1, así que ten cuidado.

Crear el api carpeta con el directorio anidado v1 dentro de controladores. Llénalo con tus controladores:

controladores / api / v1 / users_controller.rb

módulo Api módulo V1 clase UsersController < ApplicationController end end end

controladores / api / v1 / posts_controller.rb

módulo Api módulo V1 clase PostsController < ApplicationController end end end

Tenga en cuenta que no solo tiene que anidar el archivo del controlador bajo la api / v1 camino, pero la clase en sí también tiene que estar espaciada dentro de la Api y V1 módulos.

La siguiente pregunta es ¿cómo responder correctamente con los datos en formato JSON? En este artículo probaremos estas soluciones: las gemas jBuilder y active_model_serializers. Así que antes de pasar a la siguiente sección, colóquelos en la Gemfile:

Gemfile

gema 'jbuilder', '~> 2.5' gema 'active_model_serializers', '~> 0.10.0'

Entonces corre:

instalación de paquete

Usando la gema jBuilder

jBuilder es una joya popular mantenida por el equipo de Rails que proporciona un simple DSL (lenguaje específico del dominio) que le permite definir estructuras JSON en sus vistas.

Supongamos que queremos mostrar todas las publicaciones cuando un usuario toca el índice acción:

controladores / api / v1 / posts_controller.rb

 def index @posts = Post.order ('created_at DESC') final

Todo lo que necesita hacer es crear la vista que lleva el nombre de la acción correspondiente con la .json.jbuilder extensión. Tenga en cuenta que la vista se debe colocar debajo de la api / v1 camino también

vistas / api / v1 / posts / index.json.jbuilder

json.array! @posts hacer | publicar | json.id post.id json.title post.title json.body post.body end

json.array! atraviesa el @posts formación. json.id, json.title y json.body generar las claves con los nombres correspondientes estableciendo los argumentos como los valores. Si navega a http: // localhost: 3000 / api / v1 / posts.json, verá una salida similar a esta:

["id": 1, "title": "Title 1", "body": "Body 1", "id": 2, "title": "Title 2", "body": "Body 2 "]

¿Y si también quisiéramos mostrar al autor para cada publicación? Es sencillo:

json.array! @posts hacer | publicar | json.id post.id json.title post.title json.body post.body json.user do json.id post.user.id json.name post.user.name end end

La salida cambiará a:

["id": 1, "title": "Title 1", "body": "Body 1", "user": "id": 1, "name": "Username"]

Los contenidos de la .jbuilder Los archivos son código Ruby simple, por lo que puede utilizar todas las operaciones básicas como de costumbre..

Tenga en cuenta que jBuilder soporta parciales como cualquier vista de Rails ordinaria, por lo que también puede decir: 

json.partial! parcial: 'posts / post', colección: @posts, as:: post

y luego crear el vistas / api / v1 / posts / _post.json.jbuilder Archivo con los siguientes contenidos:

json.id post.id json.title post.title json.body post.body json.user do json.id post.user.id json.name post.user.name fin

Entonces, como ves, jBuilder es fácil y conveniente. Sin embargo, como alternativa, puede quedarse con los serializadores, por lo que vamos a analizarlos en la siguiente sección..

Uso de serializadores

La gema rails_model_serializers fue creada por un equipo que inicialmente administró rails-api. Como se indica en la documentación, rails_model_serializers trae la convención sobre la configuración a su generación JSON. Básicamente, usted define qué campos deben usarse en la serialización (es decir, generación JSON).

Aquí está nuestro primer serializador:

serializadores / post_serializer.rb

clase PostSerializer < ActiveModel::Serializer attributes :id, :title, :body end

Aquí decimos que todos estos campos deben estar presentes en el JSON resultante. Ahora metodos como to_json y as_json llamado a una publicación usará esta configuración y devolverá el contenido adecuado.

Para verlo en acción, modifique el índice acción como esta:

controladores / api / v1 / posts_controller.rb

def index @posts = Post.order ('created_at DESC') render json: @posts end

as_json será automáticamente llamado a la @posts objeto.

¿Qué pasa con los usuarios? Los serializadores le permiten indicar relaciones, al igual que los modelos. Además, los serializadores se pueden anidar:

serializadores / post_serializer.rb

clase PostSerializer < ActiveModel::Serializer attributes :id, :title, :body belongs_to :user class UserSerializer < ActiveModel::Serializer attributes :id, :name end end

Ahora, cuando serialice la publicación, contendrá automáticamente el contenido anidado. usuario clave con su id y nombre. Si más tarde creas un serializador separado para el usuario con el :carné de identidad atributo excluido:

serializadores / post_serializer.rb

clase UserSerializer < ActiveModel::Serializer attributes :name end

entonces @ usuario.as_json no devolverá la identificación del usuario Todavía, @ post.as_json devolverá tanto el nombre del usuario como la identificación, así que téngalo en cuenta.

Asegurando la API

En muchos casos, no queremos que nadie realice ninguna acción utilizando la API. Así que presentemos un simple control de seguridad y obligemos a nuestros usuarios a enviar sus tokens al crear y eliminar publicaciones..

El token tendrá una vida útil ilimitada y se creará en el registro del usuario. En primer lugar, añadir una nueva. simbólico columna a la usuarios mesa:

carriles g migración add_token_to_users token: cadena: índice

Este índice debe garantizar la exclusividad ya que no puede haber dos usuarios con el mismo token:

db / migrate / xyz_add_token_to_users.rb

add_index: users,: token, unique: true

Aplicar la migración:

rieles db: migrar

Ahora agregue el antes_save llamar de vuelta:

modelos / usuario.rb

before_create -> self.token = generate_token

los generar El método privado creará un token en un ciclo sin fin y verificará si es único o no. Tan pronto como se encuentre un token único, devuélvalo:

modelos / usuario.rb

private def genera_token loop do token = SecureRandom.hex devolver token a menos que User.exists? (token: token) end end end

Puede usar otro algoritmo para generar el token, por ejemplo, basado en el hash MD5 del nombre del usuario y algo de sal.

registro de usuario

Por supuesto, también debemos permitir que los usuarios se registren, porque de lo contrario no podrán obtener su token. No quiero introducir ninguna vista HTML en nuestra aplicación, así que en lugar de eso, agreguemos un nuevo método API:

controladores / api / v1 / users_controller.rb

def create @user = User.new (user_params) si @ user.save render status:: created else render json: @ user.errors, status:: unprocessable_entity end end private def user_params params.require (: user) .permit (: nombre) fin

Es una buena idea devolver códigos de estado HTTP significativos para que los desarrolladores entiendan exactamente lo que está sucediendo. Ahora puede proporcionar un nuevo serializador para los usuarios o seguir con un .json.jbuilder expediente. Prefiero la última variante (por eso no paso la : json opción a la hacer Método), pero usted es libre de elegir cualquiera de ellos. Tenga en cuenta, sin embargo, que el token no debe ser siempre serializado, por ejemplo, cuando devuelve una lista de todos los usuarios, debe mantenerse segura!

vistas / api / v1 / users / create.json.jbuilder

json.id @ user.id json.name @ user.name json.token @ user.token

El siguiente paso es probar si todo funciona correctamente. Usted puede usar el rizo Manda o escribe algún código Ruby. Ya que este artículo es sobre Ruby, iré con la opción de codificación.

Registro de usuario de prueba

Para realizar una solicitud HTTP, emplearemos la gema de Faraday, que proporciona una interfaz común sobre muchos adaptadores (el valor predeterminado es Net :: HTTP). Cree un archivo Ruby separado, incluya Faraday y configure el cliente:

api_client.rb

requiere cliente 'faraday' = Faraday.new (url: 'http: // localhost: 3000') do | config | config.adapter Faraday.default_adapter end response = client.post do | req | req.url '/ api / v1 / users' req.headers ['Content-Type'] = 'application / json' req.body = '"user": "name": "test user"' end

Todas estas opciones se explican por sí mismas: elegimos el adaptador predeterminado, configuramos la URL de solicitud en http: // localhost: 300 / api / v1 / users, cambiamos el tipo de contenido a aplicación / json, y proporcionar el cuerpo de nuestra solicitud..

La respuesta del servidor contendrá JSON, así que para analizarlo usaré la gema Oj:

api_client.rb

requiere 'oj' # cliente aquí ... pone Oj.load (response.body) pone response.status

Además de la respuesta analizada, también muestro el código de estado para fines de depuración.

Ahora puedes simplemente ejecutar este script:

rubi api_client.rb

y almacene el token recibido en algún lugar; lo usaremos en la siguiente sección.

Autenticación con el token

Para imponer la autenticación del token, el authenticate_or_request_with_http_token Se puede utilizar el método. Es parte del módulo ActionController :: HttpAuthentication :: Token :: ControllerMethods, así que no olvide incluirlo:

controladores / api / v1 / posts_controller.rb 

clase PostsController < ApplicationController include ActionController::HttpAuthentication::Token::ControllerMethods #… end

Añadir un nuevo antes_acción y el método correspondiente:

controladores / api / v1 / posts_controller.rb 

before_action: authenticate, only: [: create,: destroy] #… private #… def authenticate authenticate_or_request_with_http_token do | token, options | @user = User.find_by (token: token) end end end

Ahora, si el token no está establecido o si no se puede encontrar un usuario con dicho token, se devolverá un error 401, lo que detiene la ejecución de la acción..

Tenga en cuenta que la comunicación entre el cliente y el servidor debe realizarse a través de HTTPS, ya que de lo contrario los tokens se pueden falsificar fácilmente. Por supuesto, la solución provista no es ideal, y en muchos casos es preferible emplear el protocolo OAuth 2 para la autenticación. Hay al menos dos gemas que simplifican enormemente el proceso de compatibilidad con esta función: Doorkeeper y oPRO.

Creación de una publicación

Para ver nuestra autenticación en acción, agregue el crear acción al PostsController:

controladores / api / v1 / posts_controller.rb 

def create @post = @ user.posts.new (post_params) si @ post.save render json: @post, status:: creado else render json: @ post.errors, status:: unprocessable_entity end end

Aprovechamos el serializador aquí para mostrar el JSON adecuado. los @usuario ya estaba dentro de la antes_acción.

Ahora prueba todo usando este simple código:

api_client.rb

cliente = Faraday.new (url: 'http: // localhost: 3000') do | config | config.adapter Faraday.default_adapter config.token_auth ('127a74dbec6f156401b236d6cb32db0d') end response = client.post do | req | req.url '/ api / v1 / posts' req.headers ['Content-Type'] = 'application / json' req.body = '"post": "title": "Title", "body": "Texto" 'final

Reemplace el argumento pasado a la token_auth con el token recibido en el registro, y ejecute el script.

rubi api_client.rb

Eliminar una publicación

La eliminación de una publicación se realiza de la misma manera. Añade el destruir acción:

controladores / api / v1 / posts_controller.rb 

def destroy @post = @ user.posts.find_by (params [: id]) si @post @ post.destroy else render json: post: "no encontrado", estado:: not_found end end

Solo permitimos que los usuarios destruyan las publicaciones que realmente poseen. Si la publicación se elimina con éxito, se devolverá el código de estado 204 (sin contenido). Alternativamente, puede responder con la identificación de la publicación que se eliminó, ya que todavía estará disponible en la memoria.

Aquí está el código para probar esta nueva característica:

api_client.rb

response = client.delete do | req | req.url '/ api / v1 / posts / 6' req.headers ['Content-Type'] = 'application / json' end

Reemplaza la identificación de la publicación con un número que funcione para ti.

Configuración de CORS

Si desea permitir que otros servicios web accedan a su API (desde el lado del cliente), entonces CORS (Intercambio de recursos entre orígenes) debe configurarse correctamente. Básicamente, CORS permite que las aplicaciones web envíen solicitudes AJAX a los servicios de terceros. Por suerte, hay una gema llamada rack-cors que nos permite configurar todo fácilmente. Añadirlo a la Gemfile:

Gemfile

gema 'rack-cors'

Instalarlo:

instalación de paquete

Y luego proporcionar la configuración dentro de la config / initializers / cors.rb expediente. En realidad, este archivo ya está creado para ti y contiene un ejemplo de uso. También puede encontrar documentación bastante detallada en la página de la gema..

La siguiente configuración, por ejemplo, permitirá a cualquier persona acceder a su API utilizando cualquier método:

config / initializers / cors.rb

Rails.application.config.middleware.insert_before 0, Rack :: Cors permite hacer orígenes '*' recurso '/ api / *', encabezados:: cualquiera, métodos: [: get,: post,: put,: patch, : delete,: options,: head] end end

Prevenir el abuso

Lo último que voy a mencionar en esta guía es cómo proteger su API de los ataques de abuso y denegación de servicio. Hay una bonita gema llamada rack-attack (creada por personas de Kickstarter) que le permite a la lista negra o lista de clientes, evitar la inundación de un servidor con solicitudes y más.

Soltar la gema en Gemfile:

Gemfile

gema 'rack-attack'

Instalarlo:

instalación de paquete

Y luego proporcionar la configuración dentro de la rack_attack.rb archivo inicializador. La documentación de la gema enumera todas las opciones disponibles y sugiere algunos casos de uso. Aquí está la configuración de muestra que restringe a cualquier persona, excepto a usted, que no pueda acceder al servicio y limita el número máximo de solicitudes a 5 por segundo:

config / initializers / rack_attack.rb

class Rack :: Attack safelist ('allow from localhost') do | req | # Se permiten solicitudes si el valor de retorno es verdadero '127.0.0.1' == req.ip || ':: 1' == req.ip end throttle ('req / ip',: limit => 5,: period => 1.second) do | req | req.ip end end

Otra cosa que hay que hacer es incluir RackAttack como un middleware:

config / application.rb

config.middleware.use Rack :: Attack

Conclusión

Hemos llegado al final de este artículo. Con suerte, ¡ya te sientes más confiado en la creación de API con Rails! Tenga en cuenta que esta no es la única opción disponible, otra solución popular que existió durante bastante tiempo es el marco de Grape, por lo que puede estar interesado en comprobarlo también..

No dude en publicar sus preguntas si algo le parece poco claro. Te agradezco por estar conmigo, y feliz codificación!