Cómo arreglar el doble ícono de Google Chrome en elementary OS

Al abrir Google Chrome aparece dos veces el ícono en el dock.

Para corregirlo, abre una terminal y teclea esto:

sudo nano /usr/share/applications/google-chrome.desktop

Inserta StartupWMClass=Google-chrome-stable bajo cada una de las tres secciones siguientes:

[Desktop Entry]
StartupWMClass=Google-chrome-stable
...
 
[NewWindow Shortcut Group]
StartupWMClass=Google-chrome-stable
...
 
[NewIncognito Shortcut Group]
StartupWMClass=Google-chrome-stable
...
goedkope energie

Configuración de una aplicación Rails 4.2 con MongoDB para una JSON API

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

Una aplicación de este tipo se da muy bien para después conectarla con un cliente SPA (Single Page Application), digamos en AngularJS o para emplearla con una aplicación móvil hecha con Ionic, por ejemplo.

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 --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 MongoDB con Mongoid en lugar de una base de datos relacional como PostgreSQL o MySQL.

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 de prueba
  gem 'faker'
end
 
# Para usar MongoDB a través de Mongoid
gem 'mongoid'
# Para usar respond_with en los controladores
gem 'responders'
# Para usar ActiveModel#has_secure_password
gem 'bcrypt'

Después en la terminal:

bundle
rails g mongoid:config
rails g minitest:install

Con esto se instalan las gemas que hagan falta, después se generan los archivos de configuración para Mongoid y Minitest. No es necesario correr el generador para los responders.

Al ejecutar el generador de Minitest, te va a preguntar si debe sobreescribir el archivo test_helper.rb, contesta sí.

En el archivo config/mongoid.yml, quita el comentario de la línea que aparece a continuación y cambia el valor a false:

# raise_not_found_error: true
raise_not_found_error: false

No queremos que Mongoid levante una excepción toda vez que no encuentre un documento.

Es deseable tener más control sobre cuándo se lanza una excepción, de esta forma, cuando se busca un usuario para autenticación y no existe, en vez de un código de error 404 (Not Found) podemos mandar un 401 (Unauthorized).

Agrega este contenido al archivo config/application.rb:

config.generators do |g|
  # Framework de pruebas estilo BDD
  g.test_framework :minitest, spec: true, fixture: true, fixture_replacement: :fabrication
 
  # Estamos haciendo una API, no queremos assets, helpers, tampoco views
  g.assets false
  g.helper false
  g.template_engine nil
end
 
# Para manejar la presentación de errores 404 y 500 como JSON
config.exceptions_app = self.routes

En el archivo test/test_helper.rb, comenta las siguientes líneas:

# ActiveRecord::Migration.check_pending!
# 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.

Configuración de rutas y del controlador principal de la aplicación

En el archivo config/routes.rb agrega:

match '/404' => 'application#not_found', via: [:get, :post, :put, :patch, :delete]
match '/500' => 'application#exception', via: [:get, :post, :put, :patch, :delete]

Esto permite personalizar las respuestas de error cuando no se encuentra alguna ruta o cuando se tiene algún error en la aplicación.

Por defecto, en estos casos lo que se devuelve es HTML (y el código de estado HTTP adecuado), pero estamos interesados en siempre entregar JSON al cliente.

En el archivo app/controllers/application_controller.rb cambia :exception por :null_session y agrega:

protect_from_forgery with: :null_session
 
# Estos métodos se llaman desde las rutas
def not_found
  response = http_error( { status: '404', message: 'Not Found' } )
  render json: response, status: :not_found
end
 
def exception
  response = http_error( { status: '500', message: 'Internal Server Error' } )
  render json: response, status: :internal_server_error
end
 
 
private
# Método de utilería
def http_error( error )
  { error: { http: error } }
end

La línea de protect_from_forgery_with indica el tipo de protección que se utilizará como medida de prevención contra un ataque de tipo CSRF.

Cuando es :exception se lanza una excepción ante cualquier petición POST, PUT, PATCH o DELETE si no viene acompañada de un token especial que Rails normalmente inserta en las vistas. Siendo esta una aplicación que va a ser utilizada como API no cuenta con ellas.

De la documentación: ActionController::RequestForgeryProtection::ClassMethods

Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.

Con :null_session se indica que no habrá estado del lado del servidor, por tanto, no existe una sesión que mantega el estado de conectado dentro del sistema. La autenticación se logra por medio de un token especial que se enviará desde el cliente en la cabecera de la petición. Más adelante veremos cómo.

Otros errores que sería bueno atrapar

Igual, en el archivo app/controllers/application_controller.rb agrega:

# Para cuando lancemos la excepción de documento no encontrado en MongoDB
rescue_from Mongoid::Errors::DocumentNotFound, with: :document_not_found
# Para cuando el tipo de contenido solicitado no sea application/json
rescue_from ActionController::UnknownFormat, with: :not_acceptable
# Para cuando hagan falta parámetros requeridos en algún modelo
rescue_from ActionController::ParameterMissing, with: :unprocessable_entity
 
def document_not_found
  response = http_error( { status: '404', message: 'Document Not Found' } )
  render json: response, status: :not_found
end
 
def not_acceptable
  response = http_error( { status: '406', message: 'Not Acceptable' } )
  render json: response, status: :not_acceptable
end
 
def unprocessable_entity
  response = http_error( { status: '422', message: 'Unprocessable Entity' } )
  render json: response, status: :unprocessable_entity
end
 
def unauthorized
  response = http_error( { status: '401', message: 'Unauthorized' } )
  render json: response, status: :unauthorized
end

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 acerca del modelado de la aplicación.

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

Probemos primero con un poco de BDD

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 :index
  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
    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 único test que existe en ese archivo por este otro:

it 'no debe ser válido' do
  user.wont_be :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 BDD para un modelo.

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

Probemos ahora con un poco de TDD

Para que las pruebas se generen al estilo TDD cambia spec a false:

g.test_framework :minitest, spec: false, fixture: false

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 :index
  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 :index
  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.

En lo personal prefiero el BDD, siento que en ese estilo se expresa mejor la intención de lo que quieres programar.

Finalmente

Más adelante publicaré una guía acerca de cómo hacer pruebas de integración y cómo utilizar fabricators de manera efectiva en las pruebas de modelos y controladores.

Espero esta guía te sea útil.

Repositorio en github

lobo-tuerto/rails_json_api