Rails 5 JSON API con RethinkDB

Con Rails 5 fuera y el nuevo modo API incluido en el framework, veamos que se necesita para configurar una nueva aplicación para que funja como una JSON API.

En esta guía se explica cómo crear y configurar una aplicación Rails con RethinkDB, de tal manera que funcione como una JSON API (puro back-end).

Una aplicación de este tipo se da muy bien para después emplearla con múltiples clientes, ya sean SPAs (Single Page Applications) hechas en Angular o aplicaciones móviles construidas con Ionic 2, wink-wink.

Se asume familiaridad con Rails, git y los códigos de estado HTTP.

Preliminares

Commit inicial

Para generar el proyecto y hacer el primer commit:

rails new json_api --api --skip-active-record
cd json_api
git init
git add .
git ci -m "Commit inicial."

La opción de –skip-active-record desactiva ActiveRecord porque usaremos RethinkDB con NoBrainer en lugar de una base de datos relacional como PostgreSQL.

Configuración inicial

Agrega estas gemas a tu Gemfile:

group :development, :test do
  #...
  # Framework para pruebas
  gem 'minitest-rails'
  # Para generar objetos de prueba
  gem 'fabrication'
  # Para generar datos aleatorios
  gem 'faker'
end
 
#...
# Para usar RethinkDB a través de NoBrainer
gem 'nobrainer'
# Para construir JSON APIs con facilidad
gem 'jbuilder', '~> 2.5'
# Para usar ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

Después en la terminal:

bundle
rails g minitest:install
rails g nobrainer:install

Con esto se instalan las gemas que hagan falta, después se generan los archivos de configuración para Minitest y NoBrainer.
Al ejecutar el generador de Minitest, te va a preguntar si debe sobreescribir el archivo test_helper.rb, contesta sí.

Agrega este contenido al archivo config/application.rb:

config.generators do |g|
  # Framework de pruebas estilo BDD
  g.test_framework :minitest, spec: true, fixture_replacement: :fabrication
  g.fixture_replacement :fabrication, dir: 'test/fabricators'
end

En el archivo test/test_helper.rb, comenta la siguiente línea:

# fixtures :all

No tenemos disponible ActiveRecord, si no se comentan esas líneas al tratar de ejecutar las pruebas se lanzará una excepción.

En el archivo config/initializers/inflections.rb agrega:

ActiveSupport::Inflector.inflections( :en ) do |inflect|
  inflect.acronym 'API'
end

Esto permite que al generar los controladores se utilice API como nombre del módulo contenedor en lugar de Api.

Con la configuración inicial terminada, agrega los cambios al repo:

git add .
git ci -m "Configuración inicial."

Testing

TDD vs BDD

La diferencia entre los dos es el cómo te gusta pensar cuando modelas tu aplicación.

El TDD utiliza el es (assert), el BDD usa el debe ser (must). Básicamente es una cuestión de ser vs debe ser al modelar.

Behavior-driven development

Si lo tuyo es el TDD, pasa a la siguiente sección.

Genera tu primer controlador

rails g controller API::Users

Ejecuta y observa:

rake test

Edita test/controllers/api/users_controller_test.rb y cambia el test de ejemplo por:

it "#index debe ser exitoso" do
  get api_users_url
  response.status.must_equal 200
end

Ejecuta y observa:

rake test

Edita config/routes.rb para agregar el recurso:

namespace :api do
  resources :users
end

Ejecuta y observa:

rake test

Edita app/controllers/api/users_controller.rb y agrega:

def index
  render json: {}
end

Ejecuta y observa:

rake test

😀

Esta fue una muestra de un proceso muy básico de BDD para un controlador.

Genera tu primer modelo

rails g model User name:string email:string password_digest:string

Ejecuta y observa:

rake test

Abre el archivo test/models/user_test.rb y verás:

require "test_helper"
 
describe User do
  let(:user) { User.new }
 
  it "must be valid" do
    value(user).must_be :valid?
  end
end

Si analizamos un poco la situación y contemplamos nuestro modelo User, veremos que el usuario forzosamente necesita al menos un correo. Así, un usuario recién creado debería ser inválido.

Cambia el contenido de ese archivo por este:

require 'test_helper'
 
describe User do
  let(:user) { User.new }
 
  it 'no debe ser válido' do
    value(user).wont_be :valid?
  end
end

Ejecuta y observa:

rake test

Abre el archivo app/models/user.rb y agrega:

validates :email, presence: true

Ejecuta y observa:

rake test

😀

Esto fue una muestra de un proceso muy básico de aplicación de BDD para crear un modelo.

A continuación veremos cómo se haría con TDD.

Test-driven development

Para que los archivos de las pruebas se generen al estilo TDD cambia el valor de spec a false en el archivo config/application.rb:

config.generators do |g|
  # Framework de pruebas estilo TDD
  g.test_framework :minitest, spec: false, fixture_replacement: :fabrication
  g.fixture_replacement :fabrication, dir: 'test/fabricators'
end

Genera tu primer controlador

rails g controller API::Users

Ejecuta y observa:

rake test

Edita test/controllers/api/users_controller_test.rb y cambia el test de ejemplo por:

def test_index_action
  get api_users_url
  assert_response :success
end

Nota: El nombre de los métodos debe comenzar necesariamente con test_ para que sean ejecutados durante el proceso de pruebas.

El método anterior es equivalente a este:

test 'index action' do
  get api_users_url
  assert_response :success
end

Ejecuta y observa:

rake test

Edita config/routes.rb para agregar el recurso:

namespace :api do
  resources :users
end

Ejecuta y observa:

rake test

Edita app/controllers/api/users_controller.rb y agrega:

def index
  render json: {}
end

Ejecuta y observa:

rake test

😀

Esta fue una muestra de un proceso muy básico de TDD para un controlador.

Genera tu primer modelo

rails g model User name:string email:string password_digest:string

Ejecuta y observa:

rake test

Abre el archivo test/models/user_test.rb y verás:

require "test_helper"
 
class UserTest < ActiveSupport::TestCase
  def user
    @user ||= User.new
  end
 
  def test_valid
    assert user.valid?
  end
end

Si analizamos un poco la situación y contemplamos nuestro modelo User, veremos que el usuario forzosamente necesita al menos un correo. Así, un usuario recién creado debería ser inválido.

Cambia el único test que existe en ese archivo por este otro:

def test_invalid
  assert_not user.valid?
end

Ejecuta y observa:

rake test

Abre el archivo app/models/user.rb y agrega:

validates :email, presence: true

Ejecuta y observa:

rake test

😀

Y esta fue una muestra de un proceso muy básico de TDD para un modelo.

TDD o BDD es una cuestión de gustos. En lo personal prefiero el BDD.

Espero esta guía te sea útil.

Repositorio en github

lobo-tuerto/rails_rethinkdb

Mejorando el $log de AngularJS con decorators

La idea de este artículo es contar con un servicio de logging que nos permita contextualizar fácilmente la información que aparece en la consola del navegador.

A continuación pongo unos ejemplos para ilustrar su uso:

Uso básico:

$log.debug( 'línea debug' );
$log.info( 'línea info' );

Salida:

[0] -- 12/3/2016 15:43:38 línea debug
[1] -- 12/3/2016 15:43:38 línea info

Uso con contextos:

var logger = $log.getLogger( 'MainController' );
logger.debug( 'línea 1' );
logger.debug( 'línea 2' );
logger.debug( 'línea 3' );
 
var logger2 = $log.getLogger( 'App' );
logger2.debug( 'otra línea' );
logger2.debug( 'una línea más' );

Salida:

[0] -- 12/3/2016 15:58:05 -- [MainController]:0>  línea 1
[1] -- 12/3/2016 15:58:05 -- [MainController]:1>  línea 2
[2] -- 12/3/2016 15:58:05 -- [MainController]:2>  línea 3
[3] -- 12/3/2016 15:58:05 -- [App]:0>  otra línea
[4] -- 12/3/2016 15:58:05 -- [App]:1>  una línea más

Con desactivación de contextos:

var logger = $log.getLogger( 'MainController' );
logger.enable( false );
logger.debug( 'línea 1' );
logger.debug( 'línea 2' );
logger.enable( true );
logger.debug( 'línea 3' );
 
var logger2 = $log.getLogger( 'App' );
logger2.debug( 'otra línea' );
logger2.debug( 'una línea más' );

Salida:

[0] -- 12/3/2016 16:02:00 -- [MainController]:0>  línea 3
[1] -- 12/3/2016 16:02:00 -- [App]:0>  otra línea
[2] -- 12/3/2016 16:02:00 -- [App]:1>  una línea más

Los servicios en AngularJS se pueden mejorar de dos maneras: Usando decorators o providers.

La ventaja de usar un decorator es que sólo necesitas declararlo dentro del módulo de tu aplicación o incluir el módulo dónde está declarado como dependencia, para que entonces se decore el servicio deseado.

El código presentado a continuación utiliza un decorator:

( function() {
  'use strict';
 
  // Asume que el módulo de tu aplicación es llamado "myApp"
  // En este módulo se define el decorador
  angular.module( 'myApp' ).decorator( '$log', logDecorator );
 
  // La función "logDecorator" recibirá el servicio a decorar como
  // el parámetro $delegate, en este caso $delegate == $log
  function logDecorator( $delegate ) {
 
    // Contador general
    var n = 0;
 
    // El nuevo logger que imprime información básica
    var basicLogger = {
      log: basicDecoration( $delegate.log ),
      debug: basicDecoration( $delegate.debug ),
      info: basicDecoration( $delegate.info ),
      warn:basicDecoration( $delegate.warn ),
      error: basicDecoration( $delegate.error ),
      getLogger: getLogger,
      enabledContexts: {},
      counter: {}
    };
 
    // Esta función regresa el servicio de log básico mejorado
    return basicLogger;
 
 
    // La decoración básica incluye un contador, fecha y hora al inicio
    function basicDecoration( loggingFunction ) {
      return function() {
        var date = new Date();
        var args = Array.prototype.slice.call( arguments );
        args.unshift( '[' + n++ + '] -- ' + date.toLocaleString() );
        loggingFunction.apply( null, args );
      }
    }
 
    // La decoración contextual incluye info del contexto y un contador
    function contextDecoration( loggingFunction, context ) {
      return function() {
        var enabled = basicLogger.enabledContexts[context];
        if( enabled || enabled === undefined ) {
          var args = Array.prototype.slice.call( arguments );
          var contextInfo = '-- [' + context + ':';
          var counterInfo = basicLogger.counter[context]++ + '] >> ';
          args.unshift( contextInfo + counterInfo );
          loggingFunction.apply( null, args );
        }
      }
    }
 
    // Activa o desactiva el logging en un contexto determinado
    function enable( context ) {
      return function( enable ) {
        basicLogger.enabledContexts[context] = enable;
      }
    }
 
    // Regresa un logger para un contexto determinado
    function getLogger( context ) {
 
      // Contador contextual
      basicLogger.counter[context] = 0;
 
      // La nueva instancia de logger con información del contexto
      var contextLogger = {
        log: contextDecoration( basicLogger.log, context ),
        debug: contextDecoration( basicLogger.debug, context ),
        info: contextDecoration( basicLogger.info, context ),
        warn: contextDecoration( basicLogger.warn, context ),
        error: contextDecoration( basicLogger.error, context ),
        enable: enable( context )
      };
 
      // Esta función regresa el servicio de log contextualizado
      return contextLogger;
    }
  }
} )();

Una recomendación es que siempre utilices el servicio de AngularJS $log en vez del console.log, de esta manera siempre podrás desactivar todo el logging desde el bloque de configuración de tu aplicación con algo como esto:

angular.module( 'myApp', [] ).config( config );
 
function config( $logProvider ) {
  $logProvider.debugEnabled( false );
}

Con este nuevo logger también puedes mandar a consola toda actividad en tus controladores, directivas, servicios, etc., y si la información que aparece es demasiada, puedes desactivar los contextos que gustes desde el bloque de ejecución de tu aplicación.

Digamos que tienes un controlador llamado MainController, y en él inicializas un logger con contexto “MainController”. Para desactivar toda la salida a consola generada por ese controlador, puedes hacer algo así:

angular.module( 'myApp', [] ).config( config ).run( run );
 
function config( $logProvider ) {
  $logProvider.debugEnabled( true );
}
 
function run( $log ) {
  var logger = $log.getLogger( 'MainController' );
  logger.enable( false );
}

La idea es que en cada componente de tu aplicación tengas un logger y el contexto sea el nombre del componente.

Referencias

Enhancing AngularJS Logging using Decorators
Enhancing $log in AngularJs the simple way

Ubuntu en una MacBook Pro

Si instalaste Ubuntu en una MacBook Pro, seguramente hay ciertos detalles que necesitas revisar justo después de la instalación:

Wireless

Si no funciona la red inalámbrica. Conecta a un cable de red e instala los siguientes paquetes:

sudo apt-get install firmware-b43-installer b43-fwcutter

Reinicia Ubuntu para conectarte inalámbricamente.

Teclado

Para corregir el esquema del teclado:

Desde la configuración de las Preferencias escoger sólo Español (sin ninguna variante).

Y en el archivo:

sudo nano /etc/default/keyboard

Escribir o actualizar las siguientes variables:

XKBMODEL="macintosh"
XKBLAYOUT="es"
XKBVARIANT="mx"
XKBOPTIONS=""

Esto lo pondrá como Español de México, si lo quieres cómo Español de España, deja vacía la variable XKBVARIANT.

Cerrar tu sesión y volver a iniciarla para ver los cambios.

Cmd vs Ctrl

Si ya te acostumbraste a usar Cmd, seguro se te hará raro ahora usar Ctrl. Si deseas que esto funcione como en Mac, puedes intercambiarlas. Para intercambiar la tecla Cmd con Ctrl abre el archivo:

nano ~/.Xmodmap

Y pega lo siguiente:

clear control
clear mod4
 
keycode 105 =
keycode 206 =
 
keycode 133 = Control_L NoSymbol Control_L
keycode 134 = Control_R NoSymbol Control_R
keycode 37 = Super_L NoSymbol Super_L
 
add control = Control_L
add control = Control_R
add mod4 = Super_L

Puedes probar este cambio ejecutando el siguiente comando:

xmodmap ~/.Xmodmap

Para que se ejecute cada que reinicies, agrega el mismo comando a las Aplicaciones de Inicio.

Touchpad

Para hacer más similar el touchpad. Esto debido a la forma de lanzar el click Derecho en Ubuntu es con dos dedos sobre el TrackPad, puede ser confuso al principio. Además, el driver que trae por defecto, no ignora la Palma ni el Pulgar, y la gran mayoría de las personas de repente recargamos una o otra lo que ocasionará que el cursor brinque sin querer. Queda mucho mejor con otro driver especializado en estos problemas. Para instalarlo:

Agregar el repositorio:

sudo add-apt-repository ppa:mactel-support && sudo apt-get update

Instalar el paquete:

sudo apt-get install xserver-xorg-input-mtrack

Crear el archivo:

sudo nano /etc/X11/xorg.conf

Con el siguiente contenido:

Section "InputClass"
    MatchIsTouchpad "on"
    Identifier      "Touchpads"
    Driver          "mtrack"
    Option          "Sensitivity" "0.85"
    Option          "FingerHigh" "12"
    Option          "FingerLow" "1"
    Option          "IgnoreThumb" "true"
    Option          "IgnorePalm" "true"
    Option          "TapButton1" "0"
    Option          "TapButton2" "0"
    Option          "TapButton3" "0"
    Option          "TapButton4" "0"
    Option          "ClickFinger1" "1"
    Option          "ClickFinger2" "3"
    Option          "ClickFinger3" "3"
    Option          "ButtonMoveEmulate" "false"
    Option          "ButtonIntegrated" "true"
    Option          "ClickTime" "25"
    Option          "TapButton2" "0"
    Option          "TapButton3" "0"
    Option          "TapButton4" "0"
    Option          "ClickFinger1" "1"
    Option          "ClickFinger2" "3"
    Option          "ClickFinger3" "3"
    Option          "ButtonMoveEmulate" "false"
    Option          "ButtonIntegrated" "true"
    Option          "ClickTime" "25"
    Option          "BottomEdge" "25"
    Option          "SwipeLeftButton" "8"
    Option          "SwipeRightButton" "9"
    Option          "SwipeUpButton" "0"
    Option          "SwipeDownButton" "0"
    Option          "ScrollDistance" "75"
    Option          "VertScrollDelta"          "-111"
    Option          "HorizScrollDelta"         "-111"
EndSection

Esto pondrá el click derecho como normalmente es en Mac (dejando dos dedos encima del Touchpad y haciendo tap con otro) además de poner el scroll natural (invertido) como si lo hicieras desde un dispositivo touch.

Si no quieres el scroll natural, elimina las variables “VertScrollDelta” y “HorizScrollDelta”.

Si sientes el desplazamiento muy rápido o lento, cambia el valor de la variable “Sensitivity” (0.55 por ejemplo lo hará más lento).

Reinicia Ubuntu.

Leer particion de MacOS en Ubuntu

Si tienes una partición, e intentas abrirla, te saldra un error. Esto es porque para leer particiones de Mac OS se necesita un paquete extra, instalalo:

sudo apt-get install hfsprogs

Si te salen errores de que la unidad está dañada, puedes repararla con el siguiente comando (reemplazas “sda2” con el número que te salga en el cuadro de diálogo):

sudo fsck.hfsplus -f /dev/sda2

xTerm

Ciertas aplicaciones como xTerm no usan el Portapapeles normal (Ctrl+c Ctrl+v) sino algo que le llaman Primario.

En dichas aplicaciones se copia al Primario con Ctrl+Insert y se pega con Shift+Insert, debido a que Ctrl+c normalmente esta reservado para terminar.

Debido a que en la Laptop no tenemos la techa de Insert, vamos a tener que mapear a otras combinaciones.

Copiar

Para XTerm en particular, puedes hacer que al seleccionar un texto, lo copie tanto al Primario como al Clipboard.

Intenta abrir una terminal con el siguiente comando:

xterm -ls -xrm 'XTerm*selectToClipboard: true'&

Si seleccionas un texto y después te vas a Chrome e intentas pegarlo ya funcionará.

Para guardar esa configuración, edita el archivo ~/.Xdefaults con lo siguiente:

XTerm*selectToClipboard: true

Pegar

En Ubuntu existe un mapeo para pegar del Primario, que es con el Middle Click del Mouse, esto es darle click a la ruedita del Scroll. Como tampoco tenemos rueda de Scroll, aprovecharemos para usar una tecla que casi no se use.

Primero instala el siguiente programa:

sudo apt-get install xkbset

Ahora ejecuta o siguiente en una terminal:

xmodmap -e "keycode 134 = Pointer_Button2"; xkbset m

Esto mapeará la Tecla Comando de la derecha a Middle Click. Entonces si quieres pegar algo del Primario, solo presiona Comando derecha.

Restaurar respaldo con postgresql para sitio en Ruby on Rails

1.- Te conectas con ssh al servidor

2.- Desde cualquier ubicación entra a la consola de postgres:

sudo su postgres -c psql

3.- Darle todos los privilegios al usuario sobre la base de datos escribiendo:

GRANT ALL PRIVILEGES ON DATABASE nombre_base_datos TO usuario_base_datos

Recuerda que esta información debe estar dentro de tu archivo database.yml

4.- Salir de la consola de postgres con \q

5.- Entrar al directorio donde está la aplicación

en mi caso home/deployer/apps/mi_aplicacion

6.- Ejecutar el siguiente comando para tirar la base de datos:

rake db:drop RAILS_ENV=production

7.- Ejecutar el siguiente comando para crear la base de datos:

rake db:create RAILS_ENV=production

8.- Ejecutar el siguiente comando para restaurar la base de datos:

 psql -U usuario_base_datos -d nombre_base_datos -f nombre_respaldo
-U => el usuario de postgresql:       el usuario de la base de datos se encuentra dentro del archivo .yml en la ruta mi_aplicacion/config/database.yml
-d => el nombre de la base de datos:  el nombre de la base de datos se encuentra dentro del archivo .yml en la ruta mi_aplicacion/config/database.yml
-f => el nombre del respaldo:         hay que escribir la ruta en donde se encuentra el dump

Súper fácil!!!

Si te llega a salir un error como:

psql: FATAL: Peer authentication failed for user "tu_usuario"

Es porque tu Postgres está configurado para solo dar permiso a usuarios reales del sistema (que puedan hacer login). Eso lo podemos cambiar modificando el archivo (con permisos de administrador):

/etc/postgresql/9.3/main/pg_hba.conf

El 9.3 puede variar, depende de tu versión de Postgres.

Y cambias la siguiente linea:

De:

# TYPE DATABASE USER ADDRESS METHOD
local  all      all          peer

A:

# TYPE DATABASE USER ADDRESS METHOD
local  all      all          md5

Después de realizar ese cambio, reiniciamos Postgres:

sudo service postgresql restart

Y con eso ya te debe dar acceso.

Crear Respaldos con postgresql

1.- Te conectas con ssh al servidor

2.- Desde cualquier ubicación ejecuta los siguientes comandos:

sudo su
su postgres
cd ~

3.- Posteriormente vamos a generar el dump:

pg_dump -Upostgres nombre_base_en_producción > nombre_respaldo.sql

4.- Ten a la mano el password del usuario postgres ya que te lo pedirá

5.- Para saber en que ruta te encuentras para posteriormente mover el dump, escribe el siguiente comando:

pwd  
en mi caso dio lo siguiente:  /var/lib/postgresql

6.- Moverte a root con exit

7.- Para mover el dump a tu aplicación escribir el siguiente comando:

mv /var/lib/postgresql/nombre_respaldo.sql aplicacion/public/.

Nota: Si el respaldo pesa mucho, puedes comprimirlo de la siguiente manera:

zip nombre_respaldo.zip nombre_respaldo.sql

Súper fácil!!!

Cómo instalar Oracle Java 8 en Ubuntu 16.04

Para instalar la versión 8 teclea en la terminal:

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer

Para instalar la versión 7 teclea en la terminal:

sudo apt-get install oracle-java7-installer

Verifica tu instalación con este comando:

java -version

O con:

javac -version

Cambia entre Oracle Java 8 y Oracle Java 7

Cambia a Java 7 (desde Java 8):

sudo update-java-alternatives -s java-7-oracle

Cambia a Java 8 (desde Java 7):

sudo update-java-alternatives -s java-8-oracle

Si te interesa instalar la versión open source, sigue esta guía.

Referencias

Install Oracle Java 8 in Ubuntu via PPA repository [JDK8]
How to Install Oracle Java 7/8 (JDK and JRE) In Ubuntu

Rails 4: where (first) vs find_by (take)

Normalmente para encontrar un registro, que sabía de antemano era único, usaba algo como:

Usuario.where( email: "hola@correo.com" ).first

Sin embargo, me acabo de enterar que en Rails 4 agregaron un método que lo abrevia:

Usuario.find_by( email: "hola@correo.com" )

Y cuando el elemento a encontrar es forzoso, podemos agregar un “!” para que mande una excepción de ActiveRecord::RecordNotFound si no lo encuentra:

Usuario.find_by!( email: "hola@correo.com" )

Y es que no solo se escribe un poco menos, sino que es más eficiente, ya que viendo la definición de find_by:

# File activerecord/lib/active_record/relation/finder_methods.rb, line 80
def find_by(*args)
  where(*args).take
end

Nos damos cuenta que usa take en lugar de first y la diferencia entre ellos es que first hace un ORDER extra:

First:

SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."email" = 'hola@correo.com' ORDER BY "usuarios"."id" ASC LIMIT 1

Take:

SELECT "usuarios".* FROM "usuarios" WHERE "usuarios"."email" = 'hola@correo.com' LIMIT 1

Por lo que si lo que necesitamos es obtener un registro único, es mejor utilizar find_by. 🙂