Ruby, Sinatra, JSON y MySQL

   Hace algún tiempo que vengo utilizando Ruby on Rails del lado del servidor, sin embargo en lo que APIs se refiere es muy engorroso, sobre todo si lo que se quiere es algo de naturaleza sencilla como tomar unos valores, procesarlos y retornar el cálculo de los datos, es allí cuando nos topamos con Sinatra...

   Utilizar este Framework es tan fácil como invocarlo y comenzar a desarrollar nuestras funciones, para tenerlo en nuestro entorno seguimos las instrucciones via "gem" que están en el site, para comenzar podemos hacer algo como esto:
require 'sinatra'

get '/' do
 "Esta es la raíz!"
end
   Pero esto no es lo que buscamos, lo que queremos es conectarnos a nuestra BD para procesar datos, para simplificarnos las cosas y que nuestro archivo quede mas "legible" (por decirlo así) vamos a crear una clase donde estarán los métodos a utilizar dentro del API, al mismo tiempo que definiremos un ActiveRecord para nuestras consultas SQL, de modo que nuestro archivo vendría quedando algo así:
require 'mysql2'
require 'sinatra'
require 'sinatra/respond_to'
require 'active_record'
require 'json'

Sinatra::Application.register Sinatra::RespondTo

get '/:id' do |id|
 format_response MyAPI.getUser(id)
end

class MyAPI < ActiveRecord::Base
 class << self
  ActiveRecord::Base.establish_connection(
   :adapter  => "mysql2",
   :host     => "servidor",
   :port     => "3306",
   :pool     => 16,
   :username => "usuario",
   :password => "123456",
   :database => "mibd"
  )
  
  def getUser(UserId)  
   query = "SELECT nombre FROM usuarios WHERE id = #{UserId}"
   data = self.connection.select_all(query)
   data[0]["nombre"]
  end  
 end
end

   Una vez definido todo comenzamos a crear nuestras consultas, para nuestra tarea el API de Sinatra nos facilita las cosas ya que cada ruta es un método HTTP asociado a nuestra URL (mas info en w3.org), vamos por ejemplo a definir 4 métodos simples donde creamos, editamos, modificamos y eliminamos un usuario en específico:
require 'mysql2'
require 'sinatra'
require 'sinatra/respond_to'
require 'active_record'
require 'json'

Sinatra::Application.register Sinatra::RespondTo

# Retorna el nombre de usuario, http://127.0.0.1/user/24
get '/user/:id' do |id|
 format_response MyAPI.getUser(id)
end

# Agrega un nuevo usuario, http://127.0.0.1/user/carlos
post '/user/:name' do |name|
 format_response MyAPI.createUser(name)
end

# Modifica el nombre de usuario, http://127.0.0.1/user/id=24&name=carlitox
put '/user' do
 format_response MyAPI.updateUser(params[:id], params[:name])
end

# Elimina el usuario, http://127.0.0.1/user/24
delete '/user/:id' do |id|
 format_response MyAPI.deleteUser(id)
end

class MyAPI < ActiveRecord::Base
 class << self
  ActiveRecord::Base.establish_connection(
   :adapter  => "mysql2",
   :host     => "servidor",
   :port     => "3306",
   :pool     => 16,
   :username => "usuario",
   :password => "123456",
   :database => "mibd"
  )
  
  # GET
  def getUser(UserId)
   query = "SELECT nombre FROM usuarios WHERE id = #{UserId}"
   data = self.connection.select_all(query)
   data[0]["nombre"]
  end 
  
  # POST
  def createUser(UserName)
   query = "INSERT INTO usuarios(nombre) VALUES ('#{UserName}')"
   self.connection.execute(query)
   "done"
  end 
  
  # PUT
  def updateUser(UserId, UserName)
   query = "UPDATE usuarios SET nombre = '#{userName}' WHERE id = #{UserId}"
   self.connection.execute(query)
   "done"
  end 
  
  # DELETE
  def deleteUser(UserId)
   query = "DELETE FROM usuarios WHERE id = #{UserId}"
   self.connection.execute(query)
   "done"
  end  
 end
end

   La razón de que utilicemos la librería JSON es para que cuando hagamos nuestra llamada solicitemos el archivo en este formato (format_response), de esta forma es más fácil leer la data devuelta. No olvidemos definir dicha función:
def format_response (package)
 respond_to do |request|
  request.json { package.to_json }
 end
end

   Todo bien, pero ¿que tal si vamos a enviar mas de 1 parámetro a la vez?, como vimos en el ejemplo anterior para el método PUT, pero podemos extendernos un poco más y trabajarlos dentro de la misma clase:
require 'mysql2'
require 'sinatra'
require 'sinatra/respond_to'
require 'active_record'
require 'json'

Sinatra::Application.register Sinatra::RespondTo

# http://127.0.0.1/user/name=carlos&nick=carlitoxenlaweb
post '/user' do 
 format_response MyAPI.createUser(params)
end

class MyAPI < ActiveRecord::Base
 class << self
  ActiveRecord::Base.establish_connection(
   :adapter  => "mysql2",
   :host     => "servidor",
   :port     => "3306",
   :pool     => 16,
   :username => "usuario",
   :password => "123456",
   :database => "mibd"
  )
  
  def createUser(params)
   query = "INSERT INTO usuarios(name, nick) VALUES ('#{params[:name]}', '#{params[:nick]}')"
   self.connection.execute(query)
   "done"
  end 
 end
end

   Esto es lo básico, esta entrada puede hacerse tan extensa como repetir la documentación (que bastante hay sobre el tema), visita la página para ver todas las posibilidades: Sinatra Intro!

   Por último algunos tips útiles (todo esto fuera de la clase que definimos):

   No olvidemos cerrar siempre nuestra conexión con el servidor de base de datos:
after do
 ActiveRecord::Base.connection.close
end

   Podemos definir mas formatos de salida como xml, csv o html plano (format_response), así como los métodos que utilizara el servidor y el origen de acceso:
def format_response (package)
 response.headers['Access-Control-Allow-Origin'] = "http://carlitoxenlaweb.blogspot.com/"
 response.headers['Access-Control-Allow-Methods'] = "POST, GET, PUT, DELETE"
 respond_to do |request|
  request.txt { package.to_s }
  request.csv { package.to_csv }
  request.xml { package.to_xml }
  request.json { package.to_json }
  request.html { package.to_json }
 end
end

   Definamos siempre la IP (interfaz) y puerto que escuchará nuestro servidor
#require.....
set :bind => '192.168.1.100', :port => '6543'



2 Comentarios

Escribir Comentario
aloon
AUTOR
21 de marzo de 2016, 13:22 delete

Las consultas pueden sufrir ataques de sql injection, no?

Responder
avatar
19 de abril de 2016, 12:43 delete

Si, de esta forma pueden sufrir esos fallos de seguridad, para evitarlo se puede crear una función que formatee estos valores y construya el SQL...

Responder
avatar

Lamentablemente hay muchos usuarios en la red que han llegado al blog para escribir obscenidades, así que la moderación se hace necesaria. Recuerda utilizar un lenguaje correcto y espera a que sea aprobado.

Si necesitas publicar código haz click en "Conversión" para hacerlo legible.
ConversiónConversión