Está en la página 1de 428

Introduccin a

Ruby on Rails
desde cero

www.desafiolatam.com

Versin 0.712

2) Motivacin
Qu es Ruby on Rails?
Por qu utilizar Ruby on Rails para mi aplicacin?
Desventajas de utilizar Ruby on Rails
Cmo est estructurado este libro?
Seccin I: Front-end con Rails
3) Instalando las herramientas
OSX
Linux
En Linux y OSX
4) Estructura bsica de un proyecto de Ruby on Rails
5) Creando nuestra primera pgina web con Rails
Consola secuestrada.
Creando nuestra primera pgina
Creando nuestra segunda pgina
6) Agregando imgenes a nuestra primera pgina
Los Assets
El asset_path
7) Nuestra primera pgina con layout
El Layout
Incorporando CSS
Incorporando Javascript
Incorporando imgenes (u otros assets) dentro del CSS y del JS
Consecuencias de Sprockets
Incorporando Bootstrap.
Vistas parciales

Cargando un layout distinto


Desafo
8) Creando un formulario de registro
El tag form
Guardando los datos
Guardando los usuarios dentro de Rails
Cambiando la pgina de inicio
Desafo
9) Formulario 2.0
Creando un proyecto nuevo
Enviando un formulario por POST
Mostrando todos los usuarios en la base de datos
Desafo
10) El Gemle
Seccin II: Deployment
11) Deployment en Heroku
Tipos de hosting
Instalando el Toolbelt de Heroku
Claves SSH
Repositiorio Git
Subiendo la aplicacin a Heroku
Desafo
12) Congurando el dominio (o subdominio)
Congurando dominios cuyas autoridades no soportan redireccionamiento tipo
CName
Tips de mantencin
Desafo
Seccin III: SQL y modelado de bases de datos

13) SQL
Qu es SQL?
PostgreSQL
Instalando PostgreSQL en OSX
Instalando PostgreSQL en Linux
Entrando a PSQL
Problemas tpicos de Conguracin de PostgreSQL
El dilema del usuario y de la base de datos
Nuestros Primeros pasos en SQL
Manipulando valores de una tabla
Modicando una tabla
Constraints
Ventajas de la clave primaria
Tablas con valores autoincrementales
Ordenando los resultados.
Distinct y count
Agrupando datos
Gua de ejercicios
14) SQL con ms de una tabla
Integridad referencial
Joins
Explain
Tipos de relaciones
La clave fornea
Ejercicios
15) Modelando con SQL
Modelando un blog

Ejercicio
Modelando Tinder
Ejercicios de diagrama
Ejercicios de SQL
Modelando un carro de compras
Ejercicio
Seccin IV: Back-End con Rails
16) Rails con PSQL
Instalando Postgre en un proyecto existente
Creando un proyecto con postgreSQL
El error de socket
17) Los modelos en Rails
Los modelos como mapas a los objetos (ORM)
Creando modelos
Migraciones
El archivo schema.rb
Creando migraciones.
Creando una FK con ndice.
Getters y setters
El archivo seed
Atributos virtuales
Ejercicio
18) Reglas del negocio
Protegiendo la integridad de los datos
Validando la presencia de un campo
Validando que el campo sea nico
Validaciones custom

Valores iniciales
Callbacks
Ejercicios
19) Relaciones entre modelos
Relaciones 1 a 1
Gracando las relaciones
Relaciones de 1 a n
20) Testing unitario de modelos
La Carpeta de Tests
Estructura de un test de modelo
Corriendo tests
Los xtures
Cargando los xtures en los tests
Fixtures y relaciones
Otros tipos de testing
21) Creando un Blog
Construyendo el modelo
Construyendo los tests
Desaos
22) MVC
Arquitectura REST
Scaold
Strong parameters
Probando los strong params
23) El archivo de rutas a fondo
Introduccin al archivo de rutas
Rake routes a fondo

El archivo routes a fondo


Resources
24) Rutas anidadas y mtodos REST anidados
Introduccin a rutas anidadas
Creando un scaold anidado
Testeando una ruta anidada
Obteniendo y mostrando los resultados.
El detalle del tweet
Formulario para un nuevo tweet
Creando el mtodo create
25) Relaciones N a N
Introduccin a relaciones N a N
Creando modelos con has_and_belongs_to_many
Agregando elementos
Borrando la asociacin:
Tests para la relacin
Implementando relaciones con has_many :through
Has_many through vs Has_and_belongs_to_many
26) Haciendo un cloudtag
Creando datos para la nube de tags
Creando el controller y cargando los datos para hacer la nube de tags
Creando la vista con la nube de tags
27) Devise
Empezando con devise
Creando el modelo de devise
Ingreso, registro y salida
Login or logout

El objeto current_user
Modicando los formularios de ingresar y registro
Agregando un dato al modelo de usuarios
Bloqueando el acceso
Recuperando la contrasea
28) Devise avanzado
Agregando el rol de usuario
Testeando los accesos
Cdigos completos:
Generando los controllers de devise
Cambiando la pgina despus de registrarse
Cambiando la pgina despus de ingresar
Congurando action_mailer para enviar correos con gmail
Protegiendo las claves con dot-env
Congurando Heroku para dot-env
Quiz
29) Autorizacin con CanCanCan
Cundo no utilizar CanCanCan?
Big picture
Instalando CanCanCan
El rbol de habilidades
El mtodo can
Los roles
Revisin de habilidades
Bloqueo y carga
Manejo de conexiones no autorizadas
Probando las habilidades en la consola

Habilidades basadas en la propiedad


Habilidades en recursos anidados
30) Polimorsmo
31) Subiendo archivos con carrirewave
Instalando carrierwave
Generando el uploader
Probando desde rails console.
Creando una formulario con archivos
32) Amazon S3
Conguracin para la gema de carrierwave-aws
IAM
Agregando las claves de entorno a Heroku
33) Optimizacin
nd_each
N+1 queries en Ruby on Rails
N+1 en conteo
Contando con includes
Contando con SQL
Contando con left join
La gema bullet
Desnormalizacin y Counter cach
34) Javascript, jQuery y Turbolinks
Cmo organizar nuestro javascript?
Turbolinks
Ejemplos de uso
35) A JAX, Remotes y UJS
Para qu sirve A JAX?

UJS
36) El mtodo .ajax
37) Manejo de grcos
Haciendo los queries
Generando los grcos
Construyendo un calendario de eventos con Rails y Fullcalendar
Setup del proyecto
Setup de FullCalendar
Agregando eventos al calendario
39) Envo de correos con Action Mailer
Intro
Creando nuestro mailer
Modicando el mailer y como probarlo
Enviando el correo usando ActionMailer y Gmail
ActionMailer y Devise
ActionMailer y ActiveJob: deliver_now, deliver_later?
40) Testings automatizado con Guard
Instalar Guard en nuestro proyecto
Congurar Guard
Congurar Minitest-Reporters
Correr Guard para automatizar el testing
Seccin V: Deployment avanzado con Amazon y VPS
41) Rails y Nginx con Passenger en Ubuntu: Preparando nuestro entorno de produccin
(DigitalOcean).
Introduccin
Acerca de esta gua.
Convenciones.
Paso 0 Como acceder a nuestro servidor

Congurar el timezone en nuestro servidor


Acerca del usuario Root
Cmo ingresar sin tener que ingresar la clave cada vez que nos queremos conectar a
nuestro servidor?
Paso 1 Instalacin de RVM
Paso 2 Instalacin de Ruby y de Rails
Paso 3 Instalacin de Nginx y Passenger
Paso 4 Habilitando Passenger en Nginx
Paso 5 Instalacin y conguracin de Postgres.
Paso 6 Crear un Server Block
Paso 7 ltimos detalles
Extras
42) Deployment con Capistrano
Introduccin
Paso 0 La Aplicacin
Paso 1 Aadir Capistrano a nuestra app.
Paso 2 Preparacin de nuestro proyecto
Paso 3 Tareas personalizadas
Paso 4 Conectando el servidor con el repositorio
Paso 5 Deploy! Cmo se hace? y qu hace?

2) Motivacin
Qu es Ruby on Rails?
Rails en un framework especializado en la construccin de
aplicaciones web, su principales ventajas es que es slido, rpido y
escalable.

La versin actual de Ruby on Rails es 5.0 y se public hace algunos


das. Si bien este libro podra servir para utilizar la ltima versin, la
documentacin aun no esta completa y podra generar ms de
algn error. Es por eso que para efectos de este libro seguiremos
utilizando la versin anterior (4.2.7) hasta que la documentacin
sea lo sucientemente buena como para asegurar que no habrn
problemas.

Ruby no es lo mismo que Rails, el primero es un lenguaje de


programacin orientado a objetos y el segundo es un conjunto de
herramientas construidas en Ruby para la creacin de aplicaciones
web.

Por qu utilizar Ruby on Rails para mi aplicacin?


Ruby on Rails es impresionantemente efectivo para la construccin
de aplicaciones web, con slo un par de lneas de cdigo puedes
tener una mini aplicacin web funcionando. Adems trae un
conjunto de herramientas que permiten crear aplicaciones
complejas y escalables a niveles enterprise con equipos de
programacin relativamente pequeos.

Por otro lado tambin es sorprendentemente rpido para la

construccin de prototipos. Para una persona entendida en el


tema es posible crear un prototipo funcional de un proyecto y
publicarlo en internet en slo un par de horas.

Desventajas de utilizar Ruby on Rails


Es un framework complejo y Ruby no es tan masivo como otros
lenguajes como PHP, por lo que cuesta un poco ms encontrar
programadores. Sin embargo tiene una muy buena comunidad y
es fcil encontrar documentacin y componentes para todo lo que
necesitas. Adems da a da el mundo de la informtica ha
empezado a reconocer las ventajas de Ruby on Rails y esta
comenzando a migrar a este maravilloso framework.

Ruby on Rails tiene muchos componentes -que vamos a discutir a


lo largo del libro- que dan la impresin de que funcionan solas o
por arte de magia. Es por esto que su curva de aprendizaje es
bastante dura a pesar de ser un poderoso framework cuando se le
domina.

Finalmente est el tema del rendimiento que, al estar creado en


base a varios componentes, tiene un gran footprint. Eso signica
que es pesado y conlleva a que se necesiten servidores ms
potentes para atender la misma cantidad de usuarios, toda una
contraposicin a la facilidad con que se construyen aplicaciones en
este framework en comparacin a otros.

Tanto Ruby como Ruby on Rails estn pensados en la felicidad del


programador en lugar de la eciencia del programa.

Cmo est estructurado este libro?

Este libro cumple dos funciones; en primer lugar ser una gua
prctica de aprendizaje y en segundo contener los pasos
necesarios para construir diversos proyectos tipos, de los cuales se
pueden sacar varias ideas para emprendimientos.

Este libro se compone en 5 grandes secciones.

Front-End con Rails:

Veremos todo lo necesario para crear pginas web con Rails y a


manejar correctamente el asset_path, crear pequeos formularios
y entender los conceptos bsicos de MVC.

Deployment:

Subiremos aplicaciones a la web y aprenderemos a congurar una


pgina .com o cualquier otro tipo de dominio.

SQL y modelado de bases de datos

Estudiaremos lo necesario para a manejar y entender las bases de


datos y su funcionamiento en Rails, como se realizan las consultas
y como se optimizan.

Back-End con Rails

Aprenderemos a construir aplicaciones web con bases de datos y


autenticacin de usuarios, con controles de acceso y con sistemas
de pago.

Deployment avanzado con Amazon y VPS

Finalmente aprenderemos a subir nuestra aplicacin a entornos


enterprise como Amazon o un VPS y conguraremos nuestros
propios servidores ocupando NginX.

Seccin I: Front-end con Rails


En esta seccin estudiaremos todo lo referente a vistas o layouts.

3) Instalando las herramientas


Objetivos
1. Instalar la herramientas necesarias para trabajar con Ruby on Rails
2. Conocer las ventajas de RVM

OSX
Utilizando la versin de Ruby que viene instalada de fbrica
instalaremos Homebrew, un administrador de paquetes para OSX.
Para eso abriremos la terminal y pegaremos lo siguiente:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master

Linux
No es necesario instalar ningn administrador de paquetes, la
mayora de las distribuciones viene con alguno incluido. Por
ejemplo Ubuntu ocupa apt-get.

En Linux y OSX
Ahora instalaremos RVM, un programa que nos permite tener
diversas

versiones

de

Ruby

instaladas

actualizarlas

sin

problemas. Existen otros como Rbenv y chruby, las diferencias son


menores y slo afectarn en la ltima parte de deployment as que
para uniformar el proceso recomendamos a todos instalar RVM.

Correr este comando slo en OSX

brew install rvm

Correr este comando slo en Linux

apt-get install rvm

Es importante leer siempre los logs de la instalacin para


asegurarnos que haya terminado exitosamente y estar atentos a
poner los comandos que nos pida en caso de requerirlos.

Una vez que hemos instalado RVM y ledo todos los logs (para
asegurarnos de que todo se instal correctamente), reiniciaremos
la terminal para instalar la ltima versin de Ruby:

rvm install 2.3.1

Esta instalacin puede demorar ya que necesita compilar los


binarios de Ruby. Una vez terminado el proceso debemos vericar
qu es lo que se instal y lo haremos con el comando:

rvm list

Eso nos mostrar todas las versiones de Ruby instaladas con RVM,
de las cuales podemos escoger una con:

rvm use 2.3.1

Si queremos establecer nuestra versin por defecto lo haremos


con:

rvm --default use 2.3.1

Para asegurarnos de estar sobre la versin correcta debemos


correr el comando:

ruby -v

Finalmente instalaremos Rails con:

gem install rails -v 4.2.7

Las gemas que se instalarn dependiendo de la versin de Ruby, si


cambiamos la versin tendremos que instalar todas las gemas
nuevamente, incluyendo Rails.

Rails trabaja por defecto con SQLite y es ms que suciente para


construir prototipos, pero queda corto a la hora de construir
aplicaciones potentes. Durante el captulo de Heroku veremos
cmo instalar la gema de PostgreSQL.

Preguntas
1. Qu es RVM?
2. Cmo mostramos todas las versiones que tenemos instaladas de Ruby?
3. Cmo cambiamos la versin de Ruby que estamos ocupando?

4. Qu motor de base de datos utiliza Rails por defecto?

4) Estructura bsica de un proyecto de Ruby on Rails


Al crear un proyecto nuevo con Ruby on Rails se crearn las
siguientes carpetas y archivos de forma automtica.

Archivo

Para qu sirve

Gemle

Administrar libreras dependencias del proyecto

Gemle.lock

Muestra todas las dependencias, generado a partir del Gemle

README.rdoc

Para documentar el proyecto

Rakele

Crea tareas rake

app

Toda la aplicacin, assets, vistas, controllers y modelos van dentro

bin

Contiene los binstubs, los cuales son wrappers sobre gemas ejecutables

cong
cong.ru
db

Directorio con archivos de conguracin de Rails, los entornos y la base de


datos
Contiene la conguracin de Rack
Contiene la informacin de la base de datos, el archivo de sqlite3 y las
migraciones

lib

Mdulos para la aplicacin

log

Registro de los errores

public

Assets pblicos

test

Scripts para tests automatizados

tmp

Para archivos temporales necesarios durante la ejecucin de Rails

vendor

Assets de terceros

Cada carpeta tiene una funcin especca y que iremos detallando


a lo largo del libro.

5) Creando nuestra primera pgina web con Rails


Objetivos
1. Aprender a crear un proyecto en Rails desde cero
2. Denir la pgina de inicio de un proyecto Rails
3. Conocer el lenguajes de templates erb
4. Crear accesos a nuevas pginas

Rails es un framework MVC, esto quiere decir que divide la


informacin a lo largo de 3 capas principales: el modelo, la vista y
el controlador.

En este captulo abordaremos los componentes de vista y


controlador, para lograrlo crearemos una pgina esttica con
Ruby on Rails.

Para empezar crearemos desde el terminal nuestro primer


proyecto: una pgina esttica sobre Rails.

rails new landing1

Luego entraremos a la carpeta del proyecto desde el terminal y


corremos el servidor.

1
2

cd landing1
rails s

Si entramos a la carpeta landing1, encontraremos la estructura de


archivos y directorios que estudiamos en el captulo anterior.

Antes de seguir avanzando, vericaremos que todo funcione:


abriremos el navegador y entramos a la pgina localhost:3000

Consola secuestrada.
Mientras est corriendo el servidor en la consola, esta estar
secuestrada.

Esto quiere decir que no la podemos seguir utilizando para lanzar


comandos y por lo tanto tenemos que abrir un nuevo tab para
seguir trabajando.

Para terminar el servidor -o sea cerrarlo- sin cerrar el tab podemos


utilizar ctrl + c . Eso terminar el programa devolvindonos el
control para ejecutar otros comandos.

Creando nuestra primera pgina


Introduccin a controllers

Si bien hemos creado un nuevo proyecto en Rails, aun no hemos


agregado ninguna pgina. Es por eso que a continuacin vamos a
crear

un

controller

y,

aunque

no

lo

hemos

descrito

completamente, por ahora solo nos interesar entenderlo como un


grupo de pginas.

Por ejemplo; si creamos el controller grupo1, nuestras pginas


seran:

localhost:3000/grupo1/inicio
localhost:3000/grupo1/formulario

Tanto los controllers como los modelos y vistas (los cuales veremos
despus) se pueden crear manualmente o con el generador de
Rails.

Para crear un controller con el generador tenemos que escribir:

rails g controller nombre_grupo pag1 pag2 pag3 ...

Por ejemplo si queremos crear nicamente la pgina inicial (index),


lo hacemos de la siguiente forma:

rails g controller pages index

Es convencin de Rails que los controller siempre tengan un


nombre plural y, a su vez, tiene sentido pensando que son un
grupo de pginas.

Obtendremos como resultado en el mismo terminal:

1
2

create
route

app/controllers/pages_controller.rb
get 'pages/index'

3
4
5
6
7
8
9
10
11
12
13
14
15

invoke
create
create
invoke
create
invoke
create
invoke
invoke
invoke
create
invoke
create

erb
app/views/pages
app/views/pages/index.html.erb
test_unit
test/controllers/pages_controller_test.rb
helper
app/helpers/pages_helper.rb
test_unit
assets
coffee
app/assets/javascripts/pages.coffee
scss
app/assets/stylesheets/pages.scss

Esto nos muestra todos los archivos creados y modicados, pero


por

ahora

nos

interesan

slo

route get 'pages/index'

dos:

el

archivo

de

rutas

y la lnea donde se crea la vista

app/views/pages/index.html.erb

Antes de proceder a explicar las vistas y el archivo de rutas,


veamos lo creado. Para poder hacerlo accederemos a la pgina
creada

travs

de

http://localhost:3000/pages/index

la

URL:

Esto slo funcionar si el servidor est corriendo y si adems no


hay errores en el cdigo.

Errores de novato
1) No tener el server corriendo

Revisar rails s

2) No haber cargado el server en la carpeta correcta

Volver a la terminal ver donde est corriendo el server

3) Haber creado el controller con nombre en singular

Podemos

destruir

el

controller

con

rails destroy controller page

Introduccin a las vistas


Actualmente slo tenemos un ttulo y un prrafo pero aqu es
donde podemos empezar a agregar nuestro contenido.

Para

eso

podemos

abrir

app/views/pages/index.html.erb

el

archivo

y modicar el HTML,

incluso podemos escribir Ruby, esto lo podemos hacer gracias al


lenguaje de template de Ruby llamado erb, por eso el formato del
archivo es .html.erb

Para escribir Ruby tenemos que hacerlo dentro de las expresiones


en <%= %> o <% %>. La primera expresin se ocupa para mostrar
el contenido, el segundo sirve para evaluar, miremos los siguientes
ejemplos:

1
2
3

<% a = 2 %>
<%= a %>
<%= b = 2 %>

Esto mostrara dentro del navegador 2 2 ; la primera expresin


no se muestre porque <% %> no incluye el signo igual = , por lo
tanto el primer dos correspondiente al valor de a ( a = 2 ) y el
segundo 2 corresponde al valor de b ( b = 2 ).

Esto quiere decir que podemos enviar Ruby al navegador del cliente?

No, todo el contenido del archivo ser transformado a HTML por Rails antes ser
enviado. Esto es necesario porque los navegadores no son capaces de procesar el
lenguaje Ruby, por lo que jams entendera lo que se le enva. Por otro lado sera
peligroso, porque personas con malas intenciones podran estudiar tu cdigo e
intentar algo deshonesto.

Cmo convertir esta pgina en la pgina principal?


Si

con

nuestro

navegador

entramos

la

direccin

localhost:3000 y vemos el aviso de Rails, es porque aun no


hemos congurado en nuestra aplicacin la pgina principal y Rails
no sabe qu debe mostrar. Para eso tenemos que modicar el
archivo

routes.rb,

el

encargado

de

manejar

todos

los

enrutamientos.

Dentro del archivo

config/routes.rb

encontraremos una

lnea comentada que dice:

# root 'welcome#index

Esta lnea es un recordatorio de Rails que nos dice cmo podemos


denir una pgina inicial. Para utilizarla vamos a descomentarla

(remover el #) y luego apuntar al controller y la pgina respectiva,


o sea en nuestro caso debera decir:

# root 'pages#index'

Si ahora entramos a

localhost:3000

veremos la misma

pgina.

Creando nuestra segunda pgina


Ya no podemos ocupar le generador de controllers puesto que ya
tenemos el controller generado y no queremos sobreescribir los
archivos.

Una solucin sera crear un segundo controller pero no es


necesario crear un controller por pgina.

Para crear una segunda pgina vamos a:

1. Crear una ruta a la pgina.


2. Crear un mtodo en el controller.
3. Crear la vista.
4. Agregar el link a la pgina nueva. (esta parte no es
estrictamente necesaria),

Para nuestra prueba vamos a crear la pgina about (nosotros).

Creando la ruta
Para agregar la ruta nueva debemos abrir el mismo archivo de
rutas que abrimos anteriormente

config/routes.rb

dentro

de l agregaremos get y la ruta a nuestra nueva pgina (about) Se


debera ver as:

1
2
3
4
5

Rails.application.routes.draw do
get 'pages/index'
get 'pages/about'
root 'pages#index'
end

Existe un comando que nos permite vericar las rutas creadas,


este comando se llama rake routes y lo tenemos que correr
dentro de la carpeta del proyecto, en alguna terminal que no est
secuestrada.

Al realizar rake routes, obtendremos:

1
2
3
4

Prefix
pages_index
pages_about
root

Verb
GET
GET
GET

URI Pattern
/pages/index(.:format)
/pages/about(.:format)
/

Controller#Action
pages#index
pages#about
pages#index

Ms adelante, en este libro, analizaremos en profundidad esta


informacin, pero por ahora sabemos que es correcta porque se
incluy dentro de la columna llamada URI Patten la pgina
/pages/about , y con esto terminamos el paso de agregar la
ruta.

Si nos hubisemos saltado este paso (o comentamos el get


pages/about que pusimos en el archivo config/routes.rb ) e
intentamos entrar a la pgina obtendramos este error:

Cada vez que tengamos un error de rutas, sabemos que el archivo


culpable es routes.rb.

Ahora; si intentamos saltarnos el paso 2 y entramos directamente


a

la

pgina

siguiente error.

localhost:3000/pages/about ,

veremos

el

The action about could not be found for PagesController

O sea nos falta la accin.

Creando un mtodo en el controller


Anteriormente habamos hablado del controller, pero asumimos
que funcionaba de forma mgica, ahora vamos a tener que
modicarlo para agregar una pgina nueva, para eso abriremos el
archivo app/controllers/pages_controller en el editor, y
veremos:

1
2
3

class PagesController < ApplicationController


def index
end

PagesController

es

un

controller

que

hereda

de

ApplicationController, dentro de l cada mtodo llama a su


respectiva vista, pero lo hace de forma implcita. Esta vista se
encuentra dentro de views/pages/index.html.erb .

Si ya tenemos una ruta para about el paso siguiente es agregar un


mtodo (que es lo mismo que una accin) dentro del controlador:

1
2

def about
end

Creando la vista.
Con la ruta y el mtodo del controller creado ahora procederemos
a crear el archivo views/pages/about.html.erb y dentro de
l podemos agregar lo que queramos.

Finalmente podemos acceder a la pgina nueva si entramos


localhost:3000/pages/about

Agregando un link a una pgina interna


Rails crea variables para todas nuestras pginas internas, para
poder ver como se llaman esas variables tenemos que ocupar
rake routes

1
2
3
4

Prefix
pages_index
pages_about
root

Verb
GET
GET
GET

URI Pattern
/pages/index(.:format)
/pages/about(.:format)
/

Controller#Action
pages#index
pages#about
pages#index

La columna que dice prex son los nombres de la variable, bueno


casi, es un prejo porque hay dos: aquellas que terminan en path
y contienen la ruta relativa, y aquellas que terminan en url que
contienen la ruta absoluta.

Si queremos ver el link tenemos que recordar que la variable est


en Ruby, as que tenemos que imprimirla en la vista ocupando la
zanahoria <%= .

<%= pages_about_path %>

Otra forma de hacer lo mismo es ocupando el helper link_to de la


siguiente forma:

link_to "About", pages_about_path

El helper se transformar en un <a href=""> apuntando a la


pgina respectiva.

Y dentro de la vista de about podemos agregar un link a la pgina


de inicio de la siguiente forma:

link_to "Home", pages_index_path

Preguntas
1. Si tenemos un error del tipo el mtodo no existe, donde est el
problema?
2. Si tenemos un error del tipo el template falta, donde est el error?
3. Si tenemos un error del tipo la ruta no existe, donde est el error?
4. Qu quiere decir agregar una accin?, en qu archivo va?
5. Para qu sirve el formato .erb?
6. Cul es la diferencia en <%= %> <% %>?
7. Qu tipo de navegador se necesita para ver un proyecto con Ruby on
Rails?
8. Cuntas pginas puede tener un controller?
9. Qu comando muestra todas las rutas que existen en Rails?

10. Cmo se crea un controller con tres pginas?


11. Cmo se destruye un controller?
12. Cul es la diferencia entre pages_index_path y pages_index_url?
13. Qu hace el mtodo link_to?

6) Agregando imgenes a nuestra primera pgina


Objetivos
1. Aprender a incorporar imgenes a un sitio web
2. Introducir el concepto de asset_path

Los Assets
En Rails los assets se deberan encontrar divididos en dos carpetas
principales, la primera est dentro de app/assets y la segunda
dentro de vendor/assets`. La primera sirve para nuestros assets, la
segunda para las libreras de terceros, o sea lo que descarguemos
de internet.

El asset_path
Cuando uno est empezando a programar en Rails el asset_path
es la primera gran pesadilla, por no saberlo manejar bien uno
puede demorar horas en simplemente cargar una imagen o un
CSS, por lo mismo vamos a estudiarlo para evitar conictos
futuros.

Qu es el asset_path?

El asset_path es una lista de todos los directorios donde se


encuentran los assets, cuando queremos cargar una imagen o
cualquier tipo de assets simplemente le decimos a Rails que lo

busque dentro del asset_path

Incorporando imgenes
En una pgina web normal podemos ingresar imgenes de ruta
relativa (dentro de nuestro computador) simplemente usando el
tag:

<img src="imagen-prueba.png">

Pero esto no lo podemos hacer en Rails, bueno realmente si


escribiramos la ruta al archivo desde la raz del proyecto rails si
funcionara, por ejemplo algo como:

<img src="assets/images/imagen-prueba.png">

Lo anterior slo funcionar en el entorno local (mientras estemos


probando en nuestro computador), pero fallar cuando queramos
correr nuestra aplicacin en produccin (o sea en una mquina
remota), por lo mismo no lo debemos hacer.

La forma correcta de incluir imgenes en Rails es ocupando los


mtodos image_tag o asset_path

El mtodo asset_path
Sirve para todos los tipos de assets as que partiremos explicando
este. Para utilizar el mtodo de Rails asset_path, tenemos que
utilizar el formato erb, o sea incorporar la zanahoria <%= %>
dentro del tag <img> de HTML. O sea quedara as:

<img src="<%= asset_path 'imagen-prueba.png' %>">

Se debe tener cuidado con un par de cosas; la imagen tiene que

existir dentro de la carpeta /app/assets/images/ y adems se


debe tener mucho cuidado con la sintaxis del asset_path, porque
no debe quedar ningn espacio en blanco entre el inicio o el cierre
de la zanahoria (<%= %>) y la comilla correspondiente. Esto ltimo
es con la nalidad de evitar dejar espacios vacos en el nombre del
archivo que no forman parte del nombre.

El mtodo image_tag
Hay una forma ms breve de incluir imgenes, pero no aplica a los
otros assets, y esta es ocupando el mtodo image_tag.

Para incorporar la misma foto que incorporamos previamente


sera con:

<%= image_tag 'imagen-prueba.png' %>

image_tag adems puede recibir otros parmetros como el id o la


clase.

<%= image_tag 'imagen-prueba.png', class:"img", id:"img-1"%>

Una ventaja de ocupar image_tag es que automticamente genera


un atributo alt utilizando el nombre del archivo.

Es necesario aclarar que, para que cualquiera de estas dos


soluciones funcione, tenemos que estar ocupando la extensin
erb; o sea nuestro archivo debe ser .html.erb (por defecto en
Ruby).

7) Nuestra primera pgina con layout


Objetivos
1. Aprender a utilizar la pgina maestra
2. Profundizar en el concepto de asset_path
3. Descubrir como manejar diversos CSS y javascript en una aplicacin Rails
4. Entender el proceso de precompilacin de assets
5. Aprender a reutilizar cdigo con las vistas parciales
6. Saber cambiar el layout de los controllers y mtodos

El Layout
Si observamos detenidamente las vistas nos daremos cuenta que
no estn completas; no tienen la estructura tpica de un
documento HTML y, si es as, de adonde sale el ttulo de la pgina?

Dentro de la carpeta views existe una subcarpeta llamada layouts,


estas son las pginas maestras. Es posible tener varias pero por
defecto es una sola y esta se llama application.html.erb

Si modicamos el layout cambiaremos todas las otras vistas


simultneamente, probemos que esto sea cierto. Al abrir el layout
veremos lo siguiente:

1
2
3

<!DOCTYPE html>
<html>
<head>

4
5
6
7
8
9
10
11
12
13
14

<title>Basic</title>
<%= stylesheet_link_tag
'application',
media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application',
'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>

En esta pgina maestra podemos denir el contenido y estilo que


queramos, compartir a lo largo de todas las pginas de nuestra
aplicacin o solo utilizarla con algunas.

Para vericar que sea cierto vamos agregar el siguiente texto antes
de la instruccin yield:

1
2

HOLA !!!
Modificando el layout

Y luego, si abrimos las pginas, veremos que en ambas dice el


texto.

Por otro lado Cmo cambiamos el estilo de nuestro sitio? Para eso
tenemos que agregar CSS.

Incorporando CSS

Los archivos CSS son fciles de incorporar, simplemente debes


colocarlo dentro de la carpeta

apps/assets/stylesheets .

Todos los archivos presentes en esa carpeta se cargarn en cada


pgina gracias a que en nuestro layout viene incorporada la
siguiente lnea.

1
2

<%= stylesheet_link_tag 'application', media: 'all',


'data-turbolinks-track' => true %>

Esa

lnea

lo

que

hace

es

cargar

el

archivo

app/assets/stylesheets/application.css el cual adems


de un par de comentarios contiene:

1
2
3
4

/*
*= require_tree .
*= require_self
*/

En primer lugar hay que aclarar que estas lneas estn comentadas
al estilo CSS para evitar que la pgina web las cargue, puesto que
require

no es un instruccin de CSS vlida, pero en este

archivo si puede haber CSS.

Cabe destacar que este archivo recibe el nombre de maniesto y cada una de las lneas
require dentro recibe el nombre de directivas.

Lo siguiente que vemos es require_tree, esta es la lnea


responsable de cargar recursivamente, tanto directorios como
subdirectorios, todos los CSS dentro de esta carpeta.

En este archivo el orden de carga importa y si fuera necesario


establecer un orden de carga este se puede especicar en este

mismo archivo a travs de la instruccin require y nombrando los


css, por ejemplo:

1
2
3
4
5
6
7

/*
*=
*=
*=
*=
*=
*/

require reset
require layout
require chrome
require_tree .
require_self

Mientras que require_self carga el CSS (si es que hay contenido)


del archivo application.css en la posicin mencionada. En el
ejemplo; primero se cargara el archivo reset, luego layout y luego
chrome. A continuacin cargara -a travs de require_tree .
todos los otros archivos que existan dentro de la carpeta CSS y
nalmente el cdigo dentro de este archivo application.css
en el caso de que hubiese.

Para probarlo vamos a denir un CSS sencillo, al que vamos a


llamar

style.css

debe

estar

dentro

de

la

carpeta

app/assets/css .

1
2
3
4
5
6
7

body{
background-color:#ddd;
font-color:#333;
width:80%;
margin:20px auto;
font-family: 'helvetica';
}

Si ahora cargamos nuestra pgina veremos que el fondo es gris, los


mrgenes y que la tipografa carg.

La librera encargada de procesar estas directivas y convertir todos los CSS (y otros
assets) en un nico archivo recibe el nombre de Sprockets

Incorporando Javascript
Con respecto a este punto no nos extenderemos puesto que
funciona exactamente igual que los CSS, la diferencia es que la
lnea que carga todos los js dentro del layout es:

<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>

El

archivo

cargado

por

app/assets/javascript/application.js

defecto

es

y funciona de la

misma forma que application.css. Dentro del archivo se encuentra


lo siguiente:

1
2
3
4

//=
//=
//=
//=

require jquery
require jquery_ujs
require turbolinks
require_tree .

O sea Rails incluye las libreras de jQuery, jQuery_ujs -que sirve


para trabajar con javascript no intrusivo- y turbolinks, del cual
hablaremos ms adelante.

Incorporando imgenes (u otros assets) dentro del CSS y del JS


Los assets incorporados dentro de los archivos CSS estn sujetos a
las mismas condiciones que los que ya hemos visto, pero para
poder ocupar el mtodo asset_path dentro de estos necesitamos
que estos archivos adems contengan la extensin .erb.

Por ejemplo: si tenemos un archivo style.css el cual pone una


imagen como background de fondo, entonces debemos nombrarlo

style.css.erb y luego dentro de la url(fondo1.png) cambiarlo por:

url('<%= asset_path "fondo1.png" %>')

Es muy importante que nos jemos bien en la sintaxis de nuestro


archivo CSS. Si bien CSS es tolerante a fallas debido al principio de
graceful degradation, la precompilacin de archivos CSS no es tan
tolerante y un punto y coma sobrante causar problemas a la hora
de enviar nuestro proyecto a produccin.

Qu es la precompilacin de assets?
La precompilacin de assets es un proceso por el cual los assets
son preparados para produccin.

1. Preprocesador
Transforma los lenguajes a su forma compartible, por ejemplo
los archivos coescript se convierten en javascript, los archivos
SASS se convierte en CSS.
2. Concatenador
Todos los CSS se juntan en un solo archivo nal, todos los JS se
juntan en un archivo nal, esto reduce el nmero de requests
hechos al servidor traducindose en una pgina ms rpida.

1. Minicador

Cuando al CSS nal y al JS nal se le remueven todos los


caracteres innecesarios, como por ejemplo los saltos de lneas y
los espacios, se traduce en archivos de menor peso y por lo
mismo pginas ms rpidas.
2. Fingerprint
Consiste en agregar una secuencia de nmeros para convertir el
nombre de un asset en un nombre nico y realizar de forma
ms sencilla el caching de los assets.

El entorno
En Ruby on Rails existen distintos entornos de funcionamiento,
pero por defecto son tres; desarrollo, testing y produccin. En
algunas ocasiones es til agregar un 4 llamado staging.

La razn por la que existen diversos entornos es porque tienen


distintos

funcionamientos,

cuando

trabajamos

en

nuestro

computador queremos ver los errores con el mayor detalle posible,


pero cuando nuestra aplicacin est online y la est ocupando una
persona externa y sucede algn error queremos estar seguros de
que no expondremos una falla de seguridad de nuestro sistema y
por lo mismo en Rails se esconden los errores en modo de
produccin.

El modo testing sirve para correr pruebas, lo ocuparemos ms


adelante en este libro.

El modo staging se agrega cuando se quiere un paso previo a


produccin (a veces con clientes reales o a veces solo
homologando el ambiente para prueba) y prevenir errores
inesperados al dar el paso a produccin.

Cuando uno trabaja en el entorno de desarrollo no se


realizan todos los procesos de precompilacin de assets,
slo se realiza el preprocesado para poder utilizar sass y

coeescript, el resto de los procesos slo se procesan


cuando

pasamos

al

entorno

de

produccin

si

modicamos nuestro entorno de desarrollo para que lo


haga.

Los archivos donde se especica esta conguracin se encuentran


en

config/enviromments , estos archivos tambin permiten

modicar otros comportamientos como por ejemplo si se deben


mostrar los errores.

Podemos correr Rails en modo de produccin dentro de nuestro


computador cargando el server con el parmetro -e production

rails s -e production

Pero esto nos generar conictos ya que todava no tenemos


congurado algunos archivos, as que el intentar cargar la pgina
obtendremos el siguiente error.

Missing `secret_token` and `secret_key_base` for 'production' environment, set these val

En el captulo de Heroku como en los ltimos captulos de este


libro hablaremos con ms detalle del paso a produccin.

La carpeta assets de vendor


Rails trae tres carpetas de assets incluidas en el asset_path, hasta
el momento slo hemos mencionado la que se encuentra dentro
de app, pero dentro de la carpeta vendor y dentro de la carpeta lib
tambin es posible agregar assets.

La idea es que la carpeta

app/assets

contenga los assets

especcos del proyecto, la carpeta vendor contenga los assets de


libreras de terceros (como por ejemplo los de Bootstrap) y
nalmente la carpeta lib para otros casos.

Cuando colocamos los assets en otras carpetas el require_tree no


es suciente para cargarlos, ya que este tree se reere al de
app/assets/stylesheets . Entonces, para que los archivos se
puedan ser utilizados, se deber agregar la carpeta al asset_path.

Agregando una carpeta nueva al asset_path


Es posible agregar nuevas carpetas al asset_path modicando el
archivo /config/initializers/assets.rb

Rails.application.config.assets.paths << Rails.root.join('mis_assets', 'assets'

Tambin para hacer ms fcil las referencias puedes agregar


subcarpetas

1
2

Rails.application.config.assets.paths << Rails.root.join('mis_assets', 'assets'


Rails.application.config.assets.paths << Rails.root.join('mis_assets', 'assets'

Verificando el asset_path
Rails tiene una consola que nos permite vericar este tipo de
conguraciones, para entrar a ella dentro de la carpeta del
proyecto tenemos que escribir rails c o rails console y
luego podemos vericar nuestros assets con:

Rails.application.assets

Esto nos devolver una lista con todas las carpetas que se
encuentra dentro del asset_path

Si realizamos un cambio dentro del archivo de conguracin es


necesario salir de la consola y volver a entrar para poder ver los
cambios.

Incorporando tipografas locales


Para incorporar tipografas todava nos falta aprender una cosa
ms: a manipular que archivos y qu carpetas son parte del
asset_path.

Para

esto

vamos

/config/initializers/assets.rb

modicar

el

archivo

y dentro de la clase

vamos a agregar lo siguiente:

1
2

Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'fonts'


Rails.application.config.assets.precompile += %w( *.svg *.eot *.woff)

Finalmente cuando se modica el archivo application.rb o


cualquier initializer debemos reiniciar el servidor, y con eso ya
deberamos tener acceso a nuestras tipografas.

Consecuencias de Sprockets
Al unirse todos los CSS y cargarse en cada pgina podemos tener
consecuencias

indeseadas,

especialmente

cuando

tenemos

plantillas que cargan diversos archivos CSS en cada pginas. Para


enfrentar este problema hay dos posibles soluciones, la primera
consiste en darle un namespace a la pgina, la segunda en utilizar
un layout distinto, ahora abordaremos la solucin del namespace y
ms adelante en este captulo estudiaremos como incorporar
diversos layouts.

Para dar un namespace a la pgina lo que debe hacerse es ocupar


el hash params dentro de la vista, este hash contiene informacin
general que se realiza en cada request, una informacin
importante que tiene es el nombre del controller y del mtodo
llamado, estos se guardan bajo las claves de controller y action y
los podemos ocupar en las vistas, en el layout y en el controller.

params[:controller]

Utilizando el hash params podemos generar un namepsace a cada


vista, simplemente agregado ese dato a la clase body dentro del
layout.

1
2
3
4
5
6
7

<!DOCTYPE html>
<html>
<head>
<title>Namespace Trick :)</title>
<%= stylesheet_link_tag
'application', media: 'all', 'data-turbolinks-track' => tru
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>

8
9
10
11
12
13

</head>
<body class="<%= params[:controller]%>">
<%= yield %>
</body>
</html>

De esta forma si cargamos alguna pgina dentro del controller


pages, obtendremos:

<body class="pages">

Podemos hacerlo an ms especco utilizando:

1
2
3

<body class="<%= params[:controller]%> <%= params[:action]%>">


<%= yield %>
</body>

En ese caso si cargamos el mtodo landing dentro de pages


obtendremos:

<body class="pages landings">

Queda un paso que en teora es sencillo, ahora cada CSS que sea
especco a una grupo de pgina debe tener junto a cada marca un
.nombre-controller y cada CSS especco a una sola pgina debe
empezar con un .nombre-controller .nombre-accin

Incorporando Bootstrap.
Hay varias formas de incorporar Bootstrap, la primera y ms
sencilla es ocupar el CDN.

CDN
El CDN consiste en 2 CSS, uno el base, y el otro el tema junto con
los JS para alguna de las funcionalidades.

Estos debemos copiarlos dentro de nuestra pgina maestra en


views/layouts/application.html.erb

1
2
3
4
5
6
7
8

<!-- Latest compiled and minified CSS -->


<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstr

<!-- Optional theme -->


<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstr
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js

Pero para copiarlo bien tenemos que tener en cuenta la carga de


nuestros CSS, los CSS de Bootstrap debemos cargarlo antes de
nuestros CSS para poder modicarlo (o si no Bootstrap reescribir
nuestros cambios) y los JS debemos cargarlo despus de la
instruccin javascript_include_tag pues esta instruccin es
la que carga jQuery en nuestro sitio y los JS de boostrap depende
de jQuery. Entonces nuestro layout con Bootstrap quedara as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<!DOCTYPE html>
<html>
<head>
<title>Basic2</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/boo

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/boo

<%= stylesheet_link_tag
'application', media: 'all', 'data-turbolinks-track' => t
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>

15
16
17
18
19
20
21
22
23

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js
</head>
<body>
<%= yield %>
</body>
</html>

Descargando los CSS


Es posible descargar los CSS de Boostrap y dejar los archivos
dentro de la carpeta

vendor/assets , sin embargo debemos

tener cuidado con poner los CSS dentro de la carpeta CSS y los JS
dentro de la carpeta JS. Funciona si los cruzamos pero la idea es
mantener todo ordenado. Y si creamos una carpeta nueva
tenemos que agregarla al asset_path

Entonces primero descargamos los archivos de la pgina ocial:


http://getbootstrap.com/getting-started/

Descargaremos la versin de Bootstrap actual (en estos momentos


v3.3.7) y copiaremos los archivos CSS en lugar de los .min puesto
que Rails tiene la capacidad de minicarlos. Los archivos
bootstrap.css

bootstrap-theme.css

van

en

vendor/assets/stylesheets , luego copiamos el archivo


bootstrap.js en vendor/assets/javascript .

El ltimo paso que nos queda es mencionar dentro del


application.css los archivos de Bootstrap, puesto como habamos
explicado

previamente

la

lnea

require_tree

app/assets/stylesheets/application.css

dentro

de

slo carga el

tree de stylesheets, por lo que debemos hacer el require de forma


explcita.

/*
...

2
3
4
5
6
7
8

*= require bootstrap
*= require bootstrap-theme
*= require_tree .
*= require_self
...
*/

De

la

misma

forma

debemos

hacerlo

dentro

de

app/assets/javascript/application.js

Este debera quedar:

1
2
3
4
5

//=
//=
//=
//=
//=

require jquery
require jquery_ujs
require turbolinks
require bootstrap
require_tree .

Glyphicons
Este tema puede ser un poco ms complejo porque en primer
lugar

hay

que

vendors/assets
agregar

la

agregar

la

carpeta

fonts

dentro

de

y copiar los archivos ah, luego hay que

carpeta

fonts

al

asset_path

en

el

archivo

config/initializers/assets.rb

Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets'

En el mismo archivo hay que agregar los nuevos formatos al


proceso de precompilacin

1
2

Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets'


Rails.application.config.assets.precompile += %w( *.svg *.eot *.woff *.ttf *.woff2)

y hay que reiniciar el servidor.

Pero adems tenemos que revisar el archivo css de boostrap y


cambiar las referencias desde donde se cargan los fonts, puesto
hay que ocupar el asset_path en vez de las rutas que utiliza.

Para eso con ctrl+f dentro del archivo podemos buscar el nombre
de los fonts, en este caso glyphicons-halings, y buscar donde se
cargan, debera aparecer con url() cerca de la lnea 266
encontraremos:

1
2
3
4

src: url('../fonts/glyphicons-halflings-regular.eot');
src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype
url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),
url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphi

Ah es donde tenemos que eliminar el ../fonts y luego envolverlo


ocupando <%= asset_path "nombrefuente" %>

Debera quedar as:

1
2
3
4
5
6

src: url('<%= asset_path "glyphicons-halflings-regular.eot" %>');


src: url('<%= asset_path "glyphicons-halflings-regular.eot?#iefix" %>') format
url('<%= asset_path "glyphicons-halflings-regular.woff2" %>') format('woff2'
url('<%= asset_path "glyphicons-halflings-regular.woff" %>') format('woff'
url('<%= asset_path "glyphicons-halflings-regular.ttf" %>') format('truetype'
url('<%= asset_path "glyphicons-halflings-regular.svg#glyphicons_halflingsregular" %>'

Para probar que funcion podemos agregar un glyphicon a nuestra


vista.

<span class="glyphicon-cloud"> hola </span>

Vistas parciales
Una vista parcial es una parte de un archivo HTML que
simplemente se pone en otro archivo.

La vistas parciales se caracterizan son archivos .html.erb pero


tienen una caracterstica especial, empiezan con el prejo

_ y

esto ayuda a los programadores a distinguir que vistas no estn


completas, o sea son parciales y son para ser insertadas en otros
archivos.

Para cargar una vista parcial slo tenemos que especicar el


nombre de un archivo HTML que empiece con el prejo _

<%= render 'layouts/navbar' %>

Las vistas parciales sirven mucho en dos casos, desacoplar y


mantener ordenadas las vistas y para evitar repetir cdigo, el
primer caso lo estudiaremos en este captulo, el segundo lo
veremos ms adelante.

Cargando el navbar de Bootstrap como una vista parcial


Si queremos desacoplar una vista, por ejemplo si queremos copiar
la barra de navegacin de Bootstrap (que consiste en ms de 40
lneas de cdigo HTML) es mejor copiarlas dentro de una vista
parcial y de esa forma no ensuciar el layout. Entonces debemos
crear

un

llamaremos

archivo

dentro

de

_navbar.html.erb

app/views/layouts

y dentro de l copiaremos la

barra de Bootstrap que aparece en la pgina.

<nav class="navbar navbar-default">


<div class="container-fluid">

que

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Brand</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"
<ul class="nav navbar-nav">
<li class="active">
<a href="<%= landings_efrain_path %>">
Efrain <span class="sr-only">(current)</span>
</a>
</li>
<li>
<a href="<%= landings_gonzalo_path%>">Gonzalo</a>
</li>
</ul>
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" name
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>

y nalmente dentro del layout (y antes del yield) escribiremos: <%=


render layouts/navbar %>

Cargando un layout distinto


Cada controller se encarga de cargar el layout, si no se especica
uno se carga el layout por defecto, el cul es application.html.erb

Dentro del mtodo correspondiente podemos especicar que


layout no cargar, o si no queremos ningn layout tambin
podemos hacerlo:

1
2
3

def index
render layout: false
end

Podemos hacer lo mismo a nivel de controller para todos los


mtodos internos:

1
2
3
4
5
6
7
8

class PagesController < ApplicationController


layout false
def index
end
def about
end
end

Para mostrar un layout distinto es la misma idea, supongamos que


queremos hacer un layout distinto para las landings pages:

A nivel de accin:

1
2
3

def index
render layout: "landing"
end

A nivel de controller:

1
2
3
4
5
6
7
8

class PagesController < ApplicationController


layout "landing"
def index
end
def about
end
end

El layout puede tener la estructura que quieras, pero debes


recordar el yield o no podrs ver el contenido de la vista especca.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<!DOCTYPE html>
<html>
<head>
<title>Layout Distinto</title>
<%= stylesheet_link_tag
'application', media: 'all', 'data-turbolinks-track' => tru
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
hola
<%= yield %>
</body>
</html>

Desafo
Cargar una plantilla de HTML dentro de Rails.

Preguntas
1. Para qu sirve el layout?
2. Qu es el asset_path?
3. Qu hace la lnea require_tree?
4. Cmo se puede especicar el orden de carga de un CSS?
5. Cmo se puede cargar una imagen u otro asset dentro de un CSS?
6. Cmo se puede agregar una nueva carpeta al asset path?
7. Para qu sirven los assets dentro de la carpeta vendor?
8. Para qu sirve la lnea require_tree dentro del maniesto?
9. En qu lnea del layout se carga jQuery?
10. Por qu los CSS de Bootstrap hay que cargarlos antes de
stylesheet_link_tag?
11. Para qu sirve el rails console?
12. Qu son los vistas parciales?
13. Qu caractersticas tienen los archivos que son vistas parciales?
14. Cmo se puede cargar una vista parcial?
15. En qu consiste el proceso de minicado?

8) Creando un formulario de registro


Objetivos
1. Introducir el concepto de modelo
2. Aprender a crear un formulario bsico

En el captulo anterior vimos los componentes de vistas y controles


de Rails, en este captulo abordaremos la introduccin a modelos
creando nuestro primer formulario.

Para repasar vamos a empezar desde cero.

1) Creamos el proyecto

rails new primer_form

2) Cargamos el CDN de Bootstrap en el layout.

Agregamos los CSS y JS como se especica en la pgina:

http://getbootstrap.com/getting-started/#download-cdn

Los CSS debemos ponerlos antes de la carga del maniesto CSS y


el javascript despus del maniesto de javascript.

3) Creamos el controller landing con la pgina index

rails g controller landing index

El tag form
En Ruby on Rails al igual que en HTML podemos agregar
formularios utilizando el tags de form.

Para probarlo construiremos un formulario simple dentro del


index de la aplicacin que ya tenemos denida.

1
2
3
4

<form method="">
<input name="q">
<input type="submit" value="Enviar">
</form>

Despus de guardar, dentro de la pgina web deberamos ver un


input, y si lo llenamos veremos que por defecto nos redirige a la
misma pgina, pero esta cambiar ligeramente, pues al nal de la
url aparecer ?q=hola donde q es el nombre del input y el igual
el valor que fue pasado.

Revisando los parmetros enviados


Todos los parmetros en Rails se pasan a travs de un hash
llamado params, dentro de la vista podemos mostrar el contenido
de este hash con:

<%= params %>

Veremos:

Es Rails los hash tienen un pequea diferencia en relacin a los de


Ruby, en estos los accesos con string o con smbolos son
exactamente iguales, o sea podemos obtener el valor de q usando
params[:q] o params["q"]

Entonces si queremos mostrar slo el valor del email enviado lo


podemos hacer con <%= params[:q]%>

Ahora sacaremos <%= params %> de la vista por que no tiene


sentido mostrar los parmetros al usuario.

Guardando los datos


Ahora tenemos que lidiar con la base de datos, puesto que para
guardar los datos la necesitamos, Ruby on Rails viene por defecto
funcionando con SQLite3 y para este proyecto ser suciente.

Para crear una tabla de datos y guardar los usuarios necesitamos


crear un modelo, pues en el patrn MVC cada modelo mapea los

datos a su tabla de la base de datos. Entonces si creamos el


modelo llamado usuario, se crear una tabla en la base de datos
llamada usuarios y el modelo nos ayudar a guardar los datos sin
tener que usar comandos SQL.

Se recomienda escribir el cdigo en ingls, el inector de


Rails viene congurado por defecto en ingls y por lo
tanto puede haber un error en el nombramiento de las
tablas al intenta pluralizar bajo las reglas del espaol.

Creando el primer modelo


Podemos crear el modelo de usuarios desde la terminal con un
generador.

rails g model user email

Donde user es el nombre del modelo y email un campo que tendr


la tabla.

A diferencia de los controllers, la convencin dicta nombrar los


modelos en singular, pues esta clase mapea a un elemento (por
ejemplo un usuario) con sus datos en la tabla de la base de datos.

Al correr el generador obtendremos:

1
2
3
4
5
6

invoke
create
create
invoke
create
create

active_record
db/migrate/20151120195902_create_users.rb
app/models/user.rb
test_unit
test/models/user_test.rb
test/fixtures/users.yml

De este modo se genera el modelo User, una migracin para crear


la tabla users en base de datos y tests.

Corriendo las migraciones


Para modicar la base de datos wn Rails hay que utilizar una
migracin, esto es una secuencia de instrucciones que llevan la
base de datos de un estado a otro. Las migraciones son un tema
complejo que abordaremos profundamente en el libro, pero por
ahora slo necesitamos saber como correrlas. Para eso vamos a
utilizar el comando:

rake db:migrate

Obtendremos como resultado:

1
2
3
4

== 20151120195902 CreateUsers: migrating ==================================


-- create_table(:users)
-> 0.0037s
== 20151120195902 CreateUsers: migrated (0.0039s) ==========================

Despus de correr la migracin el siguiente paso es probar el


modelo.

Rails console
Cuando uno crea un modelo lo primero que hacemos es probarlo
en la consola de Rails. Podemos acceder a ella utilizando
rails

console

rails

c , es aqu desde donde

procesamos e insertamos contenido a la base de datos.

Creando un usuario
Cuando uno crea un modelo lo que se crea es una clase con ese
nombre, es decir dentro de Rails nosotros ahora podemos
instanciar un objeto user con:

user = User.new

O lo podemos instanciar directamente con un email:

1
2

user = User.new(email: 'gonzalo@desafiolatam.com')


=> #<User id: nil, email: "gonzalo@desafiolatam.com", created_at: nil, updated_at: nil>

Tambin podemos cambiar el email del usuario simplemente con:

user.email = "nuevoemail@desafiolatam.com"

Estos datos no persisten en la base de datos hasta que los


guardemos, eso lo hacemos con:

user.save

Si todo se realiz correctamente obtendremos:

1
2
3
4

(0.6ms) begin transaction


SQL (1.1ms) INSERT INTO "users" ("email", "created_at", "updated_at") VALUES (?, ?, ?
(0.8ms) commit transaction
=> true

Y ahora ya tenemos datos en nuestra base de datos.

Tambin podemos guardar un usuario directamente en la base de


datos sin instanciarlo, para esto utilizamos el mtodo .create

User.create(email: "creando_usuario_directamente@gmail.com")

Leyendo los usuarios desde la base de datos


Para ver todos los usuarios creados llamamos:

User.all

Esto gatillar una consulta SQL y nos devolver un array especial


llamado ActiveRecord::Relation pero el cul, a n de cuentas, es un
array.

1
2

User Load (0.3ms) SELECT "users".* FROM "users"


=> #<ActiveRecord::Relation [#<User id: 1, email: "gonzalo@desafiolatam.com", created_a

Como los resultados son una array podemos iterarlo con un .each
de tal manera que:

User.all.each {|u| puts u}

Obtendremos:

1
2
3
4

User Load (0.2ms) SELECT "users".* FROM "users"


#<User:0x007fd6332ee5d0>
#<User:0x007fd6332e6a38>
[#<User id: 1, email: "gonzalo@desafiolatam.com", created_at: "2015-11-20 20:26:21", upd

El array al nal se obtiene porque el mtodo .each al terminar de


iterar devuelve el arreglo original.

Para salir de rails console tenemos que escribir:

exit

Guardando los usuarios dentro de Rails


Ahora que sabemos lo bsico del ActiveRecord podemos guardar el
usuario cuando se enve un formulario.

Para eso tenemos que ir al controller de landings, y detectar si se


enviaron parmetros en el formulario. Si se hicieron bien los
guardamos en la base de datos.

1
2
3
4
5
6
7

class LandingsController < ApplicationController


def index
unless params[:q].blank?
User.create(email: params[:q])
end
end
end

El mtodo .blank? revisa si el parmetro q es nulo o vaco, es decir,


si hay un valor en el formulario que sea distinto de nulo o vaco,
guardaremos al usuario con su email en la base de datos.

Cuando se trabaja en la parte de datos, el servidor de Rails


( rails s ) siempre tiene datos tiles. Para ver si funcion
podemos revisarlo y veremos:

1
2
3
4
5
6
7
8
9

Started GET "/landings/index?q=gonzalo%40desafiolatam.com"


for ::1 at 2015-11-20 14:45:46 -0600
Processing by LandingsController#index as HTML
Parameters: {"q"=>"gonzalo@desafiolatam.com"}
(0.5ms) begin transaction
SQL (0.7ms) INSERT INTO "users" ("email", "created_at", "updated_at") VALUES
(0.6ms) commit transaction
Rendered landings/index.html.erb within layouts/application (0.8ms)
Completed 200 OK in 139ms (Views: 98.5ms | ActiveRecord: 1.9ms)

Esto nos dice que se llam a la pgina con el parmetro


q="gonzalo%40desafiolatam.com"

y luego se insert en la

base de datos, lo que implica que est todo OK.

En estos logs debemos tener especial cuidado cuando despus de


la insercin aparezca rollback transaction en lugar de commit
transaction, eso es un indicador de que la operacin fall.

Cambiando la pgina de inicio


Es posible convertir a cualquier pgina en la pgina de inicio, para
eso hay que abrir el archivo

config/routes.rb

y agregar la

lnea root 'controller#action' . En nuestro caso sera:

root 'landings#index'

Luego podemos vericar que haya funcionando accediendo con


nuestro navegador a localhost:3000

Tambin podemos observar que al hacer rake routes aparecer


este cambio:

1
2

landings_index GET
root GET

/landings/index(.:format)
/

landings#index
landings#index

Desafo
Crear un formulario funcional en la plantilla que creamos en el
captulo anterior.

Preguntas
1. Cul es la funcin del modelos en el patrn MVC de Ruby on Rails?
2. Que contiene el hash params?
3. Cul es la relacin entre el modelo y una tabla en la base de datos?
4. Qu son las migraciones?
5. Cmo corremos una migracin?
6. Cmo podemos probar que una migracin haya sido exitosa?
7. Cmo instanciamos un usuario nuevo?
8. Cmo instanciamos un usuario que tenga email?
9. Cmo guardamos un usuario con su email en la base de datos?
10. Cmo podemos guardar un usuario sin instanciarlo?
11. Cmo podemos ver todos los usuarios creados?
12. Cmo podemos mostrar todos los usuarios que hay en la base de datos?
13. Cmo podemos entrar a la consola de Rails?
14. Cmo podemos salir de la consola de Rails?
15. Qu hace el mtodo .blank?
16. Qu implica que aparezca rollback transaction en los logs?
17. Cmo podemos cambiar la pgina de inicio en Rails?

9) Formulario 2.0
Objetivos
1. Introducir el concepto de post
2. Introducir el concepto de MVC

Creando un proyecto nuevo


Antes de trabajar en este proyecto vamos a hacer una copia del
anterior, (si tienes experiencia con GIT puedes hacer un branch).
Para eso, en una carpeta anterior a la del proyecto podemos hacer
en terminal:

cp -r nombre_proyecto1 nombre_proyecto2

Donde nombre_proyecto1 es el nombre original del proyecto y


nombre_proyecto2 es el nuevo.

Una vez dentro y luego de abrir

rails console

podemos

conrmar que tenemos los mismos datos, es decir, se copiaron los


usuarios creados en el proyecto anterior. Esto no se debe a que
ambos utilicen las misma base de datos, sino que sqlite3 es un
archivo que est dentro de la carpeta db y nosotros copiamos, por
lo tanto trajimos todos los cambios al proyecto nuevo.

Podemos comprobar que esto es verdad agregando datos en uno


de los proyectos y luego revisando en el otro. Veremos que slo

perssten en uno de ellos, lo que no ocurrir ms adelante cuando


trabajemos con PostgreSQL u otro motor de base de datos.

Otro detalle con el que hay que tener cuidado es que no podemos
tener corriendo dos servidores de Rails en el mismo puerto; y
como el puerto por defecto es el 3000, debemos bajar el servidor
de Rails del proyecto anterior con

ctrl+c

antes de abrir el

nuevo. Y obviamente debemos abrir la carpeta correcta en el


editor. Entonces antes de seguir:

Copiaste los archivos a la carpeta nueva?


Cerraste el proyecto en el editor y abriste el nuevo?
Bajaste el servidor del proyecto anterior y abriste el nuevo?

Enviando un formulario por POST


Los formularios pueden ser enviados por diversos mtodos, los
ms comunes son GET y POST.

Cuando se enva un formulario por GET los campos aparecen en la


URL, esto es muy til si se quiere mostrar y poder compartir los
resultados, por ejemplo si buscamos algo en google y queremos
compartir los resultados de la bsqueda lo podemos hacer
copiando la url y envindosela a alguien, pero cuando se quiere
enviar informacin sensible y no dejar un registro de eso en la URL
hay que ocupar el mtodo POST

Para enviar un formulario por post tenemos que indicar en el


mtodo que es POST.

1
2
3
4

<form method="POST">
<input name="q">
<input type="submit" value="Enviar">
</form>

Si ahora probamos el formulario veremos un error que ya hemos


visto anteriormente.

No route matches [POST] "/landings/index"

Hacer un match a un ruta requiere tanto de la url como del


mtodo, pues no es lo mismo hacer un get a una pgina que un
post. Para probarlo podemos hacer rake routes :

1
2
3

Prefix Verb URI Pattern


Controller#Action
landings_index GET /landings/index(.:format) landings#index
root GET /
landings#index

Slo tenemos un acceso GET a

/landings/index

y ningn

acceso post.

Para crear una ruta nueva debemos abrir el archivo de rutas en


config/routes.rb

MVC in a nutshell
Pero Por qu necesitamos una URL nueva? si antes funcionaba
con una sola

El concepto para entender bien MVC es que una URL equivale a


una accin y una nueva accin requiere una nueva URL. Entonces
para agregar procesamiento al formulario debemos agregar una
ruta nueva que nos llevar a un controller y un mtodo especco,
desde donde manejaremos la lgica del formulario.

Si bien un if dentro del controller nos permitira manejar mltiples


acciones, debemos recordar siempre el concepto KISS (Keep It

Simple and Stupid). Entonces en lugar de distinguir entre una


variedad de parmetros dentro del if, sencillamente se recomienda
separar ambas acciones; una para mostrar el formulario y una
para procesarlo.

Otra ventaja impensada para estos casos: podemos tener varios


landings distintos sin necesidad de manejar estos formularios de
registro en ms de una base de datos. De esta forma al tener
acciones separadas evitamos reescribir cdigo.

La ruta nueva puede ser por POST o por GET, la diferencia es que
por GET los parmetros se envan a travs de la URL, en cambio
POST se pasa en los headers del request. Esto lo estudiaremos con
ms profundidad en el captulo de negociacin de contenido.

Ahora agregaremos la ruta al mtodo nuevo dentro del archivo


routes.

post 'pages/receive'

Para

conrmar

correremos

el

que

la

comando

ruta

fue

rake

agregada
routes

correctamente

en

la

consola.

Deberamos obtener como resultado:

1
2
3
4

Prefix Verb URI Pattern


Controller#Action
landings_index GET /landings/index(.:format)
landings#index
landings_receive POST /landings/receive(.:format) landings#receive
root GET /
landings#index

No podemos probar la ruta nueva en el navegador, ya que eso


sera acceder por GET; pero podemos probarla utilizando el
formulario. Para eso vamos a necesitar el prex.

Ya habamos mencionado que podemos crear un link a cualquier


pgina ocupando el prex + _path o prex + _url. El primero

corresponde a una ruta relativa y para poder dirigir a una pgina


distinta en un formulario tenemos que ocupar el atributo action

Form y action
1
2
3
4
5

<form method="POST" action="<%= landings_receive_path %>" class="form">


<label for="email">Email</label>
<input name="email">
<input type="submit" value="Subscribirse">
</form>

El mtodo form_tag
Otra forma de hacer exactamente lo mismo es utilizando el
mtodo form_tag de Ruby. No es mucho el cdigo que se ahorra
en los casos sencillos, pero no est de sobra saberlo.

1
2
3
4

<%= form_tag landings_receive_path, method: :post, class: 'form' %>


<%= label_tag "email", "Email" %>
<%= email_field_tag "email" %>
<%= submit_tag "Subscribirse" %>

Si ahora intentamos probar el formulario, obtendremos el


siguiente error:

1
2

Unknown action
The action 'receive' could not be found for LandingsController

La razn de este error es porque todava no hemos creado el


mtodo de receive, es lo que haremos a continuacin.

Para

eso

vamos

abrir

el

controller

/app/controllers/landings_controller.rb

en el editor

de texto, dentro del archivo crearemos el nuevo mtodo.

Mostrando los resultados del formulario


1
2
3
4
5
6
7
8
9

class PagesController < ApplicationController


def index
end
def receive
render json: params
end
end

Cada mtodo muestra por defecto una vista del mismo nombre,
por

ejemplo

index

carga

la

vista

dentro

de

views/landings/index.html.erb pero hay una excepcin. Si


se utiliza la instruccin render o redirect_to, este cambia el
comportamiento y en lugar de mostrar una vista (o un error,
porque todava no hemos creado la vista) nos ayudara a mostrar
los parmetros del formulario recibido.

1
2
3
4
5
6
7
8

utf8: "",
authenticity_token: "9WFUTrwJgKSCKIKA4lQPsHted6TRWmERCJTY65u+Ix94dFLxPdfY2XzFETBSEVD
email: "gonzalo@desafiolatam.com",
commit: "Subscribirse",
controller: "landings",
action: "receive"
}

Guardando los resultados


Si estamos enviando los datos de forma correcta el siguiente paso
es guardarlo. Habiendo creado el modelo, debemos mover la
lgica de negocios que tenamos en index hacia receive, de tal
modo que:

1
2

class LandingsController < ApplicationController


def index

3
4
5
6
7
8
9
10

end
def receive
User.create(email: params[:email])
render json: params
end
end

Se deben manejar los errores en caso de insercin, esto lo


estudiaremos en un captulo futuro.

Redirect_to
En lugar de mostrar los resultados del formulario despus de
crearlo, sera recomendable redirigir al usuario al landing (o a
cualquier otra pgina) y mostrarle que sus datos fueron guardados
con xito. Para esto utilizaremos el mtodo redirect_to, que recibe
dos parmetros: el primero es la URL hacia donde se redirigir al
usuario y el segundo permite enviar mensajes ash. Los mensajes
ash son variables que viven durante un slo request, es decir, se
muestran a la prxima carga de pgina, ideales para avisos de lo
lograste o que tal cosa fall.

1
2
3
4
5
6
7
8
9
10

class PagesController < ApplicationController


def index
end
def receive
@user = User.new(email: params[:email])
@user.save
redirect_to root_path, notice: "Te has registrado"
end
end

Variables flashs

Nos falta una mejora, pues siempre existe una posibilidad de que
la operacin de guardado pueda fallar. En este caso es muy difcil
que suceda dada la simplicidad de la operacin, pero siempre
debemos manejar y reportar los casos de fallar los procesos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class PagesController < ApplicationController


def index
end
def receive
@user = User.new(email: params[:email])
if @user.save
redirect_to root_path,
notice: "Te has registrado"
else
redirect_to root_path,
alert: "No hemos podido registrarte, intntalo de nuevo"
end
end
end

No basta con crear variables ash, tenemos que mostrarlas. Para


eso

entraremos

la

vista

maestra

en

layouts/application.html.erb .

Desde el punto de vista de usabilidad, es una buena prctica escibir estas alertas bajo
la barra de navegacin. Ponerlas arriba de la barra har que se mueva y rompa toda la
estructura, mostrarlas muy abajo imperdir que el usuario pueda verlas.

1
2
3
4
5

<% if flash[:notice] %>


<p style="color:green"> <%= flash[:notice] %> </p>
<% elsif flash[:alert] %>
<p style="color:red"> <%= flash[:alert] %> </p>
<% end %>

Mostrando todos los usuarios en la base de datos

Para eso vamos crear una ruta nueva que nos permita rescatar
todos los emails ingresados en la base de datos. Entonces
agregamos la ruta al archivo en

config/routes.rb

y la

llamaremos get_leads.

get 'landings/get_leads'

Dentro del controller agregaremos el mtodo get_leads y desde ah


obtendremos todos los leads.

1
2
3
4

def get_leads
@users = User.all
render json:@leads
end

Podemos

probar

entrando

http://localhost:3000/landings/get_leads

User.all devuelve todos las las de la tabla leads en algo semejante


a un array (no es exactamente un array, pero eso lo estudiaremos
ms adelante). Este resultado lo vamos a guardar dentro de una
variable de instancia, que nos permitir mostrar los resultados en
una vista, pero que por ahora nos limitaremos a mostrar como si
fuera un archivo JSON.

Ahora para terminar este proyecto haremos la vista de get_leads,


Entonces necesitamos crear el archivo
dentro de

get_leads.html.erb

app/views/pages , pero primero tenemos que

decirle al controller que ocupe la vista y para eso removeremos el


render.

1
2
3

def get_leads
@users = User.all
end

Para mostrar los resultados escribiremos una iteracin sobre todos


los leads obtenidos (los users son un array) y por cada uno de ellos
mostraremos el email.

1
2
3
4
5

<ul>
<% @users.each do |u| %>
<li> <%= u.email %> </li>
<% end %>
</ul>

Ms adelante en este libro estudiaremos el concepto de autenticacin y control de


accesos (para dar acceso restringido a estas secciones exclusivamente a algunos
usuarios).

Desafo
Modicar el formulario de la plantilla que hicimos previamente
para incorporar un formulario que funcione por posts. El usuario
debe ser noticado cuando ingres sus datos y se debe crear una
pgina que permita vericar todos los usuarios ingresados.

Preguntas
1. Cul es la diferencia entre enviar datos por get o por post?
2. Complete la oracin: una accin nueva requiere de una
3. Para qu sirve el atributo action de los formularios?
4. Cul es la diferencia entre utilizar el form de HTML y la etiqueta form_tag
de Rails?
5. Que hace redirect_to?
6. Cul es la diferencia entre redirect_to y render?
7. Qu hace render json: params?
8. Qu son las variables ash?
9. Cul es la diferencia entre utilizar <%= @users.each do |u| %> y <%
@users.each do |u| %>?

10) El Gemfile
Objetivos
1. Aprender a instalar dependencias en un proyecto Rails.
2. Manejar las versiones de las dependencias ocupando el gemle.
3. Aprender a limitar las gemas a entornos especcos.

El Gemle es un archivo que contiene todas las dependencias de


un proyecto en Rails. Cada dependencia es una biblioteca que se
conoce como gema. Es en este archivo donde especicamos qu
gemas, con sus respectivas versiones, se deben instalar para poder
correr el proyecto.

Bundler es un programa que es capaz de leer este archivo e


instalar todas las dependencias. Para correr el programa
simplemente debemos ejecutar bundle .

Ni Bundler ni el Gemle son exclusivos de Rails, sirven para controlar dependencias de


cualquier proyecto en Ruby.

El Gemle de un proyecto en Rails comienza as:

1
2
3
4
5

source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.7'
# Use sqlite3 as the database for Active Record

gem 'sqlite3'

La primera lnea seala desde donde se buscan las gemas (por


defecto vienen desde rubygems.org). Luego se listan todas las
gemas instaladas bajo la sintaxis gem, el nombre de la gema entre
comillas simples y en algunos casos la versin como parmetro
opcional.

Al correr bundle se generar un archivo llamado Gemle.lock. Este


archivo contiene todas las gemas y dependencias (otras gemas que
necesita para funcionar) de un proyecto, y no se debe manupular.

Tanto el Gemle como Gemle.lock deben ser aadidos al


repositorio, pues le permiten a otros instalar las mismas
bibliotecas de tu proyecto, con las versiones especicadas en una
sola lnea de comando.

Podemos especicar una versin en el Gemle con un segundo


parmetro.

gem 'rails', '3.0.0.beta3'

Se puede pedir que una gema sea mayor que una versin con:

gem 'rack',

'>=1.0'

Se puede pedir que la versin a instalar est dentro de una versin


menor de la gema (en otras palabras, que el cambio sea slo en el
segundo decimal):

gem 'thin',

'~>1.1'

Las gemas tambin se pueden instalar exclusivamente para


entornos especcos. Existen dos modos de hacer esto.

1) Agregando el group a la gema:

1
2

gem 'sqlite3', group: :development


gem 'pg', group: :production

2) Agregando la gema dentro de un bloque con el nombre del


grupo:

1
2
3

group :development do
gem 'sqlite3', group: :development
end

Si cambias una gema de grupo o la agregas a un grupo nuevo, recuerda siempre correr
bundle.

Preguntas
1. Para qu sirve el Gemle?
2. Cul es la diferencia entre el Gemle y el Gemle.lock?
3. Qu hace el comando bundle?
4. Qu signica que un cambio de versin sea menor?
5. Cmo se puede agregar una gema a un entorno especco?

Seccin II: Deployment

11) Deployment en Heroku


Objetivos
1. Conocer las diferentes alternativas que hay para hacer deployment de
una aplicacin.
2. Instalar y congurar las herramientas necesarias para hacer deployment
a Heroku.
3. Aprender a hacer deployment en Heroku.

Deployment es el proceso de subir la aplicacin en modo de


produccin (cuando ya est lista para atender usuarios reales). Es
el paso posterior al modo de desarrollo, el estado en el que
estamos trabajando en este momento.

Rails puede detectar el ambiente en que se encuentra y correr


conguraciones dependiendo de este. Por ejemplo en produccin
podramos tener funcionando Google Analytics, mientras que en
desarrollo no.

Adems de estar en produccin, una aplicacin requiere de dos


cosas para hacer deployment: un hosting y un dominio. No son lo
mismo y no hay que confundirse entre ellos.

El hosting es un computador donde la aplicacin estar alojada


(subida y corriendo), el dominio es el nombre (que terminar en el
.com, .cl, o cualquier otra extensin de preferencia).

Una pequea aclaracin: el dominio no es estrictamente necesario.

Si te entregan una IP ja (cuando pagas por un hosting usualmente


te dan una IP) puedes usar esa direccin para entrar a la
aplicacin. Sin embargo tus clientes o usuarios no van a recordar
ese nmero, as que es necesario tener un dominio.

Tipos de hosting
Hay varias posibilidades para subir una aplicacin:

Servidor propio
El un servidor que funciona en una mquina exclusiva, por lo que
el contrato de mantencin involucra no solo el servidor, sino de
una gran cantidad de costos asociados (temperatura, ventilacin,
mantencin de sistemas, etc). No compartes la direccin IP, pero
requiere de gran infraestructura o de warehousing (Data Center).
Es una alternativa bastante cara, as que a menos que dispongas
de los recursos o que sea un requisito del cliente (y est dispuesto
a pagar por ello) no es recomendable.

Hosting clsico
Es un hosting que uno arrienda y viene con Cpanel, por lo general
las opciones no son caras. Antes de arrendar uno recomendamos
revisar bien los trminos y condiciones, adems de asegurarse de
que tengan un buen servicio de soporte y compatibilidad con Rails
4 (muchos no lo tienen).

Un hosting clsico puede costar desde 10 dlares al ao y


generalmente cuestan al rededor de 20 dlares mensuales. Dan
poco RAM, el setup que ofrecen no est optimizado para Rails,
usualmente vienen con Apache y MySQL, pero NginX tiene mejor
rendimiento en cuanto a usuarios y costo.

Rails trabaja igual de bien con MySQL que con PostgreSQL, sin
embargo en un hosting clsico ests limitado a eso al setup que
trae y no puedes crear tu conguracin propia.

VPS
En un VPS te arriendan una mquina virtual. Existen dos tipos: o
arriendas un servidor normal (Linode, Digital Ocean, etc.) o uno
con escalamiento automtico, como Amazon.

El pro de los VPS es que obtienes un muy buen precio y una buena
mquina, al mismo tiempo que puedes realizar el setup que
quieras. El problema con los VPS es que tienes que realizar el setup
de forma manual y puede llegar a ser un bastante trabajo.

PAAS
Existen diversos sistemas PAAS, el ms famoso es Heroku de
Salesforce,

recomendables

tambin
porque

Engine
permiten

Yard.

Estos

levantar

sistemas

rpidamente

son
tu

aplicacin, y en particular Heroku porque tiene planes gratuitos


que son sucientes para subir tu primer prototipo y mostrrselo a
los primeros clientes.

Por lo mismo en este captulo nos enfocaremos en subir la


aplicacin a Heroku.

Instalando el Toolbelt de Heroku


Primero debemos crearnos una cuenta en www.heroku.com y
luego descargar el Toolbelt, esto nos permitir escribir comandos
para Heroku desde nuestro terminal.

El link al Toolbelt debera aparecer despus de crear la cuenta,


pero de todas formas puedes descargar el Toolbelt directamente
desde toolbelt.heroku.com.

Luego abriremos una nueva terminal y dentro de ella escribiremos:

heroku login

Claves SSH
Heroku intentar agregar automticamente nuestras claves ssh.
Para eso supondr que tu juego de claves se llama id_rsa, y de no
tenerlas al momento de instalarlo deberemos crearlas y luego
agregarlas. Entonces necesitamos ir a la carpeta .ssh dentro de tu
carpeta personal y crear un nuevo juego de claves.

Revisamos si existe el juego de claves:

ls ~/.ssh

A continuacin las crearemos, slo si no existen.

ssh-keygen -t rsa

luego podemos agregar las claves SSH automticamente a Heroku


con:

heroku keys:add

Tambin es posible copiar y pegar el contenido de la clave ssh


pblica (id_rsa.pub) a travs de la interfaz de Heroku, en la seccin
de conguracin de cuenta.

Repositiorio Git
Configurando Git
1
2

git config --global user.name "YOUR NAME"


git config --global user.email "YOUR EMAIL ADDRESS"

Creando el repositorio Git


Primero debemos estar trabajando sobre un repositorio Git. De no
ser as, lo crearemos con:

git init

Subiendo la aplicacin a Heroku


Creando el proyecto en Heroku
El primer paso es crear un proyecto en Heroku. Se puede hacer
inmediatamente a travs del panel de control, pero es mucho ms
sencillo realizarlo con bash en la carpeta del proyecto; los nicos
requisitos para que este mtodo funcione son tener Git
congurado y el Toolbelt de Heroku instalado.

Entonces, sobre un proyecto con su repositorio Git inicializado,


crearemos el proyecto en Heroku con el comando:

heroku create

Obtendremos algo similar a lo siguiente:

1
2
3

Creating fierce-inlet-1619... done, stack is cedar-14


https://fierce-inlet-1619.herokuapp.com/ | https://git.heroku.com/fierce-inlet-1619.git
Git remote heroku added

El subdominio siempre ser un nombre distinto al azar.

Setup del proyecto para deployment


Ahora debemos cambiar el Gemle de nuestro proyecto. Primero
hay que remover la lnea que dice sqlite3 del gemle (puesto que
Heroku no la soporta), luego hay que agregar las gemas de
postgreSQL y de rails_12factor, esta ltima permite obtener los
logs y arreglar problemas de assets en produccin. Al nal de todo
este proceso hay que guardar todo en un commit.

1
2
3

gem 'sqlite3', group: :development


gem 'pg', group: :production
gem 'rails_12factor', group: :production

Al igual que cada vez que hacemos cambios en el archivo Gemle,


debemos correr el comando para actualizar nuestras gemas. En el
caso de no agregar las gemas, no correr el bundle, o no hacer el
commit obtendremos el siguiente error:

1
2
3
4
5
6

remote:
remote:
remote:
remote:
remote:
remote:

Gem files will remain installed in /tmp/build_f0e176e4ab15d0046ef1566d12b


Results logged to /tmp/build_f0e176e4ab15d0046ef1566d12b46eb5/vendor/bund
An error occurred while installing sqlite3 (1.3.11), and Bundler cannot
continue.
Make sure that `gem install sqlite3 -v '1.3.11'` succeeds before bundling
!

7
8
9
10
11
12
13

remote:
remote:
remote:
remote:
remote:
remote:
remote:

!
!
!
!
!

Failed to install gems via Bundler.

Push rejected, failed to compile Ruby app

Detected sqlite3 gem which is not supported on Heroku.


https://devcenter.heroku.com/articles/sqlite3

Esto se debe a que en Heroku no se puede instalar sqlite.

Opcionalmente tambin podemos especicar la versin de Ruby que queremos ocupar


en Heroku. Si no la declaramos, se utilizar la versin 2.0 (que viene por defecto).

Para precisar la versin de Ruby que queremos debemos escribirla


dentro del Gemle, de tal modo que:

1
2
3
4
5

source 'https://rubygems.org'
ruby "2.3.1"

bundle

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'


gem 'rails', '4.2.7'

Luego estos cambios deben ser incorporados en el control de


cambios de Git.

1
2

git add Gemfile


git add gemfile.lock

Subiendo los cambios a produccin


Primero debemos guardar los cambios realizados en Git con:

git commit -m "cambios hechos"

Dentro de las comillas debes especicar los cambios que has


realizado (se recomienda ser preciso). Luego solo queda enviar los
cambios a produccin. Esto es lo que se conoce como hacer
deployment.

git push heroku master

Razones por las que el paso de deployment puede fallar:


No guardaste el Gemle despus de las modicaciones.
No corriste la lnea en bash bundle.
No agregaste a Git el Gemle (y el Gemle.lock) despus del
bundle.
Tienes un error en los assets.

Revisando errores
Si tienes un error en los assets, tenemos que encontrar el error y
corregirlo. El problema ms probable en esta seccin es tener
algn archivo con una extensin errada, por ejemplo Algo.csc (en
lugar de CSS).

Para debuggear esto podemos correr la lnea:

rake assets:precompile RAILS_ENV=Production

No es bueno agregar estos assets al Git, pues como vimos


previamente esto correr los preprocesadores sobre los archivos
respectivos, luego concatenar los CSS y JS, despus los minicar
y nalmente les aadir un ngerprint. Los assets nales quedarn
dentro de la carpeta public.

Despus debemos borrarlos dentro de la misma carpeta. No es


una buena prctica agregarlos a Git.

Sobre la precompilacin de assets


La precompilacin de assets es un proceso por el cual suceden las
siguientes cosas:

Migrando la base de datos de produccin


Recuerda migrar la base de datos de produccin cada vez que
migres la tuya.

heroku run rake db:migrate

Cambiando el nombre de la aplicacin en Heroku


Si quieres cambiar el nombre de tu direccin entregada por
Heroku debes escribir:

heroku apps:rename nombrenuevo

Desafo
Subir al aplicacin que creaste en el captulo anterior a Heroku. El
formulario debe quedar funcionando.

Preguntas
1. Para qu sirve Heroku?
2. Cul es la funcin del Toolbelt?
3. Con qu comando podemos crear una aplicacin nueva en Heroku?
4. Qu motor de base de datos ocupa Heroku por defecto?
5. Qu cuidados debemos tener en el Gemle antes de hacer un push a
Heroku?
6. Qu es el entorno de produccin?
7. Cmo podemos especicar la versin de Ruby a utilizar?

12) Configurando el dominio (o subdominio)


Una URL (Uniform Resource Locator) tiene la siguiente estructura:
http(s)://subdominio.dominio.dominio nivel superior

Para entenderlo bien vamos a revisarlo en orden de importancia, o


sea de derecha a izquierda.

Un dominio es, escencialmente, el nombre que uno compra para


representar su aplicacin dentro de internet. Los dominios de nivel
superior (extensiones como .com, .cl, .mx) son entregados a
diversas autoridades a lo largo del mundo y estas autoridades se
dedican a administrar y vender estos dominios. Otras compaas
como GoDaddy venden de mltiples tipos.

El valor de un dominio depende de varios factores, principalmente


de que tan solicitado es. Hoy en da es casi imposible tomar un
dominio de 4 letras, mucho menos uno que tenga sentido.
Dominios de este tipo se transan por millones de dlares.

Otros dominios pueden costar desde 5 dlares con un cupn de


descuentos. Fatwallet es una buena pgina para buscar cupones.

Una vez que tienes un dominio puedes proceder a congurarlo y


agregarle todos los subdominios que desees.

Entonces qu es el subdominio? Es lo que antecede al dominio. El


ms famoso de todos es www, pero puedes congurar el que
quieras.

Una vez comprado el dominio corresponde congurarlo para


realizar un redireccionamiento tipo Cname. Algunos sistemas de
los administradores no lo permiten, por ejemplo s se puede en
GoDaddy pero Nic Chile no lo acepta (ms adelante veremos como

resolver este problemas).

En la direccin de redireccionamiento tenemos que ingresar el


nombre de la aplicacin.

Luego en la lnea de comando, dentro de la carpeta de la


aplicacin, debemos agregar:

heroku domains:add nombre_dominio

Si quieres agregar el www debes agregar tambin:

heroku domains:add www.nombre_dominio

Si la conguracin es slo para un subdominio es posible:

heroku domains:add nombre_subdominio.nombre_dominio

Para probarlo simplemente debes entrar a la pgina congurada.


Recuerda que la propagacin de nombres es lenta y por lo tanto
puede demorar de 1 a 48 horas (aunque normalmente es una
hora).

Configurando dominios cuyas autoridades no soportan


redireccionamiento tipo CName
Esto se puede lograr instalando un plugin de Heroku. Hay dos
buenas alternativas para realizar esto (pues tcnicamente son lo
mismo): PointDNS y Zerigo. Ambos tienen planes gratuitos, pero
hay que tener en mente que son empresas tecnolgicas y esa

situacin podra cambiar en el futuro. Ahora bien Heroku es una


empresa de buenas prcticas y no realiza cobros indebidos o sin
previo aviso.

La conguracin es sencilla:

1. Debes instalar el plugin desde el dashboard de Heroku.


2. Luego entraa a la pgina del plugin y busca los DNS.
3. Utiliza los DNS encontrados para ponerlos en la pgina de la
autoridad.
4. Debes congurar la pgina del plugin para que apunte a la
aplicacin con el nombre respectivo.
5. Igual que cuando congurabas el .com tienes que utilizar
Heroku domains:add nombre_dominio.cl

Algunos plugins dentro de Heroku (en sus versiones


gratuitas) no permiten ms de un redireccionamineto.

Tips de mantencin
Entrar a la consola del proyecto en produccin
1

heroku run rake console

Ver los ltimos logs


Muy til cuando tienes errores en la versin remota, pero en la
local funciona bien.

heroku logs

Dejar una consola con los logs corriendo


1

heroku logs -t

Descargando la base de datos de Heroku


Es posible descargar la base de datos de Heroku a tu computador y
utilizarla como respaldo o mejor an, utilizarla para trabajar con
datos reales sin el miedo de destruir datos importantes. El
requisito es tener instalado postgres en el computador, puesto que
ese es el sistema de bases de datos que ocupa Heroku.

Paso 1: Realizar una copia de la base de datos en Amazon.

heroku pg:backups capture

Paso 2: Recuperar la base de datos desde Amazon (es el lugar


donde se guarda).

curl -o latest.dump `heroku pg:backups public-url`

Esto descargar un archivo que se llama latest.dump en la carpeta


desde donde hayas escrito la lnea de comandos.

Paso 3: cargar la base de datos descargada a tu sistema.

pg_restore --verbose --clean --no-acl --no-owner -h localhost -d nombreDeLaBaseDeDatos l

Desafo
Comprar un dominio .com y congurar la aplicacin creada en el
captulo anterior para que quede funcionando con el dominio o
con un subdominio.

Preguntas
1. Cul es la diferencia entre un dominio de nivel superior y un dominio?
2. Qu es y para qu sirven los subdominios?
3. Dnde se compra un dominio.com?
4. Qu conguracin debemos hacer para congurar nuestro .com?
5. Qu signica DNS?
6. Qu tipo de redireccin no soporta Heroku?
7. Qu podemos hacer para congurar un dominio cuyo servidor de
nombre no es compatible con los redireccionamientos de Heroku?
8. En qu consiste el modo de mantencin de Heroku?
9. Cmo podemos ver los logs de Heroku?

Seccin III: SQL y modelado de bases de datos

13) SQL
Objetivos
1. Aprender a crear usuarios, bases de datos y tablas con PostgreSQL
2. Poder ingresar, actualizar y remover valores de tablas utilizando SQL

Qu es SQL?
SQL es un lenguaje que permite realizar consultas de bases de
datos, es del tipo declarativo porque uno en lugar de especicar el
como obtener los resultados uno simplemente pide lo que
necesita y SQL lo devuelve.

Existen diversas implementaciones de SQL, siendo las ms


famosas MySQL, PostgreSQL y Oracle, hoy en da MySQL le
pertenece a Oracle y a pesar de que algunas implementaciones
siguen bajo licencia GPL tambin ha incorporado diversas licencias
comerciales lo que empuj a diversos miembros de la comunidad
a moverse a PostgreSQL, ahora la principal razn para utilizar
PostgreSQL no es el tema de las licencias si no el soporte de
diversos tipos de datos nativos que funcionan muy bien con Ruby
on Rails.

PostgreSQL
Rails es un framework agnstico a la base de datos, esto quiere
decir que puede ser congurado con cualquiera de ella mientras

existan los drivers, y existen drivers para todas las bases de datos
conocidas.

Al da de hoy PostgreSQL es la mejor opcin para trabajar con Rails,


primero porque viene congurado con Heroku y homologar los
entornos de desarrollo con el de produccin facilita el desarrollo y
pruebas del software, pero adems Postgres incluye diversas
funcionales como manejo nativos de array y de hashs que
abordaremos ms adelante en este libro.

Instalando PostgreSQL en OSX


Para instalar PostgreSQL podemos descargar la aplicacin
PostgreAPP esta automticamente crear un usuario con el mismo
nombre de usuario del sistema.

Esta forma es fcil de prender y apagar y nos aseguramos de que


el servidor no est corriendo de fondo gastando recursos en
nuestro computador.

Instalando PostgreSQL en Linux


Podemos instalar PostgreSQL en linux utilizando apt-get

apt-get install postgre sql-9.5

Tambin es posible instalar la interfaz grca de pgadmin


http://www.pgadmin.org/

Entrando a PSQL

PostgreSQL

es

un

servicio

que

corre

dentro

de

nuestro

computador, para poder entrar a este servicio lo haremos con el


comando:

psql

En caso de no estar corriendo el siguiente error:

1
2
3

psql: could not connect to server: No such file or directory


Is the server running locally and accepting
connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

En caso de que el servicio no est levantado podemos levantarlo


con:

sudo service nginx restart

Si todava no puedes entrar a postgres con el comando psql


despus de levantar el servicio o si quieres saber ms de como
funciona PostgreSQL pasa al siguiente captulo.

Problemas tpicos de Configuracin de PostgreSQL


El archivo ms frecuente en causa de errores de conguracin de
Postgres es uno llamado pg_hba.conf , este es el PostgreSQL
Client Authentication Conguration File o sea el archivo que
congura los accesos.

El pg_hba.conf puede estar en distintas carpetas segn la

distribucin del sistema operativo, para buscarlo haremos:

find / -name "pg_hba.conf"

Dentro

del

archivo

encontraremos

varias

conguraciones

comentadas, o sea que no aplican, y encontraremos una muy


importante.

local

all

all

trust

Esta se reere a las conexiones locales por socket, si hay un


problema de acceso debido al password lo que debe hacerse es
cambiar esta conguracin de peer a trust.

Luego para realizar los cambios de la conguracin podemos


correr el comando

pg_ctl

reload

o reinciar nuestro

computador.

El otro posible problema que impide que entremos en algunos


sistemas es que PostgreSQL se encuentre corriendo en un puerto
distinto al 5432, el cul es el puerto por defecto, dentro del archivo
postgresql.conf podemos congurar el puerto y cambiar el
valor.

El dilema del usuario y de la base de datos


Para entrar a psql necesitamos un usuario y una base de datos de
Postgres, si no especicamos quien es el usuario asumir que es
uno llamada igual que tu usuario en el sistema, si ese usuario no
existe nos mostrar error y no podemos entrar.

Para especicar el usuario podemos ejecutar

psql nombre_usuario

Si no tenemos ningn usuario congurado podemos entrar con el


de postgres

psql postgres

Nuestros Primeros pasos en SQL


Dentro de psql

Una curiosidad que tiene SQL es que es un lenguaje insensible a las maysculas, la
convencin consiste en escribir las palabras reservadas de SQL en maysculas, y los
nombres de las tablas y valores en minsculas.

Creando usuarios
Luego podemos crear un usuario con:

CREATE USER nombre_usuario;

Cambiando la clave
En postgres los usuarios y los roles son lo mismo, podemos
cambiar el password de un usuario con:

ALTER ROLE x WITH password 'xx';

Dando acceso de superusuario


1

ALTER ROLE nombre_usuario WITH superuser;

Listando a todos los usuarios creados en la base de datos.


Podemos listar a todos los usuarios para ver si los creamos
exitosamente.

gonzalosanchez=# \du

Role name

Attributes

Member of

desao_blog

Superuser

{}

super_gonzalo

Superuser

{}

admin

Superuser, Create role, Create DB, Replication

{}

Creando una base de datos


En SQL las tablas son archivos, archivos que contienen un solo
formato y se parecen en cierto sentido a un excel, slo que las
columnas son jas.

Cuando creamos una tabla denimos una estructura, cuando


insertamos datos no podemos salirnos de la estructura de la tabla
denida, sin embargo en el futuro podemos cambiar la estructura
de la tabla siempre y cuando especiquemos que hacer con los
datos que existen actualmente.

Una base de datos es un conjunto de tablas que pueden o no estar


relacionadas entre ellas.

Para crear una base de datos simplemente debemos utilizar el


comando

CREATE DATABASE nombre_base_de_datos;

Listando una base de datos.


Postgres al igual que otros motores de bases de datos maneja
mltiples bases de datos, y cada usuario del sistema.

Para listar todas las bases de datos lo podemos hacer con \l la


lista muestra las bases de datos, el dueo, la codicacin y los
privilegios asociados.

BD

Owner

Encoding

Privileges

TrainingAdmin_development

gonzalosanchez

UTF8

TrainingAdmin_test

gonzalosanchez

UTF8

blog_development

gonzalosanchez

UTF8

blog_test

gonzalosanchez

UTF8

blog_with_testing_development

gonzalosanchez

UTF8

blog_with_testing_test

gonzalosanchez

UTF8

blogbootcamp2_development

desaoblog

UTF8

blogbootcamp2_test

desaoblog

UTF8

clasepostgre

gonzalosanchez

UTF8

Botando una base de datos


Para poder borrar una base de datos existente utilizaremos el
comando

DROP DATABASE nombre_base_de_datos

Por lo mismo en lugar de borrar se dice botar una base de datos,


por el drop.

No hay vuelta atrs de este comando, por lo mismo hay


que utilizarlo con mucha responsabilidad y siempre
respaldar las bases de datos, especialmente antes de
hacer este tipo de operaciones.

Conectndose a una base de datos


Para poder ver los datos de las diversas tablas que hay dentro de
una base de datos necesitamos primero conectarnos a una, eso lo
podemos lograr con:

\c nombre_base_de_datos

Para poder conectarnos a una base de datos necesitamos estar


dentro de Postgres con un usuario que tenga permisos para poder
ver esa base de datos.

Manejo de tablas
Finalmente una vez dentro de la base de datos podemos hacer
queries a las tablas, para saber que tablas hay dentro de la base de
datos con:

\t

Para poder crear tablas, que son archivos con estructuras jas,
primero vamos a tener que entender un poco ms de estas
estructuras, y para eso hay que entender que cada columna tiene
un nombre y un tipo de dato.

Columna = Nombre + Tipo de dato

Introduccin a tipos de datos.


Los tipos de datos bsicos en Postgres 9.4 se dividen en diversas
categoras:

Numricos, Monetarios, de caractres, binarios, de fechas,


booleanos, de enumeracin, geomtricos e incluso existen tipos de
datos para manejar json, xml, hashs y arrays.

Los detalles exacto de los tipos de datos no los abordaremos en


este libro pero pueden ser consultados en la documentacin ocial
de

PostgreSQL.

http://www.postgresql.org/docs/9.4/static/datatype.html

Partamos creando una tabla con los tipos ms simple, integer y


varchar

integer, que permite guardar enteros de hasta 4 bytes, o sea


nmeros entre -2147483648 to +2147483647.
varchar(n) el cual permite almacenar strings de hasta n
caracteres.

Creando una tabla


La sintaxis bsica para crear tablas es la siguiente:

1
2

CREATE TABLE table_name(


column1 datatype,

3
4
5
6
7

column2 datatype,
column3 datatype,
.....
columnN datatype
);

Ahora con esto nosotros guardaremos datos en una tabla que


almacena personas con nombre y edad

1
2
3
4

CREATE TABLE personas(


name varchar(64),
age integer
);

EN PSQL y en otros sistemas SQL existen otros tipos de datos para manejar caracteres,
pero en PSQL a diferencia de los otros varchar y text son ms rpidos que char

Manipulando valores de una tabla


Insertando valores
Para insertar valores dentro de la tabla podemos hacerlo con el
comando INSERT, la sintaxis bsica es la siguiente:

INSERT INTO table_name VALUES ('valor_campo1', 'valor_campo2');

Para insertar valores en la tabla persona, insertamos un nombre y


una edad.

1
2

INSERT INTO personas VALUES ('Camila', '26');


INSERT INTO personas VALUES ('Gonzalo', '30');

Tambin es posible insertar valores especicando las columnas

INSERT INTO table_name (col1, col2) VALUES ('valor_campo1', 'valor_campo2')

Los valores no especicados sern nulos (siempre y cuando esto


no viole ninguna restriccin de la tabla)

En SQL hay ms estados que verdadero y falso. Por


ejemplo si una columna es boolean o sea que puede
contener verdadero o falso como valor tambin existe la
posibilidad que sea nulo y nulo es distinto de falso para
SQL

Sucede los mismo para chars o varchars donde una


columan puede tener contenido, estar vaca o estar nula y
son cosas distintas.

Leyendo valores de una tabla.


Para leer datos de una tabla la instruccin es SELECT, con SELECT
nosotros podemos especicar los valores que queremos o utilizar
* para indicar que queremos todos los campos de la tabla.

SELECT * FROM personas;

name
Camila

age
26

Gonzalo

30

(2 rows)

Si queremos slo un campo podemos indicarlo

SELECT name FROM personas;

name
Camila
Gonzalo

Actualizando valores de una tabla


Para cambiar valores ocuparemos la instruccin update

UPDATE personas SET age = 28;

Sorpresivamente obtendremos lo siguiente:

UPDATE 2, o sea se cambiaron 2 valores, si ahora mostramos todas


las personas con SELECT * FROM personas; veremos:

name

age

Camila

28

Gonzalo

28

Para cambiar slo una persona debemos acompaar el update de


la instruccin where

UPDATE personas SET age = 30 WHERE name = 'Gonzalo'

Debemos cuidar las comillas, estas tienen que ser comillas simples
cuando remplacemos valores por strings, en lugar pueden ir sin
comillas cuando los valores sean numricos.

UPDATE personas SET age = 31 WHERE age = 30;

Borrando datos de una tabla


Podemos borrar datos ocupando la instruccin delete, pero
debemos tener mucho cuidado de ocupar el where o borraremos
todos los datos.

DELETE FROM personas;

DELETE 2 << UPS

ahora para la siguiente prueba vamos a insertar nuevamente los


valores.

1
2
3

INSERT INTO personas VALUES ('Julian', '55');


INSERT INTO personas VALUES ('Fernanda', '19');
INSERT INTO personas VALUES ('Paulina', '20');

Podemos borrar bajo la condicin de igualdad, pero tambin


podemos borrar bajo otras condiciones, como en el siguiente
ejemplo:

DELETE FROM personas WHERE age < 25;

Modificando una tabla


Previamente dijimos que la estructura de una tabla es ja y no
puede ser cambiada, pero eso se reere en cuanto a que la
insercin de datos no puede salirse de la estructura de la tabla, sin
embargo en SQL tenemos una instruccin para cambiar el formato
de la tabla, y ese es ALTER .

Agregando una columna


1

ALTER TABLE personas ADD COLUMN id VARCHAR(16);

Removiendo una columna


1

ALTER TABLE personas DROP COLUMN id;

Constraints
Los constraints son reglas que creamos para cuidar la integridad
operacional de la base datos, o sea que los datos que tengamos
cumplan con las reglas del negocio, algunos son muy obvios como
por ejemplo que el precio o el stock de un producto no puedan ser
negativos, otros son exclusivos del negocios, por ejemplo tener x
productos comprados para poder obtener ciertos descuentos.

Creemos la tabla productos para probarlo:

1
2
3
4

CREATE TABLE products (


name VARCHAR(100),
price NUMERIC CHECK (price > 0)
);

Si

intentamos

ingresar

un

producto

con

precio

negativo

obtendremos:

ERROR: new row for relation products violates check constraint products_price_check

Evitando valores nulos


Al momento de crear la tabla, o de alterarla podemos establecer
que una columna no pueda tener valores nulos, esto es muy
conveniente para evitar errores de integridad de datos.

1
2
3
4

CREATE TABLE products2 (


name VARCHAR(100) NOT NULL,
price NUMERIC CHECK (price > 0)
);

Si intentamos insertar productos con:

INSERT INTO products2 VALUES (NULL, 100);

INSERT INTO products2 (price) VALUES (100);

ERROR: null value in column "name" violates not-null constraint


DETAIL: Failing row contains (null, 100).

Eso no evita que podamos ingresar valores vacos.

El constraint Unique
CREATE TABLE products3 (

1
2
3
4

1
2

name VARCHAR(100) UNIQUE,


price numeric CHECK (PRICE > 0)
);

INSERT INTO products3 (name, price) VALUES ('producto 1', 100);


INSERT INTO products3 (name, price) VALUES ('producto 1', 100);

Obtendremos primero un insert y luego un error de duplicated key,


puesto que el nombre debe ser nico.

La clave primaria
La clave primaria es una combinacin entre los constraints NOT
NULL y UNIQUE, pero adems es un ndice, que en este caso
permite encontrar de forma rpida los resultados, y adems te
asegura que sea nico los resultados, esto en la mayora de los
casos hace sentido, por ejemplo todos los autos tienen una
patente la cual es nica dentro de cada pas, y esta clave permite
encontrar todos los datos referentes a ese auto especco, en la
mayora de los pases existe un identicador asociado a las
personas, (RUT, RUN, RFC, n de seguridad social, etc..)

Para agregar una clave primaria tenemos que alterar la estructura


de la tabla puesto que vamos a agregar un ndice que en cierto
sentido es como agregar una columna.

ALTER TABLE personas ADD PRIMARY KEY (id);

Si hemos ido siguiendo la secuencia de este libro en este momento


obtendremos el siguiente error.

ERROR:

column "id" contains null values

El error dice que una columna con clave primaria no puede tener
valores null y eso tiene mucho sentido ya que este es un
identicador nico.

Por lo mismo ahora tenemos que darles id a todas las personas


que tengamos en la base de datos, si has seguido la secuencia
debera ser slo una persona.

SELECT * FROM personas;

name

age

Julian

55

id

UPDATE personas SET id=1 WHERE name='Julian';

y ahora si podremos cambiar la tabla personas.

ALTER TABLE personas ADD PRIMARY KEY (id);

Si todo est bien en lugar de error, obtendremos como output


alter table, pero ahora nunca ms podremos ingresar usuarios sin
id o con un id que est repetido.

Por ejemplo, si hacemos un insert ahora con el mismo id:

INSERT INTO personas VALUES ('Julian', 55, 1);

Obtendremos:

1
2

ERROR: duplicate key value violates unique constraint "personas_pkey"


DETAIL: Key (id)=(1) already exists.

Pero si podemos hacerlo si realizamos un INSERT con:

INSERT INTO personas VALUES ('Julian', 55, 2);

Creando una tabla con primary key


Es posible crear directamente una tabla con clave primaria, la
sintaxis es la siguiente:

1
2
3
4

create table ejemplo1 (


columna1 tipo primary key,
columna2 tipo
)

Ventajas de la clave primaria


La clave primaria es nica, nos permite buscar, actualizar los datos
y borrar los datos en base a este criterio.

Los valores de la clave primaria no pueden ser nulos, por lo que


siempre podremos buscarlos por ese criterio.

Acelera las bsquedas y los ordenamientos en base a este criterio.

Tablas con valores autoincrementales

En PSQL es posible crear columnas que se vayan llenando


automticamente, utilizando los tipos small serial, serial, y big
serial, estos valores parten en 1 y puede llegar hasta 32767,
2147483647 y 9223372036854775807 respectivamente.

Para crear una tabla con una columna serial:

1
2
3
4

CREATE TABLE tablamagica (


id SERIAL,
name varchar(50)
);

Adems se puede agregar una columna serial a una tabla ya


existente, eso lo logramos con:

1
2
3
4
5

CREATE SEQUENCE tablename_colname_seq;


CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;

Luego para probar podemos llenar nuestra tabla mgica con datos:

1
2

INSERT INTO tablamagica (name) values ('prueba1');


INSERT INTO tablamagica (name) values ('prueba2');

Y cuando mostremos la tabla veremos

1 | prueba1 2 | prueba2

Donde los valores 1 y 2 se insertaron de forma automtica


utilizando la secuencia serial.

Ordenando los resultados.


Para esta seccin necesitaremos un mini set de datos, para eso
borraremos todos los que tenemos e ingresaremos nuevos.

1
2
3
4
5

DELETE
INSERT
INSERT
INSERT
INSERT

FROM
INTO
INTO
INTO
INTO

personas;
personas VALUES
personas VALUES
personas VALUES
personas VALUES

('Francisca', 30, 1);


('Juan', 31, 2);
('Javier', 32, 3);
('Penelope', 28, 4);

Mostrando todos los resultados

SELECT * FROM personas;

name

age

id

Francisca

30

Juan

31

Javier

32

Penelope

28

Mostrando los resultados ordenados por edad


Para ordenar por algn criterio tenemos que agregar ORDER BY y
la columna sobre la cual vamos a ordenar.

SELECT * FROM personas ORDER BY age;

name

age

id

Penelope

28

Francisca

30

Juan

31

Javier

32

Mostrando los resultados ordenados por nombre


alfabticamente pero en reverso.
Esto lo logramos ocupando DESC en la query.

SELECT * FROM personas ORDER BY name DESC;

name

age

id

Penelope

28

Juan

31

Javier

32

Francisca

30

(4 rows)

Limitando la cantidad de resultados


Podemos limitar los resultados agregando al query la instruccin
LIMIT

SELECT * FROM personas LIMIT 1;

Es combinable con otras instrucciones por ejemplo con las de


ordenamiento

SELECT * FROM personas ORDER BY name DESC LIMIT 1;

Conteo
Aqu la sintaxis es ligeramente distinta, en lugar de seleccionar una
tabla en especco seleccionaremos la cuenta de elementos de esa
tabla

SELECT COUNT(*) FROM personas;

En este caso obtendremos 4.

Select distinct
Para los siguientes ejemplos necesitamos un par de datos ms,
para eso vamos a ingresar a otra mujer llamada Penelope en la
base de datos de distinta edad.

Nuestro set de datos quedara:

SELECT * FROM personas;

name

age

id

Francisca

30

Juan

31

Javier

32

Penelope

28

Penelope

30

Podemos seleccionar de la base de datos todos los nombres


distintos, o sea si hay un nombre repetido no aparecer en la
respuesta.

SELECT DISTINCT(name) FROM personas;

name
Javier
Juan
Penelope
Francisca

Un ejemplo donde esto podra ser muy til es si queremos extraer


de nuestra base de datos cuantas personas hay de cada pas o
ciudad (o ambas)

Distinct y count
Podemos combinar distinct y count para contar la cantidad de
elementos distintos.

SELECT COUNT(DISTINCT(age)) FROM personas;

Con los datos de nuestro ejemplo deberamos obtener 4, y si


hacemos el count sin el distinct deberamos obtener 5

Agrupando datos
Cmo podemos hacer para contar cuantas personas tienen cada
edad?

SELECT name, COUNT(name) FROM users GROUP BY name;

Cuando usamos group by debemos tener cuidado de no


seleccionar datos que no estn agrupados

SELECT id, COUNT(name) FROM users GROUP BY name;

Esto nos dar el error:

ERROR:

column "users.id" must appear in the GROUP BY clause or be used in an aggregate

Preguntas
1. Qu tipo de lenguaje es SQL?
2. Cmo se entra a SQL?
3. Cmo crear un usuario en psql?
4. Qu comando muestra todas las bases de datos dentro de postgresql?
5. Qu comando muestra todas las usuarios dentro de postgresql?
6. Cul es la diferencia entre una base de datos y una tabla?
7. Cmo se le da permisos a un usuario para poder modicar una base de
datos?
8. Cmo insertar datos en una tabla existente
9. Qu es la clave primaria?
10. Por qu es importante especicar el where en las instrucciones de
update y delete?
11. Cmo se puede borrar una base de datos?
12. Cmo se puede borrar una tabla?
13. Cmo se puede obtener la persona de mayor edad de la tabla personas?
14. Qu hace select distinct?
15. Qu son los constraints?

Gua de ejercicios
Pelculas
Completar los queries y poner la consulta SQL respectiva de cada
pregunta subirlos a la plataforma de empieza.

CRUDS
1. Crear la base de datos movies
2. Crear la tabla movie con la clave primaria id y nombre
3. Ingresar la pelcula El Rey Len
4. Ingresar la pelcula Terminator II
5. Alterar la tabla pelculas para agregar el ao
6. Cambiar los datos de todas las pelculas existentes a 1984
7. Borrar la pelcula Terminator II
8. Crear un usuario nuevo en la base de datos
9. Asignarle un rol que slo permite hacer consultas select (no
podr ingresar)
10. Cambiar de usuario en la base de datos
11. Probar que no puede ingresar una pelcula

Sorting
1. Ingresar 5 pelculas ms, con nombres y aos distintos.
2. devolver las primeras 3 pelculas (ordenadas alfabticamente)
3. devolver las ltimas 3 pelculas (ordenadas por ao)

Conteo
1. Contar la cantidad de pelculas en la base de datos
2. Contar la cantidad de pelculas por ao.
3. Alterar la tabla para agregar la categora de la pelcula
4. Agregar categoras a todas las pelculas existentes
5. Obtener un listado de las categoras (sin repeticiones)

Productos
Completar los queries y poner la consulta SQL respectiva de cada
pregunta subirlos a la plataforma de empieza.

1. Crear la base de datos productos


2. Crear la tabla productos con id, nombre, precio
3. id debe ser la clave primaria, agregar el constraint de que el
precio debe ser mayor que cero y el nombre del producto nico.
4. Insertar 10 productos
5. Contar la cantidad de elementos que hay, se deberan obtener
10
6. Contar la cantidad de elementos que hay con precio mayor a
1000
7. Contar la cantidad de elementos que hay con precios distintos
8. Ordenar los productos por precio

14) SQL con ms de una tabla


Objetivos
1. Manejar los conceptos de integridad, clave primaria y clave fornea
2. Aprender a realizar consultas a mltiples tablas
3. Introducir los tipos de relaciones que pueden existir entre los modelos

Integridad referencial
La integridad es un concepto asociado a la calidad y validez de los
datos, por ejemplo supongamos como en el caso del ejercicio
anterior tenemos pelculas y categoras pero que pasa si queremos
renombrar una categora, entonces tendramos que asegurarnos
de cambiar todas las referencias a la categora en la tabla pelculas,
en una base de datos pequeas esto podra no ser un problema
pero en bases de datos grandes es ms complejo.

Cmo se soluciona el problema de las referencias?

Separando una tabla en dos partes, la primera contiene los datos


donde insertaremos la pelcula, la segunda es una tabla donde
insertaremos todas las categoras, y las relacionaremos a travs de
un nmero.

De esta forma slo tenemos que cambiar el valor dentro de la


categora para cambiar todos los valores referidos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

create table movies (


id integer primary key,
title varchar(64),
category_id integer
);
create table categories (
id integer primary key,
name varchar(64)
);
INSERT INTO categories VALUES (1, 'Accin');
INSERT INTO categories VALUES (2, 'SCI FI');
INSERT INTO categories VALUES (3, 'Animacin');
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT

INTO
INTO
INTO
INTO
INTO
INTO
INTO
INTO
INTO

movies
movies
movies
movies
movies
movies
movies
movies
movies

VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES

(1,
(2,
(3,
(4,
(5,
(6,
(7,
(8,
(9,

'Terminator', 1);
'Terminator 2', 1);
'Volver al futuro', 2);
'Terminator 3', 1);
'Volver al futuro 2', 2);
'Tiburn', 1);
'Akira', 3);
'Ghost in the Shell', 3);
'Duro de matar', 1);

Luego podemos juntar ambas tablas ocupando la instruccin


SELECT

SELECT * FROM movies, categories;

id

title

category_id

id

name

Terminator

Accin

Terminator 2

Accin

Volver al futuro

Accin

Terminator 3

Accin

Volver al futuro 2

Accin

Tiburn

Accin

Akira

Accin

Ghost in the Shell

Accin

Duro de matar

Accin

Terminator

SCI FI

Terminator 2

SCI FI

Volver al futuro

SCI FI

Terminator 3

SCI FI

Volver al futuro 2

SCI FI

Tiburn

SCI FI

Akira

SCI FI

Ghost in the Shell

SCI FI

Duro de matar

SCI FI

Terminator

Animacin

Terminator 2

Animacin

Volver al futuro

Animacin

Terminator 3

Animacin

Volver al futuro 2

Animacin

Tiburn

Animacin

Akira

Animacin

Ghost in the Shell

Animacin

Duro de matar

Animacin

El problema es que hacerlo nos da el producto cartesiano entre


ambas tablas, o sea todos los datos con todos los otros, que
obviamente no es lo que queremos, para obtener los resultados
que buscamos tenemos que utilizar la instruccin where.

SELECT * FROM movies, categories WHERE categories.id = movies.category_id;

id

title

category_id

id

name

Terminator

Accin

Terminator 2

Accin

Volver al futuro

SCI FI

Terminator 3

Accin

Volver al futuro 2

SCI FI

Tiburn

Accin

Akira

Animacin

Ghost in the Shell

Animacin

Duro de matar

Accin

Y estos si son los resultados que queremos.

Ambigedad en los nombres

Cuando hay campos con los mismos nombres en ambas tablas no


podemos ser ambiguos, por ejemplo si realizamos el query:

SELECT * FROM movies, categories WHERE id = category_id;

ERROR: column reference "id" is ambiguous LINE 1: SELECT * FROM


movies, categories WHERE id = category_id;

Es por lo mismo que en la mayora de los casos tendremos que


utilizar tanto el nombre de la tabla como el nombre del campo.

Joins
Otra forma de lograr resultados similares es utilizando la
instruccin JOIN

SELECT * FROM movies INNER JOIN categories ON (categories.id = movies.category_id

Tipos de joins
Hay diversos tipos de join y principalmente dieren en como tratar
la unin de estos conjuntos cuando los valores no estn asociados.

Para entender bien la diferencia vamos a ingresar una pelcula sin


categora y una categra sin pelcula.

1
2

INSERT INTO movies VALUES (10, 'Pelcula sin categora');


INSERT INTO categories VALUES (4, 'Categora sin pelculas');

Luego si probamos:

SELECT * FROM movies, categories WHERE categories.id = movies.category_id;

O si probamos:

SELECT * FROM movies INNER JOIN categories ON (categories.id = movies.category_id

En ambos casos no obtendremos resultados ni de la pelcula nueva


ni de la categora nueva.

Left Outer Join


1

SELECT * FROM movies LEFT OUTER JOIN categories ON (categories.id = movies.

id

title

category_id

id

name

Terminator

Accin

Terminator 2

Accin

Volver al futuro

SCI FI

Terminator 3

Accin

Volver al futuro 2

SCI FI

Tiburn

Accin

Akira

Animacin

Ghost in the Shell

Animacin

Duro de matar

Accin

10

Pelcula sin categora

Right Outer Join


1

SELECT * FROM movies RIGHT OUTER JOIN categories ON (categories.id = movies

id

title

category_id

id

name

Terminator

Accin

Terminator 2

Accin

Volver al futuro

SCI FI

Terminator 3

Accin

Volver al futuro 2

SCI FI

Tiburn

Accin

Akira

Animacin

Ghost in the Shell

Animacin

Duro de matar

Accin

Categora sin pelculas

Full Outer Join


1

SELECT * FROM movies FULL OUTER JOIN categories ON (categories.id = movies.

Muestra el cruce entre ambas tablas dejando los datos nulos de


ambas.

Explain
Explain es una instruccin de PSQL que nos permite pedirle al
motor de base de datos que nos explique que plan utiliz para
ejecutar un query

Para buscar datos siempre hay varios planes posibles, cada motor de SQL determina
de forma automtica cul es el mejor plan.

EXPLAIN SELECT * FROM users;

1
2
3
4

QUERY PLAN
--------------------------------------------------------Seq Scan on users (cost=0.00..7.28 rows=128 width=373)
(1 row)

Donde Seq Scan indica que el plan de bsqueda es leer todos los

datos de forma secuencial, el primer nmero de costos indica el


tiempo necesario para empezar, el segundo el tiempo necesario
para terminar, las las indican la cantidad de las ledas y el ancho
la cantidad promedio de bytes de una la, ms columnas implica
ms ancho, tipos de datos ms grandes implican ms anchos.

Miremos ahora un plan con un query con where

EXPLAIN SELECT * FROM users WHERE id > 10;

1
2
3
4

--------------------------------------------------------Seq Scan on users (cost=0.00..7.60 rows=119 width=373)


Filter: (id > 10)
(2 rows)

Lo interesante que vemos ac que independiente que id es una


clave primaria an as la bsqueda que se hace es secuencial en
lugar de utilizar el ndice.

Tipos de relaciones
Recientemente separamos la tabla pelculas en dos tablas para
evitar tener datos repetidos como el nombre de la pelcula dentro
de cada una.

Al tener dos tablas se gener una relacin entre ellas, podemos


decir que una pelcula tiene una categora, pero una categora
puede tener mltiples pelculas, este tipo de relaciones se llama de
uno a muchos, puesto que a un elemento de una tabla de la base
de datos le corresponden varios elementos de la otra tabla.

Hay tres tipos de relaciones famosas.

Relaciones de uno a uno.


Relaciones de uno a muchos.
Relaciones de muchos a muchos.

Relaciones de uno a uno


Las relaciones de uno a uno consisten en dividir un tabla que tiene
muchas columnas en dos, para esto supongamos que estamos
construyendo un software de ventas y tenemos una tabla de
usuarios.

Donde la tabla tiene:

Nombre
Edad
Direccin
Telfono
Tarjeta de crdito
RUT (identicador de la persona)
Compaa
Direccin compaa
Telfono compaa
Cargo

En una tabla como esta podemos separar los datos del usuario con
los de la compaa, y slo llenarlos si la persona tiene compaa,
entonces quedara la tabla usuarios y la tabla de compaas, pero
para unirlas tenemos que hacer una de dos, guardar un id de la
compaa en la tabla usuarios o guardar el usuario en la tabla de
compaa, determinar cual es la mejor forma tiene que ver ms
con el diseo de la aplicacin que con el de la base de datos, pero
la pregunta de rigor que nos haramos es que se guardar primero,
si creamos el usuario y luego la compaa es ms sencillo guardar

el usuario en la compaa, si creamos primero la compaa y luego


al usuario probablemente sea ms sencillo guardar el dato de la
compaa en el usuario.

Hay diversas formas de diagramar las relaciones, usualmente se


representa con una echa cuando tiene un elemento y con doble
echa cuando tiene ms de un elemento.

Relaciones de 1 a n
Estas son las relaciones ms frecuentes, normalmente sucede
cuando hay una tabla de datos y una tabla de categora de esos
datos, por ejemplo: persona y pas, o, ciudad o pas, pelcula y
categora. En otros casos sucede cuando tambin hay un dueo de
muchas cosas, por ejemplo un autor y sus posts, o un autor y sus
comentarios.

Relaciones de n a n
Estas relaciones se dan cuando la autora de algo es compartida,
por ejemplo un proyecto puede tener varios integrantes, pero a su
vez un integrante puede tener varios proyectos, o una persona

puede tener permisos para varias secciones, pero cada seccin


puede ser accedida por varios usuarios.

Estas relaciones son un poco ms complejas porque no se pueden


representar directamente en un modelo de datos relacional,
porque donde pondramos los ndices?, si los agregamos por el
lado de usuarios un proyecto_id como haramos para meter varios
proyectos?, por otro lado si lo agregamos del lado del proyecto con
usuario_id como haramos para meter varios usuarios?

La solucin es convertir la relacin de n a n en dos relaciones de 1


a N.

De esta forma pueden haber cuantas personas quieran trabajando


en cuantos proyectos quieran.

La clave fornea
La clave fornea (en ingls Foreign Key o FK) es un ndice que
permite mantener la integridad referencial entre dos tablas,
principalmente evita que se borre un elemento de una tabla si hay
otros que se reeran a el.

En nuestro ejemplo anterior no podramos borrar la categora


accin porque hay pelculas que utilizan esa categora.

1
2
3
4

CREATE TABLE movies2 (


id integer PRIMARY KEY,
category_id integer REFERENCES categories (id)
);

Ahora si intentamos ingresar una pelcula con una categora que


no existe, obtendremos:

Insert into movies2 values (2, 10);

1
2

ERROR: insert or update on table "movies2" violates foreign key constraint "movies2\_ca
DETAIL: Key (category_id)=(10) is not present in table "categories".

Preguntas
1. Cul es la diferencia entre los outer y los inner joins?
2. Cul es la diferencia entre SELECT * FROM movies, categories;
y
SELECT * FROM movies, categories WHERE categories.id = movies.category_id; ?
3. Cul es la diferencia entre SELECT * FROM movies, categories WHERE
categories.id = movies.category_id; e y realizar el mismo select con inner
join?
4. Por qu es importante en el select nombrar la tabla?
5. Para qu sirven las relaciones de 1 a 1?
6. Nombre 5 ejemplos donde sera til una relacin de 1 a n
7. Nombre 5 ejemplos donde sera til una relacin de n a n
8. Para qu sirve la clave fornea?

9. Qu signica FK?

Ejercicios
Shopping
Crear la base de datos shopping101 y dentro de ella.
Crear la tabla compradores, con id y nombre.
Ingresar al menos 5
compradores.
Crear la tabla pas con id y nombre.
Ingresar al menos 5 pases.
Alterar la tabla compradores para agregar la columna pais_id.
Agregar un par de usuarios que
no tenga pases asignados.
Obtener todos los usuarios con todos los pases.
Obtener todos los pases que no tienen asigando ningn
usuario.
Obtener la cantidad de usuarios de cada pas.
Obtener al pas con mayor cantidad de usuarios.

movieDB
Crear la base de datos moviedb.
Crear la tabla category.
Agregar la columna category_id a movie.
Ingresar 3 categoras de pelculas.
Accin
Terror
Drama
Ingresar las FK para relacionar las pelculas existentes con las

categoras respectivas.
Obtener todas las pelculas de la categora Accin y contarlas.
Ordenar la tabla de categoras segn la cantidad de pelculas
que hay con esas categoras.

Relaciones n a n
1. Crear la tabla tags, con una clave primaria id, y el campo tag.
2. crear la tabla movie_tags con la clave primaria id, y las claves
forneas tag_id y movie_id.
3. Ingresar un grupo de tags.
4. Asignar 3 tags a cada pelculas (puedes ocupar ms de un
insert).
5. Obtener todas los nombres de las pelculas con el tag
dinosaurios.
6. Utilizando joins, devuelve todas las pelculas con todos sus tags.
7. Contar la cantidad de tags que tiene cada pelcula. -hint tienes
que hacer dos joins en el mismo query.
8. Devolver los tags ordenados por mayor uso. -hint hacer el join
slo con la tabla intermedia.

Exportando datos
1. Exportar todos los datos en un archivo SQL

15) Modelando con SQL


Objetivos
1. Repasar SQL a travs de casos prcticos.
2. Modelar diversos ejemplos tpicos de aplicaciones.

El secreto para modelar con SQL es distinguir bien cuales son las
tablas y cuales son los atributos de esas tablas, por ejemplo
supongamos que queremos hacer un blog, entonces claramente
un blog tiene artculos, ahora los comentarios son un campo de
artculo o una tabla aparte?, el ttulo del artculo es un campo del
blog o no?, todo ese tipo de preguntas tenemos que poder
hacernos para poder modelar con SQL. Es muy importante tener
experiencia en esto para trabajar en Rails puesto que Rails no
modela por nosotros, slo nos genera las consultas SQL de forma
automtica.

Modelando un blog
Lo primero que tenemos que hacer es distinguir a los actores y las
acciones principales.

Entonces:

una visita entra y ve artculos


una visita entra al artculo puede leerlo y ver sus comentarios
una visita se registra y se convierte en un usuario
un editor (el cual es un tipo de usuarios puede crear artculos

nuevos)
un editor puede borrar artculos y comentarios
al borrar un artculo se deben borrar todos sus comentarios

La pregunta de rigor al modelar es Qu informacin necesitamos


utilizar? Y para poder utilizarla, qu informacin necesitamos
guardar?

Respecto a nuestro blog tenemos que considerar algunas


preguntas y comentarios:

Vamos a guardar las visitas en la base de datos o no?, quizs


no sea til ya que estas no registran nada, si queremos medir el
acceso de visitas podemos hacerlo utilizando alguna solucin
como google analytics y no tenemos la necesidad de meter
estos datos en nuestra base de datos.
Los artculos necesitamos guardarlos, porque vamos a mostrar
esa informacin, cada artculo puede tener un ttulo y una foto.
Los usuarios necesitamos guardarlos, y surge de aqu un tipo de
usuario el cual es el editor, por lo tanto tenemos que guardar el
tipo del usuario dentro de la base de datos para consultarlo
despus.
Hay comentarios, y estos tenemos que guardarlos para poder
mostrarlos, y le pertenecen a un usuario, pero adems uno
comenta dentro de un artculo, y dentro de un artculo pueden
haber

varios

comentarios,

pertenecen a un artculo.

Entonces el modelo quedara:

as

que

los

comentarios

le

Las convenciones son muy importantes, especialmente cuando


trabajamos con Rails, una de estas convenciones es la del nombre
de las claves forneas, el nombre debe ser el nombre de la tabla
referida en singular con un sujo id.

O sea si la tabla comentario tiene usuarios, la FK debera llamarse


usuario_id y en lo posible todos los nombres en ingls para evitar
problemas.

Ejercicio
Crear la base de datos blogX.
Crear un usuario llamado "Julian" del tipo editor.
Crear 5 artculos y asociarlos al usuario creado.
Crear 3 usuarios ms y agregar comentarios a los artculos

creados.
Mostrar todos los artculos con la cantidad de comentarios de
cada uno.
Mostrar todos los usuarios con la cantidad de comentarios
creados de cada uno.
Mostrar los artculos junto con la informacin del editor.

Modelando Tinder
Tinder es una famosa red social de citas, que tiene usuarios y tu
vez personas marcando si te gustas o no, si ambas se marcan
como gustados entonces es un match, ahora hay mltiples formas
de modelar esto, pero analicemos alguna.

La pregunta entonces es, que actores hay, y que informacin nos


interesa guardar.

Claramente necesitamos a los usuarios con sus fotos, en este


momento no es importante que los usuarios se saquen de
Facebook porque necesitamos guardarlos de todas formas en
nuestra base de datos.

Por otro lado lo que nos interesa guardar son dos cosas, una los
likes, un usuario hace like a otro, entonces un usuario da y recibe
likes, cuando existe un like del usuario uno al dos y existe uno del
dos al uno entonces hacemos match.

En nuestra aplicacin puede (o puede que no) importarnos de que


likes viene el match, una vez que ya lo hacemos podemos darnos
el lujo de perder esa informacin, pero si debemos saber entre que
usuarios se hizo match.

Ahora en el funcionamiento de la aplicacin, se encuentran


personas que estn cerca, por lo tanto tenemos que saber donde
estn esas personas, para eso agregaremos latitud y longitud, y
adems no podemos mostrarle usuarios que ya haya descartado a
una persona, por lo que tenemos que guardar la informacin de si
la interaccin fue de like o unlike, para eso renombraremos la tabla
like a interacciones y guardaremos cada interaccin entre un
usuario uno y otro.

Ejercicios de diagrama
Modicar el diagrama para que el usuario pueda agregar todas
las fotos que quiera.
Agregar la tabla de mensajes para guardar los mensajes entre
usuarios.

Ejercicios de SQL
Crear las tablas en el modelo
Agregar 10 usuarios
Agregar 4 interacciones positivas
Agregar 4 interacciones negativas
Obtener todos los usuarios con los que hayas tenido una
interaccin negativa
Obtener todos los usuarios con los que hayas tenido una
interaccin positiva
Obtener todos los usuarios con los que no hayas tenido una
interaccin.
Obtener todos los nombres de los matches de un usuario.

Modelando un carro de compras


El carro de compras ms bsico consiste en un carro donde
guardamos los items que queremos y despus procedemos a
hacer el checkout.

Una pregunta interesante que uno debe hacerse es Nos interesa


guardar el carro de compras?, es perfectamente posible que solo
nos interese guardar los items comprado y no el carro mismo,
muchas veces el carro solo se guarda en la sesin del navegador.

Como no podemos tener relaciones de n a n romperemos la tabla


ocupando una intermedia.

Ejercicio
Crear las tablas en la base de datos.
Ingresar 5 items.
Ingresar 2 usuarios.
Generar una orden de compra con dos items.
Obtener todos los items que ha comprado un usuario.
Obtener los items ms comprados.

Seccin IV: Back-End con Rails

16) Rails con PSQL


Objetivos
1. Aprender a crear un proyecto Rails con PostgreSQL como motor de base
de datos.
2. Aprender a modicar un proyecto Rails existente para agregar
PostgreSQL como motor de base de datos.

Instalando Postgre en un proyecto existente


En Ruby todas los componentes se encapsulan en gemas, en este
caso instalaremos los drivers de PostgreSQL para Ruby utilizando
la gema pg, para eso agregamos al gemle:

gem 'pg'

Luego dentro del terminal escribiremos bundle y acto seguido


debemos cambiar el archivo de conguracin de desarrollo, este se
encuentra en el archivo database.yml.

1
2
3
4
5
6
7

development:
adapter: postgresql
encoding: unicode
database: nombre_base_de_datos
pool: 5
username: usuario
password: password

Los archivo yaml o .yml requieren de una indentacin


perfecta, asegura de agregar espacios de sobre ni que
falten, as como no agregar comillas donde no vayan.

Con el archivo database congurado el siguiente paso es abrir el


archivo gemle, sacar la gema de sqlite3 del entorno de desarrollo
y agregar la gema pg al entorno de desarrollo y de produccin.
Despus creamos la base de datos y migramos con:

1
2

rake db:create
rake db:migrate

Antes de continuar es importante observar el output del comando


anterior en la consola, debido a que existe una posibilidad de que
el usuario utilizado en la conguracin en el archivo database.yml
no tenga permisos para crear bases de datos dentro de
PostgreSQL, en ese caso podemos hacer una de las siguientes 3
cosas:

Cambiar el usuario de la conguracin dejando no que si tenga


accesos para crear bases de datos.
Dar acceso a ese usuario a crear bases de datos.
Crear la base de datos a mano.

Creando un proyecto con postgreSQL


Es posible crear un proyecto directamente ocupando PostgreSQL
con:

rails new myapp --database=postgresql

De todas formas necesitaremos abrir el archivo database.yml para


congurar nuestra base de datos, sin embargo no tendremos que
abrir el gemle para remover la gema sqlite3 y agregar la gema pg.

El error de socket
Existe un error muy comn al cargar el servidor de Rails y abrir la
pgina que dice

1
2
3

could not connect to server: No such file or directory


Is the server running locally and accepting
connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

Esto normalmente se debe a que el servidor de postgreSQL no est


corriendo.

Preguntas
1. Por qu es necesario congurar el archivo database.yml si creamos el
proyecto con `rails new myapp database=postgresql`
2. Qu hace rake db:create?
3. Qu hace rake db:setup?
4. Cul es la diferencia entre instalar postgres en el sistema operativo e
instalar la gema de postgre en el proyecto en Rails?
5. En qu archivo se congura la base de datos?

#17) Los modelos en Rails

Objetivos
1. Entender el concepto de modelo como ORM
2. Manejar las migraciones para modicar la base de datos
3. Agregar datos a travs de rails console
4. Agregar datos a travs del archivo seed
5. Utilizar atributos virtuales
El concepto de modelo en Rails puede ser complicado de entender porque es simultneamente
dos cosas. En primera opcin es un **ORM** (object relational mapper) lo cul consiste en
conectar elementos de dentro de una tabla de base de datos con un objeto de alto nivel que nos
permite abstraernos del SQL y trabajar con objetos que saben buscarse, actualizarse y
guardarse en la base de datos. Adems los modelos manejan la capa lgica del negocio, o sea se
encargan de cuidar la integridad de los datos a un nivel ms abstracto y ms fcil de programar
que el de SQL En este captulo abordaremos los modelos como un ORM, y en el captulo 16
abordaremos el como cuidar y validar datos con los modelos. ##Los modelos como mapas a los
objetos (ORM) El modelo al ser un mapa del objeto nos permite manipular datos de la base de
datos de forma simple, mientras la clase sirve para mapear la tabla, las instancias del modelo
nos sirven para mapear las. Veamos un ejemplo, para eso creemos el modelo Task en un
proyecto nuevo. ~~~bash rails g model task user:string ~~~ Obtendremos: ~~~ invoke
active_record create db/migrate/20151130040223_create_tasks.rb create app/models/task.rb
invoke test_unit create test/models/task_test.rb create test/xtures/tasks.yml ~~~ Al crear
modelos se generan migraciones que nos permiten crear las tablas de forma automtica en la
base de datos, en el caso anterior se gener la migracin
`db/migrate/20151130040223_create_tasks.rb`, podemos correr esos cambios con: ~~~ rake
db:migrate ~~~ Luegos gracias a esto obtendremos mtodos de clase que nos permiten traer a
memoria todos los tasks que hay en la base de datos, o, encontrar uno en especco, ya sea el
primero, el ltimo o por algn criterio a nuestra voluntad. Estos los podemos probar
directamente en `rails console` o dentro de nuestro proyecto Rails . ~~~bash Task.all #Obtiene
todas las tareas en la base de datos Task.nd(1) #Busca la tarea con id 1 Task.rst #Devuelve el
primer task de la base de datos Task.last #Devuelve el ltimo task de la base de datos
Task.where(user: Gonzalo) #Busca todas las tareas cuyo usuario sea Gonzalo, esto funciona
siempre y cuando el modelo usuario tengo un campo llamado name, en cuyo caso y utilizando
debera fallar ~~~ No es sorpresa que en ninguna de las pruebas anteriores obtendremos

resultados distinto de vaco, puesto que todava no tenemos datos, para hacerlo podemos
generar instancias de los tasks para guardar tareas nuevas. ~~~bash t = Task.new(user:
Gonzalo) t.save ~~~ O podemos crearlas directamente utilizando el mtodo create ~~~bash
Task.create(user: Gonzalo) ~~~ Para actualizar un dato de la base de datos tenemos que traerlo
a memoria primero, luego podemos modicarlo y guardarlo. ~~~bash t = Task.rst t.name =
R2D2 t.save ~~~ Hay mtodos que traen a memoria un objeto, en ese caso ese objeto ser de
ese tipo, como por ejemplo t.task ser Task, pero los mtodos que traen mltiples datos a
memoria como .all, o .where nos devuelven un objeto del tipo ActiveRecord\_Relation que **no
son un array** pero se comportan como uno, en trminos de que son iterables. ~~~ruby
Task.all.each {|t| puts t.user} ~~~

Se debe tener cuidado de no confundir los mtodos de


instancia con los de clase, por ejemplo no tiene sentido
obtener

`Task.user`

de

qu

usuario

estaramos

hablando? sin embargo si tiene sentido obtener un


usuario de la tabla y preguntar su nombre.

La clave para evitar este error es entender que los mtodos de


clase sirven para operar por sobre la tabla, y en cambio los
mtodos de instancia sirven para operar sobre una de las las.

Creando modelos
En Rails cada vez que creamos un modelo se crea una migracin
que nos permite modicar la base de datos y que existe esa
persistencia a nivel de base de datos cuando ingresamos,
borramos o modicamos datos.

Entonces Para qu sirven los modelos?

Para guardar datos y para asegurarnos que esos datos cumplan


con reglas, y esas reglas las podamos programar en Ruby y con

toda la magia de Rails en lugar de SQL

Y para qu sirven las migraciones?

Para cambiar la estructura de la base de datos, particularmente


sirven para crear, actualizar y borrar tablas, campos e ndices.

Convencin de nombres
La convencin en Rails es que los modelos se escriben en singular,
de ah el mismo Rails lo pluraliza para escribir el nombre de la tabla
en la base de datos.

Por eso mismo no debemos escribir estos nombres en


espaol puesto que las reglas no son las mismas y
podemos causarnos problemas.

Creando un modelo vaco


Podemos crear un modelo vaco, (slo id, created_at, updated_at
los modelos son en minscula, con:

rails g model nombre_modelo

Creando un modelo con diversos campos


Para crear el modelo con uno o ms campos, pero debemos
ocupar un nombre distinto

rails g model nombre_modelo2 campo1:tipo_de_dato campo2:tipo_de_dato

Si el tipo_de_dato se omite se asumir que es string.

Por ejemplo si creamos un modelo para guardar comentarios,


obtendremos:

1
2
3
4
5
6

invoke
create
create
invoke
create
create

active_record
db/migrate/20151128090136_create_comments.rb
app/models/comment.rb
test_unit
test/models/comment_test.rb
test/fixtures/comments.yml

O sea, un archivo de migracin, un archivo con el modelo y tests


(los cuales cubriremos pronto en un prximo captulo)

Podemos agregar todas las columnas que necesitemos a una tabla, hay tablas en
empresas que tienen 50 columnas y no hay problemas con eso, pero tampoco se
deben agregar datos innecesariamente ni mucho menos se debe caer en el error de
tener columnas que repitan la informacin.

Ahora, dentro de los tipos de datos comunes que ocuparemos, los


ms frecuentes son:

integer
oat (decimales)
boolean (true o false)
string (hasta 255 chars)
text (ms de 255 chars)

No podemos tener dos modelos con el mismo nombre

Migraciones
Una migracin es un set de cambios para la base de datos, por
ejemplo agregar una tabla, cambiar de nombre una tabla, agregar
campos a las tablas, botarlas, etc.

Las migraciones son clave para el desarrollo pues nos ayudan a


ordenar el desarrollo de nuestra aplicacin, la base de datos es el
esqueleto de una aplicacin web y las migraciones nos permiten
generar un control de cambios bien no y evitar errores por
diversas versiones.

Imaginemos el siguiente caso, dos personas, Pedro y Ral estn


trabajando en una aplicacin web, Pedro crea un campo nuevo
para una tabla y crea el cdigo para manejar ese campo, luego
sube al repositorio los cambios, pero la base de datos est en su
computador, por lo tanto slo sube los cambios respectivos a
lgica del manejo del campo y no notica que agreg un campo en
la base de datos, luego Ral descarga los cambios de Pedro y la
aplicacin deja de funcionar.

Las migraciones resuelven el tipo de problemas que tienen Pedro y


Ral, pues estas son parte del cdigo y por lo mismo se agregan a
los repositorios, gracias a las migraciones podemos compartir de
forma sencilla las aplicaciones y con slo rake db:migrate estar
listos para empezar a trabajar.

El archivo schema.rb
El

secreto

para

entender

las

migraciones

db/schema.rb , el cual a esta altura luce as:

es

el

archivo

1
2
3
4
5
6
7
8

ActiveRecord::Schema.define(version: 20150819042819) do
create_table "tasks", force: :cascade do |t|
t.string
"task"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string
"user"
end
end

El schema contiene toda la informacin respecto al estado actual


de la base de datos, la suma de todas las migraciones construye el
schema, y la versin del schema, o sea el nmero que aparece
corresponde al nombre del archivo de la ltima migracin.

Rails sabe que hay migraciones no corridas con solo comparar el


ltimo archivo de las migraciones con el de la versin del schema.

Creando migraciones.
Al crear modelos se crean migraciones, pero nosotros tambin
podemos crear una migracin nueva utilizando el generador de
Rails

rails

migration

nombre_migracion

generar una migracin vaca.

Una migracin vaca luce as:

1
2
3
4

class MigracionX < ActiveRecord::Migration


def change
end
end

Dentro del mtodo change nosotros podemos:

Agregar una columna

pero esto

add_column :tabla, :columna, :tipo_de_dato

Por ejemplo podemos agregar el campo completed a la tabla task


con:

add_column :tasks, :completed, :boolean

Remover una columna


1

remove_column :tabla, :columna

Por ejemplo podemos remover el campo completed a la tabla task


con:

remove_column :tasks, :completed

Generadores mgicos
Tanto para el aadir columnas como para removerlos existen
autogeneradores, Si recuerdan bien ya los hemos ocupado
previamente para agregar el campo usuario a task.

La Lgica del autogenerador es la siguiente, dentro del bash:

rails g migration addXAndYAndZToTable x:string y:integer z:float

y el cdigo anterior generar automticamente:

1
2
3

class AddXAndYAndZToTask < ActiveRecord::Migration


def change
add_column :tasks, :x, :string

4
5
6
7

add_column :tasks, :y, :integer


add_column :tasks, :z, :float
end
end

Tambin existe generador mgico para borrar campos.

rails g migration removeXAndYFromTable x:string y:integer

Revirtiendo una migraciones


El pasado no lo debemos modicar, pero podemos, por lo tanto
debemos hacerlo con cuidado.

Supongamos que Ral realiz una migracin pero esta tuvo algn
error, ya sea por el tipo de dato, o por un error tipogrco y
supongamos que se dio cuenta despus de haber corrido el
comando rake db:migrate si Raul todava no ha compartido
sus cambios a travs de un push todava est a tiempo de
enmendar su error.

Enmendando un error.
Como no podemos modicar el schema lo que tenemos que hacer
es modicar una de las migraciones, pero siempre teniendo en
mente que la suma de las migraciones debe dar el schema, y no
pueden haber por ah dos sumas distintas, o sea en todos los
cdigos las migraciones deben ser las mismas, por eso slo vamos
a enmendar los errores de esta forma cuando no hayamos
compartido nuestros cambios.

Pasos para enmendar el error.

1. Devolver la base de datos a una versin anterior con

rake db:rollback

esto el schema ahora apuntar a la

versin anterior a la ltima


2. Modicar la migracin y guardar los cambios
3. Correr rake db:migrate

Si los cambios ya fueron enviados entonces lo que tenemos que


haces es simplemente avanzar hacia adelante, o sea crear una
nueva migracin que remueva la columna mal hecha y que la cree
de nuevo, de esta forma para aplicar los cambios slo basta rake
db:migrate.

Cuidado con los branches


Cuando se est trabajando con sqlite3 en la versin de desarrollo
no existen problemas con los branchs puesto que cada branch
tiene su propia copia de la base de datos, pero cuando se ocupa
postgreSQL u otro motor similar existen problemas como el
siguiente.

Imaginemos que Ral ahora est trabajando en un proyecto con


PostreSQL, va a implementar una funcionalidad nueva por lo que
hace un branch del cdigo, luego crea una migracin y la corre
modicando la base de datos, despus se da cuenta que no le
gust como iba y vuelve al branch de desarrollo, pero al intentar
trabajar se da cuenta que la base de datos no se ha devuelto a su
forma original si no que est con los cambios implementados en el
branch.

Hay dos formas de resolver esta situacin, volver al branch y hacer


un rake db:rollback (o tantos como sea necesario) y volver al
branch original o

Botn de autodestruccin
En el peor de los casos siempre se puede empezar de nuevo, esto

quiere decir que podemos resetear la base de datos utilizando la


siguiente receta:

1. rake db:drop
2. rake db:create
3. rake db:migrate
4. rake db:seed

Si quieres saber ms sobre migraciones revisa la gua ocial de


migraciones

en

Rails

http://guides.rubyonrails.org/active_record_migrations.html

Es importante siempre respaldar la base de datos porque una vez que la botemos no la
podremos recuperar.

Ahora que ya entendemos lo bsico de migraciones podemos


volver al modelo y sus campos.

Destruir un modelo
Para destruir un modelo podemos ocupar:

rails destroy model nombre_modelo

Agregando un campo extra.


Una vez que hayamos creado un modelo no podemos volver a
crear campos para agregar datos, desde el punto de vista de SQL
no tendra sentido crear dos veces las misma tabla, habra que
destruir una para luego crear la otra, por lo mismo podemos
destruir el modelo pero si ya tiene cdigo esto no tendra sentido,
entonces lo que hacemos es agregar un campo extra ocupando el

generador de Rails.

Hay dos opciones, con el helper que se crea la migracin


automtica o sin el que nos obliga a nosotros a escribir los
comandos para crear las tablas, aadir los campos o hacer lo que
queramos hacer.

Con helper
1

rails g migration addCampoToTabla campo:tipo

Por ejemplo:

rails g migration addNameToUser name:string

Sin helper
1

rails g migration nombreMigracion

y luego se debe modicar la migracin generada.

Correr las migraciones


Todo cambio en el modelo a nivel de agregar columnas o
cambiarlas o borrarlas, o crear tablas requiere de una migracin,
estos archivos se crearn slo si seguimos las convenciones, o los
podemos crear nosotros siguiendo las reglas, pero una vez que los
archivos estn listos hay que dar el siguiente paso, que consiste en
correr las migraciones.

Todo hacia adelante

rake db:migrate

Revisar que la consola no muestre error.

Una versin hacia atras:

rake db:rollback

Un nmero n de versiones hacia atrs

rake db:rollback STEP=n

Correr las migraciones hacia arriba o abajo a una versin


especca.

rake db:migrate VERSION=20150306120002

El nmero de la versin lo podemos sacar de los nombres de los


archivos dentro de la carpeta de migraciones.

Revisar las migracin generada


Todas las migraciones se encuentran dentro de la carpeta
db/migrate , las migraciones estn ordenadas por un nmero
que es una fecha invertida por lo tanto las ltimas siempre iran al
nal.

Al correr el comando rake db:migrate se corren todas las


migraciones y a partir de ellas se genera db/schema.rb , no se
corran todas desde cero, el archivo schema contiene un nmero,
ese nmero corresponde al de la ltima migracin corrida, a partir
de ah rake db:migrate corre todo el resto de las migraciones y
actualiza el archivo schema.

Nunca debemos modicar el archivo schema directamente

Probar un modelo
Una vez creado el modelo debemos probarlo como lo hicimos en
captulos previos.

rails c

Y luego dentro del archivo:

Modelo.new

Es importante ocupar mayscula y singular, esas son los reglas


para referirse a una clase del tipo Active Record

Si lo hicimos bien obtendremos una instancia del modelo este


mostrar, id, created_at, updated_at y todos los otros campos que
hayas agregado a la base de datos.

En caso de que no haya sido generado obtendremos un error.

Creando una FK con ndice.


Si estamos ocupando PostgreSQL u otro motor con soporte de
ndices sobre claves forneas es posible de utilizar el generador de
Rails para crear el campo y el ndice simultneamente, esto se
puede lograr con la palabra clave references

1
2

rails g model user


rails g model company users:references

Veremos que La migracin obtenida incluye el ndice.

1
2
3
4
5
6
7
8
9

class CreateCompanies < ActiveRecord::Migration


def change
create_table :companies do |t|
t.references :users, index: true, foreign_key: true
t.timestamps null: false
end
end
end

Los ndices generan dependencias, por ejemplo ya no


podremos borrar un usuario sin borrar o reasignar la
compaa que tiene antes

Getters y setters
Para la siguiente seccin vamos a crear un modelo user con
nombre y un campo para saber si es administrador o no.

rails g model user name:string admin:boolean

u = User.create(name:"Gonzalo", admin:true)

Los modelos automticamente crean mtodos getter y setters para


cada uno de los campos de nuestro objeto.

Por ejemplo podemos obtener el nombre del usuario con


u.name y el valor de si es administrador con u.admin en el
caso de los valores booleanos se crea un mtodo getter adicional
con un signo de interrogacin al nal, entonces es lo mismo
escribir u.admin que u.admin?

El archivo seed
El archivo seed sirve para agregar datos de prueba a la base de
datos de desarrollo (e incluso podra servir para poblar una base
de datos de produccin, como por ejemplo para agregar los
administradores y conguraciones iniciales)

Dentro del archivo seed vienen comentado dos ejemplos que se


explican de forma bastante clara

1
2

#cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])


#Mayor.create(name: 'Emanuel', city: cities.first)

El primero sirve para crear varias ciudades, el segundo para crear


un Mayor para la primera ciudad (dependiendo de la versin de
Rails estos ejemplos pueden ser distintos).

La idea es borrar esto y crear tus propios datos de prueba.


Ejemplo:

Movie.create([{name:"El rey leon"}, {name:"terminator"}])

Dentro del seed es posible agregar Ruby:

1
2

c = Category.create("Disney")
Movie.create(name: "El rey len 2", category: c)

Un tip muy til es utilizar el mtodo create! en lugar de create (sin signo de
exclamacin) debido a que cuando falla lanza un excepcin que nos ayudar a
encontrar errores.

Atributos virtuales
Los atributos virtuales son atributos que tienen los modelos pero
no persisten en la base de datos (no se guardan), para qu sirven
entonces?, pueden tener distintos usos, desde almacenar una
variable temporalmente, hasta servir para hacer un clculo que no
queremos guardar en la base de datos, para crear atributos
virtuales slo tenemos que crear variables de instancias en el
modelo con sus respectivos getter y setters.

Preguntas
1. Para qu sirven los modelos?
2. Qu es un ORM?
3. Para qu sirven las migraciones?
4. Suponiendo que existe el modelo User con el atributo name
Qu hace User.all?
Cmo puedo devolver al ltimo de los usuarios
guardados en la base de datos?
Cmo podemos devolver todos los usuarios de
nombre "Desao"?
Cmo podemos cambiar todos los registros con el
nombre "Desao" por el nombre "Desao 2"?
5. De cul clase heredan los modelos en Rails?

6. Para qu sirve el archivo seed.rb?


7. Cul es la diferencia entre el mtodo create y el mtodo create!?
8. Qu son los atributos virtuales?

Ejercicio
Crea el modelo icecream con el campo sabor (avour) el cual es
un string
Corre las migraciones
Crea 10 helados, al menos 3 deben de ser de sabores distintos,
y dos de ellos debe ser de sabor a chocolate.
Selecciona todos los helados de sabor a chocolate, deberan ser
dos
Crea el modelo provider con la columna name
Agrega la columna e ndice de FK provider_id a la tabla helados
Corre las migraciones
Crea un proveedor llamado "Proveedor de helados 1"
Modica todos los helados existentes para que esten asociados
al proveedor uno. (solo hay que cambiar la columna
proveedor_id)
haz un rollback
Muestra todos los helados.

18) Reglas del negocio


Objetivos
1. Utilizar el modelo para proteger la integridad de los datos
2. Aprender a validar campos del modelo

Protegiendo la integridad de los datos


En el captulo de SQL vimos como agregando constraints podamos
proteger datos que cumplieran ciertas reglas, es posible de Rails
levantar una migracin y crear constraints con un ALTER TABLE
pero tambin es posible agregar las reglas a nivel de modelo.

Dentro de los modelos podemos validar presencia de campos, que


estos sean nicos, que sean mayores que otros, que la
combinacin de campos sea nica, o que un producto no est
expirado o que una licitacin est hecha dentro de cierto rango de
fechas, este tipo de reglas dependen especcamente de cada
negocio.

Validando la presencia de un campo


En el modelo respectivo, dentro de la clase:

validates :nombre_campo, presence: true

Un detalle importante si estamos trabajando en la


consola los cambios en el modelo no se reejan de forma
inmediata, tenemos que salir y volver a entrar de rails
console o utilizar el comando reload!

Validando que el campo sea nico


En el modelo respectivo, dentro de la clase:

validates_uniqueness_of :nombre_campo

Validaciones custom
Podemos validar un campo o conjunto de campos con las reglas
del negocio que queramos, para eso hay que hacer dos cosas, la
primera dentro de la clase debemos agregar un mtodo validate
acompaado de los mtodos que queramos ejecutar, ejemplo

1
2
3
4
5
6
7

validate :metodo_custom
def metodo_Custom
if expiration_date.present? && expiration_date < Date.today
errors.add(:expiration_date, "can't be in the past")
end
end

Valores iniciales

Hay dos formas de agregar valores iniciales a un modelo, una es a


nivel de base de datos, para lograrlo tenemos que generar un
migracion para agragar una columna con valor inicial o para
modicar una columna y darle valor inicial.

Ejemplos:

1
2

add_column :users, :notification_email, :boolean, :default => true


change_column :player_states, :location, :integer, :default => 0

Callbacks
La segunda forma es utilizando callbacks, los modelos permiten
llamar automticamente mtodos en ciertos momentos, por
ejemplo antes de guardarse, de esa forma podemos detectar si no
hay valores ingresados y ponerlos nosotros.

Para hacerlo ocuparemos el mtodo before_save en el modelo


respectivo.

before_save :metodo1, :metodo2

Estos mtodos se llamarn en el orden establecido, o sea primero


metodo1 y luego metodo2, pero para que esto funcione debemos
denir los mtodos, ejemplo:

1
2
3

def metodo1
self.user = User.first
end

Debemos tener mucho cuidado de nunca devolver false


dentro de estos mtodos pues en ese caso Rails asumir
que alguna validacin fall y har rollback del commit, o
sea no se guardarn los datos.

Existen siete puntos donde se puede agregar callback

Punto
(-) save
(-) valid
(1) before_validation
(-) validate
(2) after_validation
(3) before_save
(4) before_create
(-) create
(5) after_create
(6) after_save
(7) after_commit

Para agregar valores por defecto before_create es muy buen lugar, si lo hacemos
despus del create o sea con after_create es necesario volver a guardar el objeto
generando dos query en vez de una.

Podramos ver ahora un ejemplo ms complejo, en lugar de


convertir a un valor null podramos ver un objeto.

1
2
3
4
5
6
7
8
9
10

class Movie < ActiveRecord::Base


belongs_to :category
before_save :set_default_category
def set_default_category
c = Category.where(name: "Sin categora").first
self.category = c
end
end

category es un mtodo denido a travs de belongs_to :category, nos da un getter y un


setter que nos permite asignar una categora, podemos llamarlo de forma explcita
self.category o de forma implcita category.

Hay quienes preeren la forma explcita y hay quienes


preeren la forma implcita pero a menos que ocupemos
mtodos private y protected no hay diferencia.

Ejercicios
Copiar todos los cdigos a un archivo de texto

Crear un proyecto nuevo de Rails, con una conguracin nueva


de la base de datos
Crear el modelo de posts (sin agregar los usuarios)
Validar que el ttulo del post
este presente
Crear 5 artculos desde la
consola

Crear una artculo sin ttulo y


observar el error obtenido
Crear el modelo de usuarios
Agregar una migracin para
agregar el usuario a los posts
Modicar el modelo de post
para validar que el usuario est
presente
Crear un post sin usuarios para
asegurarnos de que la
validacin est funcionando
Crear el modelo de comentarios
Validar que el usuario est
presente
Crear el usuario "Usuario por defecto"
Cuando se cree un post sin
usuario se debe asignar al
usuario por defecto.
Cuando se borre un usuario
que sea dueo de posts se
deben asignar todos esos post
al usuario "usuario por
defecto"

Preguntas
1. Cmo podemos agregar una validacin de presencia sobre un atributo
del modelo?
2. Cmo podemos agregar un mtodo custom para validar un modelo?
3. En qu archivo se muestra el estado actual de la base datos?
4. Qu signican los nmeros al lado de los archivos de migraciones?
5. Cmo podemos agregar un campo nuevo a un modelo?

6. Cmo podemos eliminar un campo de un modelo?


7. Que consecuencias habran de modicar el archivo schema
directamente?
8. Para qu sirve dar valores iniciales a un campo del modelo?
9. Cul es la diferencia de realizar este trabajo a nivel de base de datos y a
nivel de modelo?
10. Cmo se crea una migracin para dar un valor inicial en el modelo?

19) Relaciones entre modelos


Objetivos
1. Utilizar el modelo para proteger la integridad de los datos
2. Gracar automticamente los modelos de datos utilizando los modelos
de Rails

En SQL podemos relacionar dos tablas haciendo un select a


mltiples tablas o haciendo un join, cuando lo hacemos estamos
uniendo dos o ms tablas en una resultante.

select * from posts, users where posts.post_id = users.id

En Rails podemos hacer lo mismo al establecer relaciones, sin


embargo debemos especicar el tipo de relacin.

Agregar relaciones en los modelos nos permite movernos de un


objeto a sus objetos relacionados de forma sencilla, los modelos
requieren que

Por ejemplo si establecemos una relacin de uno a muchos entre


los posts y los comentarios podemos hacer lo siguiente:

1
2

@post = Post.first
@post.comments

Establecer la relacin en el modelo nos genera automticamente


mtodos que nos permiten obtener los objetos relacionados de

forma sencilla, en este captulo estudiaremos esas asociaciones y


sus ventajas.

Relaciones 1 a 1
Cuando existen relaciones 1 a 1 se dice que uno de los modelos
tiene a otro, y el otro le pertenece al primero.

Si tenemos un usuario y una compaa semnticamente hace ms


sentido decir que la compaa le pertenece al usuario, y que el
usuario tiene una compaa, debemos tratar de ocupar las
relaciones siempre en el sentido semntico, si no se hace muy
difcil programar si lo que se lee no tiene sentido en espaol.

Entonces crearemos dos modelos usuario y compaia

1
2

rails g model user name:string


rails g model company name:string user:references

Corremos las migraciones:

rake db:migrate

y ahora la parte nueva consiste en agregar las relaciones a los


modelos.

En el modelo user.rb agregaremos

has_one :company

En el modelo company.rb nos daremos cuenta que ya se encuentra


la relacin, esto se agreg automticamente ya que Rails lo dedujo
a partir del user:references

belongs_to :user

**Reiniciar con reload!** Al igual que cuando agregamos


los campos validadores en los modelos, al agregar
referencias debemos reiniciar la consola, despus de
hacer cambios en el modelo, lo podemos hacer ocupando
el comando reload!

Debemos tener cuidado de por cual lado va la clave


fornea y no invertir el orden por error, por eso siempre
es bueno dibujar el diagrama

Si lo implementamos bien dentro de rails console podemos


obtener un usuario a partir de la empresa y viceversa, de esta
forma.

Para probarlo, vamos a crear un usuario y una empresa

1
2

u = User.new(name:"Rick Hunter")
c = Company.new(name:"SDF-1 Macross", user:u)

Qu fue lo que obtuvimos al hacer las relaciones? ganamos el


obtener mtodos getter automticos para obtener objetos a partir
del otro, ejemplo:

1
2
3
4

u.company
=> #<Company id: nil, name: "SDF-1 Macross", user_id: nil, created_at: nil, updated_at:
c.user
#<User id: nil, name: "Rick Hunter", created_at: nil, updated_at: nil>

Borrando una relacin en cascada.


Podemos borrar campos en cascada, esto quiere decir que al
borrar un usuario se borrar su compaa automticamente, esto
lo podemos hacer agregando dependent: :destroy en el modelo
dentro de la relacin.

has_one :company, dependent: :destroy

Reiniciar la consola.

Para probarlo esta vez si necesitamos guardar los datos, (si no


estn no tiene sentido borrarlos)

1
2
3

u = User.create(name:"Rick Hunter")
c = Company.create(name:"SDF-1 Macross", user:u)
u.destroy

Veremos que justo despus de hacer el destroy obtendremos:

1
2
3
4
5
6

2.2.2 :004 > u.destroy


(0.1ms) begin transaction
SQL (0.2ms) DELETE FROM "companies" WHERE "companies"."id" = ? [["id", 1]]
SQL (0.1ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 1]]
(0.9ms) commit transaction
=> #<User id: 1, name: "Rick Hunter", created_at: "2015-11-30 05:26:26", updated_at: "2

Donde del SQL claramente se distinguen dos borrados, primero el


de la compaa asociada y luego el de el usuario que se intenta
destruir.

Graficando las relaciones


Existe una gema en Rails que permite generar los diagramas de
clases del modelo de datos.

Esta gema requiere de que tengamos instalado graphviz en


nuestro computador, esto se puede lograr con:

En OSX

brew install graphviz

En Ubuntu

sudo apt-get install graphviz

gem 'railroady', group: :development

y nalmente bundle.

Luego podemos generar el diagrama de modelos corriendo la


lnea.

railroady -M | dot -Tsvg > doc/models.svg

O podemos generar todos los diagramas con la lnea.

rake diagram:all

Los diagramas se guardan en la carpeta doc del proyecto Rails.

Ahora podemos diagramar al relacin que creamos en el proyecto


anterior.

Se necesita la base de datos para generar el documento,


por lo mismo es necesario correr las migraciones antes
del comando.

Models diagram
Date: Nov 28 2015 - 02:15
Migration version: 20151128080750
Generated by RailRoady 1.3.1
http://railroady.prestonlee.com

User
id :integer
name :string
phone :string
created_at :datetime
updated_at :datetime

Company
id :integer
users_id :integer
created_at :datetime
updated_at :datetime

Relaciones de 1 a n
Implementar relaciones 1 a n es muy similar a implementar
relaciones 1 a 1, para probarlo vamos a crear un proyecto nuevo y

crear dos modelos, pelculas y categoras, una categora tiene


muchas pelculas, una pelcula le pertenece a una categoras

1
2

rails g model category name:string


rails g model movie name:string category:references

luego

rake db:migrate

Una cosa es que en la base de datos existan las relaciones y otra


muy distinto es que existan en el modelo, el modelo es una capa
de abstraccin por sobre los datos, por lo mismo al igual que en las
relaciones 1 a 1 ahora debemos agregar las relaciones.

para

agregar

belongs_to

las

:category

relaciones
al

modelo

debes
de

agregar
pelculas

has_many :movies al modelo de categora.

en el modelo category.rb

has_many :movies

y luego abriremos el modelo de movies.rb para revisar si se cre la


relacin, debera aparecer:

belongs_to :category

Observar que el has_many viene acompaado con el nombre de la


tabla en plural, y es por que son varias pelculas y no una, esta es
una de las grandes razones porque debemos programar en ingls
en lugar de en espaol, porque las reglas de ineccin (para pasar

de singular a plural) no son las mismas en ambos idiomas.

Para probarlo tenemos que entrar a rails console y luego crear una
categora y una pelcula.

1
2

c = Category.create(name: "Children and Family")


m = Movie.create(name:"Hercules", category: c)

Al igual que en las relaciones 1 a 1 podemos hacer

m.category

Si observamos el diagrama veremos que es similar al de 1 a 1 pero


en este caso vemos varias echas para representar que la relacin
es de 1 a n.

Lo nuevo en este tipo de relacin es que categora no tiene una


pelcula, tiene muchas, por lo mismo el getter para obtener todas
las pelculas es en plural.

1
2

c.movies
#=> #<ActiveRecord::Associations::CollectionProxy [#<Movie id: 1, name: "Hercules", cate

El mtodo build
Este mtodo es parecido al new pero permite automticamente
instanciar un objeto hijo.

m = c.movies.build(name:"Wall-E")

De esta forma automticamente el objeto nuevo tiene como clave


fornea la id del padre, debemos tener cuidado porque an
cuando el objeto no est guardado en la base de datos si
mostramos ahora todas las pelculas veremos esta nueva, para
guardarlo en la base datos es el mismo proceso.

m.save

Mltiples relaciones de 1 a n
Son muy pocas las aplicaciones que tengan nicamente una o dos
tablas, una aplicacin pequea puede tener entre 5 y 10 y una
grande puede tener cientos, muchas veces existen relaciones entre
varias de estas simultneamente.

En el caso tpico de un blog tenemos usuario, comentarios y posts,


ya estudiamos un posible modelo para esto en el captulo 13, y era:

Generamos los tres modelos:

1
2
3

rails g model user name:string


rails g model post content:text user:references
rails g model comment content:text post:references user:references

Corremos las migraciones con

rake db:migrate

las relaciones en los respectivos modelos.

En el modelo de usuario agregamos:

1
2

has_many :comments
has_many :posts

En el modelo de posts:

1
2

has_many :comments
belongs_to :user

En el modelo de comments:

1
2

belongs_to :user
belongs_to :comment

agregamos

Podemos rescatar los post de un usuario de la misma forma que


hicimos previamente, pero para realizar las pruebas vamos a tener
que agregar algunos datos, lo podemos hacer en el archivo seed.rb
o directamente en la consola.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

u1 = User.create(name:"Usuario 1")
u2 = User.create(name:"Usuario 2")
u3 = User.create(name:"Usuario 3")
p1 = Post.create(content:"Articulo 1 del usuario 1", user: u1)
p2 = Post.create(content:"Articulo 2 del usuario 1", user: u1)
p3 = Post.create(content:"Articulo 3 del usuario 1", user: u1)
Comment.create(content:"Comentario del usuario 1 en el articulo 1", user: u1
Comment.create(content:"Comentario del usuario 1 en el articulo 1", user: u1
Comment.create(content:"Comentario del usuario 2 en el articulo 1", user: u2
Comment.create(content:"Comentario del usuario 2 en el articulo 1", user: u2
Comment.create(content:"Comentario del usuario 3 en el articulo 1", user: u3
Comment.create(content:"Comentario del usuario 3 en el articulo 1", user: u3
Comment.create(content:"Comentario del usuario 2 en el articulo 1", user: u2
Comment.create(content:"Comentario del usuario 3 en el articulo 2", user: u3

Para ingresar los datos ahora debemos escribir rake db:seed

1
2

user = User.first
user.posts

Tambin

podemos

user.comments

obtener

todos

los

comentarios

con

pero que pasa si lo que necesitamos es un

poco ms complejo, por ejemplo como podramos obtener todos


los comentarios que ha recibido en todos sus posts un usuario.

Intuitivamente podramos decir user.posts.comments pero sera


un error, puesto que user.posts es un active record association, es
como un array de posts y este no tiene un mtodo .comments

Podemos seleccionar primero todos los posts de ese usuario y


luego buscar dentro de estos los comentarios.

1
2

comments = []
user.posts.each {|p| comments << p.comments }

El problema es que ahora comments es un array y no podemos


realizar ltros sucesivos o utilizar otros mtodos del Activerecord

Como solucin podemos utilizar dos joins, o sea unimos la tabla de


comments con la de post, y la de post con la de usuarios.

Comment.joins(:post => :user)

Esto se transforma en una consulta SQL con:

SELECT "comments".* FROM "comments" INNER JOIN "posts" ON "posts"."id" = "comments"

Pero hay una forma mucho ms sencilla, que es utilizando


has_many_through

Has many :through


Para llegar a una tercera tabla a travs de una segunda podemos
utilizar has_many :through en el modelo, la sintaxis es la siguiente:

has_many :tabla_final, through: :tabla_intermedia

Ahora en el caso de nuestro modelo de usuarios ya tenemos una


relacin llamada comentarios, por lo que no podemos crear otra
igual, pero en este caso podemos crear una relacin con nombre,

para eso especicamos el nombre de la relacin y el destino, ya


que no lo puede calcular slo.

has_many :received_comments, through: :posts, source: :comments

Ahora

si

recargamos

la

consola,

podemos

utilizar

user.received_comments y obtendremos todos los comentarios


que ha recibido un usuario.

Preguntas
1. Para qu sirve relacionar los modelos?
2. Por qu es necesario agregar una columna en la base de datos para
hacer la asociacin?
3. Cul es la conversin respecto a la columna de asociacin?
4. Cul es la diferencia entre una relacin de 1 a 1 y una de 1 a n?
5. Cmo se puede agregar nombre a una relacin?
6. Para que sirve el mtodo .build?
7. Cmo se pueden borrar en cascada los elementos de una relacin?

20) Testing unitario de modelos


Objetivos
1. Entender el concepto de testing unitario
2. Aprender a construir tests
3. Aprender a correr tests automatizados con rake
4. Entender como funcionan los xtures
5. Identicar los Pitfalls del testing unitario

Los modelos son el esqueleto de la aplicacin y por lo tanto es muy


importante que desarrollemos tests automatizados para probar
nuestra lgica del negocio.

En este captulo aprenderemos a crear set de datos y correr


pruebas para probar que nuestros modelos fallan y decimos que
debemos probar que las cosas fallan, porque la lgica del testing
nace del intentar hacer fallar algo.

Si intentamos demostrar que algo funciona probablemente solo


incluiremos algunas pruebas necesarias y no demostraremos que
nuestro cdigo es slido y lo que es peor obviaremos aquellas que
son ms importantes y representan un riesgo para nuestra
aplicacin.

La Carpeta de Tests
Dentro de un proyecto Rails todos los test se encuentran en la
carpeta test de nuestra aplicacin, dentro de ella encontraremos

subcarpetas para distintos tipos de tests, en este captulo


utilizaremos slo dos de ellas, models y xtures los cuales sirven
para hacer unit testing de los modelos.

continuacin

abordaremos

los

tests

bsicos

ocupando

nicamente la carpeta de models y luego aprenderemos a manejar


los test agregando los xtures.

Estructura de un test de modelo


Cada archivo de test viene ya con la estructura, la cual consiste en
la clase que hereda de los TestCase y dentro de esta van todos los
pruebas que deniremos para nuestro modelo.

Formas de escribir un test


Hay dos formas de escribir los tests dentro de este archivo, la
primera es creando mtodos que empiecen con test_

1
2
3

def test_the_truth
assert true
end

La segunda es de la siguiente forma:

1
2
3

test "the truth" do


assert true
end

Esta ltima forma es la ms utilizada puesto que es ms clara, pero


cualquiera de las dos sirve y cumple el mismo propsito

El mtodo assert
Los asserts que vimos en las lneas anteriores son armaciones,
con un assert le estamos diciendo a Ruby que una cosa tiene que
ser de una forma signica que hay un error y que nos debe dar
una aviso.

Entonces con estos asserts podemos hacer pruebas como


determinar si un objeto con ciertos campos puede ser guardado
con xito en la base de datos.

Un ejemplo tpico sera preguntar si un objeto puede ser guardado


con cierto valor nulo, supongamos entonces que tenemos un
modelo de posts donde estos no pueden ser guardados sin ttulo.
nuestro test sera:

1
2
3
4

test "Post cannot be save without title" do


p = Post.new(title: nil)
assert_not p.valid?
end

En el caso expuesto podemos ver que creamos un post sin ttulo y


luego le decimos a rails que esto no debera ser vlido.

En el ejemplo no era necesario pasar el parmetro title: nil, se realiz uncamanete para
resaltar que el post no tena ttulo.

Tipos de asserts
Para realizar los tests existen diversos tipos de asserts, estos
vienen

de

la

librera

https://github.com/seattlerb/minitest

Algunos de los ms usados son:

de

minitest

Assertion

Purpose

assert( test, [msg] )

Asegura que el resultado sea verdad.

assert_not( test, [msg] )

Asegura que el resultado sea falso.

assert_equal( expected, actual, [msg]

Asegura que el primer parmetro sea igual al

segundo.

assert_not_equal( expected, actual,

Asegura que el primer parmetro sea distinto al

[msg] )

segundo.

assert_nil( obj, [msg] )

Asegura que el objeto sea nulo.

assert_not_nil( obj, [msg] )

Asegura que el obejto no sea nulo.

assert_empty( obj, [msg] )

Asegura que el objeto est vaco.

assert_not_empty( obj, [msg] )

Asegura que el objeto no est vaco.

assert_includes( collection, obj, [msg]

Asegura que un objeto est en una coleccin de

datos.

assert_not_includes( collection, obj,

Asegura que el objeto no est en la coleccin de

[msg] )

datos.

Cada test contiene uno de los assets vistos en la tabla o incluso


ms de uno, la lgica del testing unitario es probar una sola cosa a
la vez lo que suele traducirse a hacer slo un assert por test pero
esto no es estrictamente necesario, lo que si es importante es
aislar los casos de pruebas, en los modelos se suele hacer un
assert por mtodo

Cuando ejecutemos los tests obtendremos un reporte por cada


assert que no haya cumplido.

Corriendo tests
Todos estos tests se corrern cuando ejecutemos el comando

rake en la terminal dentro de la carpeta del proyecto, al correrlo


en un proyecto nuevo (o donde no tengamos tests denidos,
obtendremos:

1
2
3

# Running:
Finished in 0.007509s, 0.0000 runs/s, 0.0000 assertions/s.

Observando los resultados


Al correr los tests obtendremos resultados por cada assert, estos
resultados pueden ser de tres tipos:

error (E)
fail (f)
success (.)

De esta forma si obtenemos despus de rake:

Signica que tenemos dos failures y un error y tres success.

Interpretando los resultados


Un . signica que el test tuvo xito, o sea que el assert cumple con
lo pedido, por ejemplo si tenemos un assert_equal 2,2 estamos
diciendo que dos debe ser igual a dos, y eso es cierto por lo que el
resultado es un success.

Una f signica que el test fall, o sea que el assert no se cumpli


por ejemplo se esperaba que algo fuera verdad y no lo fue, o que
se cumpliese una igualdad y esta no se cumpli.

Un error signica que hay un error de tipeo o se llama a un


mtodo que todava no ha sido implementado, es muy comn
tener errores cuando hacemos los tests antes que el resto del
cdigo.

El Entorno de testing
Los test corren en un entorno especial llamado testing y no
comparten los datos con la base de datos de desarrollo o
produccin.

Los datos de la base de datos de Testing se cargan desde unos


archivos llamados xtures y de ah van a parar a la base de datos
cada vez que corremos los test.

Un detalle muy importante es que hay mltiples tests, la base de


datos es poblada con los xtures por cada test, de este forma si
borramos un objeto dentro de un test podremos disponer de el en
el test siguiente

Los fixtures
Los xtures son los archivos con los datos que se cargaran en la
base de datos en cada prueba. Cuando creamos un modelo se crea
automticamente un xture para ese modelo, los xtures estn en
formato yaml por lo que su indentacin es clave.

1
2
3
4
5

one:
content: MyText
two:
content: MyText

Un secreto para no tener problemas con la indentacin es cuidar las tabulaciones, son
crticas en YML, utilizar dos espacios, en lugar de tabs, esto requiere congurar el
editor.

Por defecto los xtures incluyen valores para todos los campos que
fueron creados con el generador de Rails al momento de generar
el modelo, si los valores los agregamos despus con una migracin
no aparecern aqu, pero los podemos agregar manualmente.

Cargando los fixtures en los tests


Si tenemos un test del tipo:

1
2
3

test "probar que el usuario sea valido" do


assert user.valid?
end

De dnde sacamos el valor de user?, la respuesta es de los


xtures, para poder utilizar un xture en un test lo hacemos de la
siguiente forma:

1
2
3
4

test "probar que el usuario sea valido" do


user = users(:one)
assert user.valid?
end

El mtodo Setup
El mtodo setup permite crear un mtodo comn para todos los
otros que se carga antes que cualquier otra cosa, y sirve casi
exclusivamente para cargar los xtures, si todos los mtodos
cargan xtures distintos no tiene sentido, pero en muchos casos

un xture se comparte en todos o casi todos los mtodos, en ese


caso es muy til para evitar repetir cdigo.

El mtodo setup va as:

1
2
3
4
5
6
7
8
9
10

require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = users(:one) #cargamos el fixture
end
test "probar que el usuario sea valido" do
assert @user.valid?
end

En la lnea 5 se ve que escribimos @user en lugar de user,


debemos ocupar variables de instancias, si fueran locales no
podramos compartirlas entre los distintos mtodos.

Los fixtures no gatillan los callbacks


Imaginemos que tenemos un modelo llamado Grupo, y cada vez
que se crea un grupo, se crea automticamente un post.

Entonces dentro del modelo de grupos tendramos algo como:

1
2
3
4
5

after_create :create_welcome_post
def create_welcome_post
new_post = self.posts.build(name: "Bienvenidos al grupo")
new_segment.save
end

Al cargar los xtures de grupo veremos que no se gatilla el callback

test "Welcome post must be created after the group is created" do

2
3
4

group = groups(:one)
assert_equal 1, group.posts.count
end

Este test fallar porque los xtures no cargan los


callbacks, son datos que se guardan directamente en la
base de datos

Cmo probamos un callback?

Muy sencillo, sin xtures

1
2
3
4

test "Welcome post must be created after the group is created" do


group = Group.create()
assert_equal 1, group.posts.count
end

Existe otra opcin que no abordaremos en este libro, que consiste


en ocupar factories, la gema factory_girl resuelve este problema
https://github.com/thoughtbot/factory_girl

Fixtures y relaciones
Tambin hay que destacar que los xtures no incluyen por defecto
el campo id, pero podemos agregarlo, esto podra ser til a la hora
de probar relaciones pero existe una forma mejor de probar
relaciones.

Otros tipos de testing

Existen otros tipos de testing como el test funcional que nos


ayudar a probar los controllers, y los test de integracin que
ayudan a probar los requisitos del sistema, los tests funcionales los
abordaremos ms adelante en este libro, sin embargo no veremos
tests de integracin pero se dejan mencionados para que el lector
pueda investigar sobre el tema.

Preguntas
1. Por qu los tests los debemos escribir con la lgica de fallar?
2. Qu son los xtures?
3. Qu implica que un test sea unitario?
4. En qu entorno corren los tests?
5. Cmo utilizamos un xture dentro de un test?
6. Cmo podemos correr los tests?
7. Cul es la diferencia entre escribir def test_insert y test "insert"
do?
8. Qu signica assert?
9. Cuntos assert pueden haber en un test?
10. Para qu sirve el mtodo valid?
11. Cul es la diferencia entre assert assert_equal?
12. Cmo se cargan los xtures en un test?
13. Para qu sirve el mtodo setup en los tests?
14. Por qu el mtodo setup se ocupan variables de instancias en lugar de
locales?

21) Creando un Blog


Construyendo el modelo
En este captulo construiremos un blog aplicando lo aprendido,
utilizaremos como base el modelo que hemos generado en
captulos anteriores del blog para realizar los tests del modelo.

Entonces y a modo de repaso, construimos el proyecto desde cero.

rails new blog_with_testing --database=postgresql

Corremos rake db:migrate para crear el archivo schema y para


probar si tenemos algn conicto con la conguracin de la base
de datos

rake db:migrate

Luego creamos los modelos, por ahora todo campo que no sea un
fk, ser un string, esto nos ayudar a demostrar un test pronto.

1
2
3

rails g model user name email role


rails g model post title content user:references
rails g model comment content user:references post:references

Construyendo los tests


Los test del modelo consisten principalmente en lgica del negocio,
entonces necesitamos saber que vamos a validar antes de hacer
los test

Partiremos validando que un usuario no se pueda crear un usuario


sin email

Test para modelo sin campo presente


Para evitar errores de cualquier tipo primero tenemos que partir
de xtures vlidos ya que estos se cargan directamente en la base
de datos. Si vamos a crear una regla de negocio que valida la
existencia de un email y luego creamos usuarios sin email vamos a
tener problemas.

1
2
3
4
5
6
7
8
9

one:
name: MyString
email: MyString
role: MyString
two:
name: MyString
email: MyString
role: MyString

La lgica de la validacin de la existencia de un campo siempre es


similar, utilizamos un xture vlido, anulamos el campo que
debera ester presente, esperamos que eso sea invlido.

1
2
3
4
5
6
7
8
9
10

require 'test_helper'
class UserTest < ActiveSupport::TestCase
test "user should have an email" do
user = users(:one)
user.email = nil #decimos que no tiene email
assert_not user.valid?, "el usuario no puede no tener email"
end
end

O, utilizando el mtodo setup (es exactamente lo mismo):

1
2
3
4
5
6
7
8

def setup
@user = users(:one) #cargamos el fixture
end
test "user should have an email" do
@user.email = nil #decimos que no tiene email
assert_not @user.valid?
end

Al correr los tests con rake obtendremos:

1
2
3
4

1) Failure:

UserTest#test_user_should_have_an_email [/Users/gonzalosanchez/Proyectos/clases/bootcamp
Expected true to be nil or false

Adems a los mtodos assert les podemos pasar un parmetro


ms con el mensaje.

assert_not user.valid?, "user must have an email"

De esta forma al correr los test obtendremos algo ms especco .

1
2

UserTest#test_user_should_have_an_email [/Users/gonzalosanchez/Proyectos/clases/bootcamp
user must have an email

Ahora, nosotros hicimos un test que dice que un usuario no


debera ser vlido si el email es nulo, entonces al fallar nos est
diciendo que hay usuarios con email nulo, por lo tanto nuestro
cdigo no lo est validando, y eso es obvio porque no lo hemos
implementado.

Cmo lo arreglamos?

Agregando una validacin de presencia en el model de usuario al


campo email, entonces en el modelo de usuario:

validates :email, presence: true

Si ahora corremos los tests veremos:

1
2
3

Running:
.
Finished in 0.071091s, 14.0665 runs/s, 14.0665 assertions/s.

Es normal que los test fallen, que fallen signica que una
funcionalidad no est implementada o est mal implementada, y
eso es genial porque nos permite encontrar problemas en nuestro
cdigo y adems nos permite ordenar nuestro trabajo.

A esta losofa de testear primero y codear despus se llama Test


Driven Development o TDD

Test para modelo con campo nico


Ahora si se pide que el mail sea nico, qu necesitamos?

La respuesta es otro test

1
2
3
4

test "user cant have a duplicatd email" do


u = User.new(email: @user.email)
assert_not u.valid?, "user with email #{u.email} is repeated"
end

Por qu en esta ocasin no ocupamos un xture?, nuevamente, el


secreto para sobrevivir al testing es que todos los xtures deben
ser vlidos porque estos se cargan directamente a la base de
datos, y si uno de ellos parte rompiendo un regla ya tendremos
problemas.

Entonces como probamos que est repetido?, creamos uno nuevo


(no lo guardamos todava) y vericamos si con los datos que tiene
es vlido.

Test para probar la relacin


Para probar una relacin necesitamos dos xtures, uno para cada
elemento de la relacin, hay varias formas de hacer esta prueba, lo
que nosotros haremos es crear un usuario y un post, un usuario
tiene muchos posts, por lo mismo podramos preguntarnos si
dentro de todos los posts del usuario se encuentra el xture, para
eso probaremos con el mtodo assert_includes

Entonces, primero agregamos el id del usuario al xture de usuario

1
2
3
4
5
6
7
8
9

one:
id: 1
name: MyString
email: MyString
role: MyString
two:
id: 2
name: MyString

10
11

email: MyString
role: MyString

Luego agregamos el id del usuario como user_id al xture de post

1
2
3
4
5
6
7
8
9

one:
title: MyString
content: MyString
user_id: 1
two:
title: MyString
content: MyString
user_id: 2

Con estos datos estamos diciendo que en nuestra base de datos


hay dos usuarios y dos post, y cada usuario tiene su post, ahora
dentro del test veremos si el usuario one efectivamente tiene el
post one.

Luego creamos nuestro test.

1
2
3

test "user has posts" do


assert_includes @user.posts, posts(:one), "user one should have post one"
end

Al correr los test obtendrs un error en lugar de un failure, los


failures suceden cuando el assert no cumple la expectativa, los
errores por diversos motivos, pero en este caso es porque el
usuario no tiene el mtodo posts

1
2
3

UserTest#test_user_has_posts:
NoMethodError: undefined method `posts' for #<User:0x007ff0dcce96e8>
test/models/user_test.rb:19:in `block in <class:UserTest>'

Cmo lo arreglamos?, agregando el mtodo post a usuarios, por


ahora lo haremos manualmente sin agregar la relacin para
demostrar el failure, y devolveremos un arreglo vaco ya que
assert_includes espera como primer parmetro una coleccin de
datos y un array vaco cumple con eso.

model user.rb

1
2
3
4
5
6
7
8

validates :email, presence: true


validates :email, uniqueness: true
def posts
[]
end
end

Ahora al correr los tests obtendremos:

Expected [] to include #<Post id: 980190962, title: "MyString", content: "MyString", use

Claro, nosotros esperamos que el usuario tenga dentro del arreglo


un post, y un arreglo vaco no tiene nada adentro, ahora
borraremos el mtodo posts y montaremos la relacin para
arreglarlo.

1
2
3
4
5

class User < ActiveRecord::Base


validates :email, presence: true
validates :email, uniqueness: true
has_many :posts
end

y voila, hemos pasado la prueba porque ahora si se incluye.

Running:

2
3
4
5

...
Finished in 0.121981s, 24.5939 runs/s, 32.7919 assertions/s.

Test de pertenencia
Ahora queremos hacer el test inverso, o sea asegurarnos que un
post le pertenece a un usuario, si existe el mtodo obtendremos o
un usuario en caso de que haya o nil en caso de que no, y error en
casa de que no exista el mtodo, para eso dentro de los posts
escribiremos el test.

1
2
3

test "post has user" do


assert_not_nil posts(:one).user, "Post one should have a user"
end

Test para diferencias de tiempo


Validando que un post no se pueda actualizar despus de 5
minutos. Primero haremos el test, para eso lo primero que
necesitamos es revisar que el xture de post incluye la fecha en
que fue guardado, como nos interesa que no se pueda guardar
despus de 5 minutos crearemos 2, uno hace 6 minutos que no
podemos actualizar y uno de hace 4 minutos que si podremos.

1
2
3
4
5
6
7
8
9
10
11
12

require 'test_helper'
class PostTest < ActiveSupport::TestCase
# test "the truth" do
#
assert true
# end
test "cant update after 5 minutes" do
@post = posts(:one)
assert_not @post.valid?
end
test "can update before 5 minutes" do

13
14
15
16
17

@post = posts(:two)
assert @post.valid?
end
end

Al correr los tests veremos que slo uno de ellos falsa, esto se
debe a que no existe ninguna validacin, por lo que al intentar
guardar el post despus de 5 minutos funciona y no debera
funcionar.

Ahora tenemos que crear la validacin, como no existe una para


tiempos tendremos que crear una custom como aprendimos
previamente en el libro.

El modelo de post quedara as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class Post < ActiveRecord::Base


belongs_to :user
has_many :comments
validate :time_limit
def time_limit
delta = Time.now - self.created_at
if delta > 5.minutes
errors.add(:expiration_date, "can't update")
end
end
end

La diferencia de tiempos es simplemente al resta de cuanto tiempo


ha pasado desde que se cre y si es mayor a 5 minutos

Desafios

En un modelo de datos donde hay usuario y cada usuario puede


tener hasta 6 fotos, se pide.

Crear los modelos de usuario y fotos


Crear las asociaciones
Asegurar que el usuario tiene la relacin con fotos
Asegurar que el usuario puede tener un mximo de 6 fotos
Asegurar que el modelo foto tenga la relacin con usuario

En un modelo de datos donde hay artculos, item pedido, orden de


compra y usuarios, donde un una orden de compra se relaciones
con muchos artculos y viceversa y una orden de compra le
pertenece a un usuario. se pide:

Crear el modelo de artculo, item_pedido, orden_compra y


usuario
Establecer las relaciones acorde al enunciado.
Asegurar que un artculo no pueda estar en un pedido si tiene
stock igual a cero
Asegurar que un artculo no puede ser pedido si tiene stock
igual a cero
Asegurar que la orden de compra tiene al menos un item
pedido.
Asegurar que el item_pedido le pertenezca a una orden de
compra
Asegurar que desde el modelo de usuario se pueda rescatar las
ordenes de compra.
Asegurar que desde el modelo de usuario se pueda rescatar los
itemes pedidos
Asegurar que desde el modelo de usuario puede rescatar los
artculos a travs de las respectivas relaciones

Preguntas
1. Por qu es bueno que el archivo xtures contenga nicamente xtures
vlidos sea vlido?
2. Qu es TDD?
3. Por qu para probar el test de email hicimos assert_not user.valid? en
lugar de assert user.valid?, no deberamos revisar que fuera vlido?
4. Qu son los errores y cuales son la diferencia con los failures?
5. Por qu en en el caso de la validacin de la relacin usuario -> posts
obtuvimos un error en vez de un failure? Qu se hizo para resolverlo?

22) MVC
Objetivos
1. Introducir el concepto REST
2. Aprender a utilizar el scaold de Rails
3. Conocer los 7 mtodos REST
4. Introducir el helper de form_for

Ruby on Rails es un framework MVC, eso quiere decir que separa la


programacin en 3 capas lgicas importantes, modelo, vista y
controlador, ya vimos supercialmente estas capas en los
captulos anteriores, en este vamos a aprender un poco ms de
ellas e integrarlas.

Para empezar debemos entender el diagrama bsico.

Un navegador se conecta a localhost:3000/ruta , lo que est


sucediendo ah es que el navegador se est conectando con el
servidor que est corriendo localmente en nuestro computador,
ese mismo que nosotros levantamos corriendo el comando
rails s , es por eso que necesita estar andando para que
podamos entrar a la pgina localhost:3000.

Cuando el servidor de Ruby on Rails detecta una conexin, las


cules desde ahora en adelante llamaremos request lo que hace
es vericar el archivo de rutas, este le indica que controller debe
resolver el request y con que mtodo, dentro de este a veces se
hacen llamados al modelo (y a veces no) y luego se mostrar la
vista.

La ventaja de esta separacin en tres capas es que hace fcil la


revisin de un cdigo de un tercero y coordinar tareas con el
equipo de trabajo, como podemos ver en los siguientes ejemplos:

1. Si se necesita actualizar una vista en particular para un


diseador es fcil saber cual archivos es, se revisa la URL, luego
se revisa a que controller y que mtodo redirige y luego busca el
archivo dentro de views con el mismo nombre dentro del
mtodo.
2. Si hay una falla dentro de una pgina que no carga, el primer
responsable es el controller, si este no tiene nada fuera de la
normal o est todo ok, revisamos el modelo y luego la vista.
3. Toda la lgica de negocios est en el modelo, si una regla del
negocio slo tendremos que actualizar este archivo, en caso de
que agreguemos un campo o saquemos uno tendremos que
revisar la vista pertinente pero slo eso.

En resumen la arquitectura MVC hace fcil la coordinacin de la


construccin y la mantencin de una aplicacin web y es por eso
que tan usada en la industria de desarrollo, tanto as que incluso
se est ocupando para el desarrollo de apps no webs.

Arquitectura REST
Rails es un framework MVC, pero hay otro concepto de desarrollo
embebido en la lgica de la construccin de proyectos, y es el de
REST.

La idea de REST es denir recursos independientes para construir


una aplicacin, cada uno de estos recursos tiene 7 mtodos
primarios.

1. Index: Muestra todos los elementos de un recurso.


2. Show: Muestra el detalles de un recurso
3. New: Muestra el formulario para crear un nuevo recurso
4. Create: Maneja la lgica de la creacin de un nuevo recurso

5. Edit: Muestra el formulario para editar un recurso


6. Update: Maneja la lgica para la actualizacin de un recurso
7. Delete: Borra un recurso especco.

La forma ms fcil de empezar a trabajar con REST en Rails es con


el generador de scaold.

Scaffold
En Rails existe una forma de crear todos los mtodos rest para el
recurso que queramos, es ms el scaold crea:

1. El modelo (y cada vez que se crea un modelo viene


acompaado de la migracin para crear la table en la base de
datos)
2. El controller con los mtodos REST
3. Las rutas para cada uno de los mtodos REST.
4. Las vistas necesaria para cada uno de los mtodos rest
5. Tests y xtures bsicos
6. Un archivo SCSS para ingresar sass (una variante de CSS)
7. Un archivo coeescript (una variante de Javascript)
8. Un archivo de helper para delegar la lgica del controller

Para probarlo creemos un proyecto nuevo, donde haremos una


una lista de tareas.

rails g scaffold tasks task:string

Como resultado obtendremos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

invoke
create
create
invoke
create
create
invoke
route
invoke
create
invoke
create
create
create
create
create
create
invoke
create
invoke
create
invoke
invoke
create
create
invoke
invoke
create
invoke
create
invoke
create

active_record
db/migrate/20150815182842_create_tasks.rb
app/models/task.rb
test_unit
test/models/task_test.rb
test/fixtures/tasks.yml
resource_route
resources :tasks
scaffold_controller
app/controllers/tasks_controller.rb
erb
app/views/tasks
app/views/tasks/index.html.erb
app/views/tasks/edit.html.erb
app/views/tasks/show.html.erb
app/views/tasks/new.html.erb
app/views/tasks/_form.html.erb
test_unit
test/controllers/tasks_controller_test.rb
helper
app/helpers/tasks_helper.rb
test_unit
jbuilder
app/views/tasks/index.json.jbuilder
app/views/tasks/show.json.jbuilder
assets
coffee
app/assets/javascripts/tasks.coffee
scss
app/assets/stylesheets/tasks.scss
scss
app/assets/stylesheets/scaffolds.scss

El ltimo archivo creado scaolds.scss es una base para mostrar


los errores de los formularios y otros detalles menores, si no es de
tu agrado puedes borrarlo o modicarlo.

Cmo lo probamos?

Primero tenemos que saber cuales son las rutas, eso lo podemos
lograr con rake routes

1
2
3
4
5
6
7
8
9

Prefix Verb
URI Pattern
Controller#Action
tasks GET
/tasks(.:format)
tasks#index
POST
/tasks(.:format)
tasks#create
new_task GET
/tasks/new(.:format)
tasks#new
edit_task GET
/tasks/:id/edit(.:format) tasks#edit
task GET
/tasks/:id(.:format)
tasks#show
PATCH /tasks/:id(.:format)
tasks#update
PUT
/tasks/:id(.:format)
tasks#update
DELETE /tasks/:id(.:format)
tasks#destroy

Viendo las rutas descubrimos que para entrar al index de tasks


tenemos que entrar a localhost:3000/tasks

Y si no hemos realizado la migracin obtendremos el siguiente


error.

Esto se debe a que cada vez a que Rails detecta que hay una

migracin que an no ha sido corrida, pero resolverlo simplemente


debemos correr el comando rake db:migrate en el terminal.

rake db:migrate

Si la migracin es correcta, deberamos obtener:

1
2
3
4

== 20150815182842 CreateTasks: migrating ======================================


-- create_table(:tasks)
-> 0.0024s
== 20150815182842 CreateTasks: migrated (0.0025s) =============================

Luego volvemos a entrar a la pgina web y podremos ver el index


de task.

Desde ah podemos ingresar tareas nuevas, ver el detalle de cada


una, en la misma vista se provee un link para crear un task nuevo,
si utilizamos el inspector de elementos o hacemos hover con el
mouse sobre el link veremos que el link apunta hacia tasks/new.

Para saber exactamente que pgina es lo compararemos con el


resultado de rake routes, de esta forma sabremos que task/new
utiliza el controller tasks con el mtodo new, entonces para
saber que accin se realiza revisaremos el controller y mtodo
respectivo.

1
2
3
4
5
6
7

class TasksController < ApplicationController


# GET /tasks/new
def new
@task = Task.new
end

end

Si observamos el mtodo veremos que no hay mucha lgica, solo


se crea un objeto task vaco que se ocupar para guardar el tasks

Sin embargo en la vista app/views/tasks/new.html.erb nos


encontraremos con sorpresas.

1
2
3
4
5

<h1>New Task</h1>
<%= render 'form' %>
<%= link_to 'Back', tasks_path %>

Render es una instruccin que no habamos visto en los


controllers, pero no en las vistas aunque tiene el mismo propsito,
cargar otro vista, cuando de una vista se carga otra se le denomina
vista parcial, para sealizar que un archivo es una vista parcial se
utiliza como prejo un _, como es el ejemplo del archivo
app/views/tasks/_form.html.erb .

Veamos el archivo generado.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<%= form_for(@task) do |f| %>


<% if @task.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@task.errors.count, "error") %> prohibited this task from being
<ul>
<% @task.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :task %><br>
<%= f.text_field :task %>

17
18
19
20
21

</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

Lo primero que vemos es el helper form_for, es similar al


form_tag slo que este es capaz de determinar las rutas para
guardar o actualizar a partir de un objeto, en cambio en form_tag
hay que especicarlas como lo hicimos en el captulo previo.

En caso de que al querer guardar un objeto haya un error lo


mostraremos, de eso se encargan las lneas 2 a la 12. y entre las
lneas 14 a 17 se crea el eld task y de la 18 a la 20 el botn de
envo.

La ruta del formulario se calcula con el objeto, si el objeto task es


nuevo apuntar al mtodo create si es antiguo a update, revisemos
el mtodo create.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# POST /tasks
# POST /tasks.json
def create
@task = Task.new(task_params)
respond_to do |format|
if @task.save
format.html { redirect_to @task, notice: 'Task was successfully created.'
format.json { render :show, status: :created, location: @task }
else
format.html { render :new }
format.json { render json: @task.errors, status: :unprocessable_entity
end
end
end

El

mtodo

create

parte

Task.new(task_params)

con

algo

muy

interesante

esta es la clave para entender los

form_for todos los datos del formulario se envan en un hash, esto

lo podemos ver en los logs de rails server

1
2
3
4
5
6
7
8

Started POST "/tasks" for 127.0.0.1 at 2015-08-17 13:22:29 -0500


Processing by TasksController#create as HTML
Parameters: {"utf8"=>"", "authenticity_token"=>"PIaNf9h4CoHU/lxBWTSH3Kn9lY/LekE6fmPH8
(0.1ms) begin transaction
SQL (0.2ms) INSERT INTO "tasks" ("task", "created_at", "updated_at") VALUES (?, ?, ?)
(0.8ms) commit transaction
Redirected to http://localhost:3000/tasks/3
Completed 302 Found in 3ms (ActiveRecord: 1.1ms)

Los parameters son un hash, este hash ya lo hemos ocupado antes


cuando dentro de un mtodo del controller utilizbamos
render json: params

en lugar da cargar una vista, lo que

estamos haciendo ah es mostrar el hash params.

Dentro de este hash existe el key task (que corresponde al recurso)


y que a su vez es un hash donde vienen todos los campos creados
y modicados.

Utilizando la misma idea del captulo anterior podemos mostrar el


hash en lugar de

1
2
3

def create
render json: params
end

Si queremos leer el hash dentro del controller lo podemos hacer


utilizando params, por ejemplo para mostrar todos los campos
pasados del formulario para ingresar una tarea lo podemos hacer
con:

1
2
3

def create
render json: params[:task]
end

Qu
@task

es

el
=

task_params

creado

Task.new(task_params)

en

el

scaold?

task_params

es

params[:task] pero limpiado, a este tcnica de limpieza se le


denomina Strong parameters

Strong parameters
Veremos en las ltimas lneas del task controller el siguiente
cdigo.

1
2
3
4

# Never trust parameters from the scary internet, only allow the white list through.
def task_params
params.require(:task).permit(:task)
end

Ah se dene un mtodo task_params, donde dice que del key task


solo permitiremos un key dentro, en este caso se le llama task,
pero si hubiese otro, por ejemplo el id de un usuario podramos
obtenerlo agregndolo a la lista.

1
2
3

def task_params
params.require(:task).permit(:task, :user_id)
end

Todo lo que no est en esa lista ser declarado un parmetro ilegal,


y ser borrado, ahora para probar esto agregaremos un campo
nuevo a la base de datos, un nombre del responsable de la tarea.

Como el modelo ya est creado no corresponde utilizar


rails g model

en su lugar crearemos una migracin que

agregue el campo a la base de datos. Esto se hace as:

rails g migration addUserToTask user:string

Si la migracin fue creada con xito deberamos obtener:

1
2

invoke
create

active_record
db/migrate/20150819042641_add_user_to_task.rb

Si abrimos el archivo creado, deberamos ver lo siguiente

1
2
3
4
5

class AddUserToTask < ActiveRecord::Migration


def change
add_column :tasks, :user, :string
end
end

Si add_column no aparece probablemente se debe a que no


alternaste la minsculas y maysculas en

addUserToTask

correctamente, la notacin utilizada es de lower camel case.

Si el archivo es igual al mostrado entonces procedemos a correr la


migracin, ah es cuando realmente se modica la base de datos.

rake db:migrate

Obtendremos como resultado:

1
2
3
4

== 20150819042819 AddUserToTask: migrating ====================================


-- add_column(:tasks, :user, :string)
-> 0.0015s
== 20150819042819 AddUserToTask: migrated (0.0016s) ===========================

Nota sobre las migraciones

Las migraciones son el medio por el cual modicamos la base de


datos, jams deberamos modicar la base de datos sin una
migracin, como queremos agregar usuarios responsables a la
tarea haremos justo eso, crear una migracin que agregue un
usuario a la tarea, donde el usuario es un string.

Discutiremos ms profundamente el tema de las migraciones en


un prximo captulo, si te interesa tener ms informacin puedes
revisar la documantacin ocial de migraciones

Probando los strong params


Ahora tenemos un nuevo campo user, vamos a probar los strong
params agregando el campo user al formulario pero sin agregarlo
a

los

strong

params.

Para

eso

abriremos

el

archivo

app/views/tasks/_form.html.erb y dentro agregaremos el


nuevo eld.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<%= form_for(@task) do |f| %>


<% if @task.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@task.errors.count, "error") %> prohibited this task from being
<ul>
<% @task.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :task %><br>
<%= f.text_field :task %>
</div>

<div class="field">
<%= f.label :user %><br>

22
23
24
25
26
27
28
29

<%= f.text_field :user %>


</div>

<div class="actions">
<%= f.submit %>
</div>
<% end %>

Recargaremos la pgina y llenaremos el formulario.

Para probar si funcion abriremos desde el bash la consola Rails

rails c

Adentro buscaremos el ltimo task para ver si fue ingresado


correctamente.

Task.last

1
2
3

Task Load (0.7ms) SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."id" DESC LIMIT 1
=> #<Task id: 1, task: "Tarea 1", created_at: "2015-08-19 04:43:40", updated_at: "20152.2.2 :002 >

La razn del por qu user es nil, la podemos encontrar en los logs


de rails server (en el tab secuestrado)

1
2
3
4
5
6
7
8
9
10

Started POST "/tasks" for 127.0.0.1 at 2015-08-18 23:43:40 -0500


Processing by TasksController#create as HTML
Parameters: {"utf8"=>"", "authenticity_token"=>"iGFUq2LShLo5NZV+Cjd5dmtnnz48MbSde
Unpermitted parameter: user
(0.2ms) begin transaction
SQL (1.6ms) INSERT INTO "tasks" ("task", "created_at", "updated_at") VALUES (?, ?
(0.8ms) commit transaction
Redirected to http://localhost:3000/tasks/1
Completed 302 Found in 30ms (ActiveRecord: 2.6ms)
</pre>

Podemos ver una lnea que dice Unpermitted parameter: user, y la


razn es sencilla, user no est dentro de los strong parameters,
dentro del controller de tasks tendremos que agregar:

params.require(:task).permit(:task, :user)

y luego si podremos crear tasks con usuarios.

Preguntas
1. Cuales son las ventajas de la separacin de capas MVC en una aplicacin
web?
2. Cul es la diferencia entre crear un scaold y un controller?

3. Cules son los 7 mtodos de la arquitectura REST en Rails?


4. Qu es una vista parcial?
5. Cul es la diferencia entre form_for y form_tag?
6. Cul es la diferencia entre task_params y params[:task]
7. Para qu sirven los strong params?

23) El archivo de rutas a fondo


Objetivos
1. Aprender a pasar parmetros a los mtodos del controller utilizando el
archivo routes.
2. Entender como funciona los query strings.
3. Estudiar las diferencias entre los bloques collection y member.
4. Introducir el concepto de rutas anidadas.

Introduccin al archivo de rutas


El archivo routes.rb
El archivo routes.rb que se encuentra dentro de la carpeta cong
contiene todas las URL que sern accesibles por los usuarios del
sistema, si la URL no est especicada aqu entonces simplemente
no ser accesible.

El archivo routes se encarga de mapear la ruta con un controller y


un mtodo dentro de ese controller, de esta forma cada URL
corresponde a una accin que se encuentra denida dentro de un
mtodo de instancia del controller especicado.

Entonces la pgina /controller_x/pages apuntar al mtodo


pages dentro del controller controller_x

Es posible tambin que el nombre de la ruta no corresponda con el


nombre del mtodo, o omitir el nombre del controller, eso lo

veremos ms adelante.

Otra cosa que puede hacer el archivo routes es pasar parmetros,


los parmetros en la url son unas especies de variables, de esa
forma

el

programador

puedes

especicar

la

pgina

/controller_x/pag/20 y eso llamar al mtodo pag asignando


el valor 20 a una variable.

Rake routes
rake es un programa de administracin de tareas (parecido a ANT),
por ejemplo rake db:mgirate para migrar la base de datos, en
particular la tarea importante que utilizaremos respecto a las rutas
es rake routes, esto nos mostrar todas las rutas existentes en el
sistema. Veremos ms detalles de Rake routes despus de crear
nuestra primera ruta sin parmetros.

Rutas sin parmetros


El caso ms comn para crear rutas es el de sin parmetros, estas
son tiles cuando queremos pginas estticas o incluso cuando
queremos obtener todos los elementos de un recurso, por ejemplo
todos los post, otro caso til por ejemplo es el perl del usuario
conectado actualmente, por otra parte obtener el perl de un
usuario especco requerira el id de ese usuario.

Las rutas sin parmetros pueden ser con controller (la forma ms
habitual) y sin el controller, no tiene ventajas omitir el controller
pero hay personas que lo preeren por esttica de la URL.

Ruta con controller


Ejemplo: localhost:3000/controller\_x/about\_us

Para crear una ruta especicando el controller debemos agregar al

archivo routes una nueva direccin de la siguiente forma:


get 'controller/accion' , como por ejemplos

get 'pages/about_us'

para probar que esto funcione en el terminal dentro del mismo


proyecto escribiremos rake routes

Rake routes a fondo


Despus de digitar el siguiente comando en la consola

rake routes

Obtendremos lo siguiente: (Esto es un proyecto nuevo, si lo


hacemos en otro probablemente obtendremos muchas ms rutas)

Prex

Verb

URI Pattern

pages_about_us

GET

/pages/about_us(.:format)

Controller#Action
pages#about_us

Pero, Qu es cada columna?

Prefix: El prefijo
El prex muestra una variable que se crear para referirnos a esa
pgina, es un prejo porque hay dos sujos posibles, que son _url
y _path, el primero muestra la ruta absoluta y el segundo la ruta
relativa.

De esta forma si nosotros dentro de una vista de Rails imprimimos

<%= pages_about_us_url %> obtendremos la direccin a esa


pgina.

Verb, el verbo
Los request dentro de HTTP se hacen en conjunto con un verbo, en
la mayora de los casos este verbo es GET, pero tambin existen
otros como, POST, PUT, PATCH, DELETE, etc.

El verbo depende del llamado que se haga, por ejemplo cuando


nosotros ingresamos una url en el navegador automticamente
estamos haciendo un GET, pero cuando enviamos un formulario lo
ms probable es que estemos haciendo un POST.

Ahora, para que una ruta sea vlida tienen que suceder dos cosas,
la primera es que la url tiene que estar habilitada, la segunda es
que el verbo tiene que corresponder a la URL. En el caso anterior si
intentamos

hacer

un

localhost:3000/pages/about\_us

POST

entonces obtendremos

que la ruta no existe, puesto que nuestra ruta habilitada es GET.

Ahora si dentro del archivo routes cambiamos el get por post, al


hacer rake routes obtendremos:

Prex

Verb

URI Pattern

pages_about_us

POST

/pages/about_us(.:format)

Controller#Action
pages#about_us

URI pattern
el URI pattern es la direccin URL en conjunto con el formato,
ejemplo .html, .json, .xml, .js. Los controllers en RAILS por defecto
responden a mltiples formatos pero es posible limitarlos a uno o
slo algunos.

URI Controller#Action
nalmente la ltima columna indica quien es el controller y el
mtodo responsable cuando se carga una pgina web, esto es muy
til para debuggear errores pues nos indica en que parte del
cdigo est probablemente el problema.

Ahora que ya entendemos ms sobre rake routes podemos


proceder a ver aplicaciones interesantes del archivo routes.

El archivo routes a fondo


Ruta sin controller
Primero aclarar que una ruta sin controller no quiere decir que no
exista un controller, es slo que no aparece en la URL.

Por ejemplo: localhost:3000/about_us

Para obtener una ruta sin controller tenemos que especicar


dentro del archivo routes cul es el controller de la siguiente
forma:

get 'about_us' => 'pages#about_us'

Luego con rake routes obtendremos lo siguiente:

Prex

Verb

URI Pattern

about_us

GET

/about_us(.:format)

Controller#Action
pages#about_us

Claramente para que esta pgina funcione tiene que existir el

controller pages con un mtodo llamado about_us y la respectiva


vista.

Cambiando el prefijo
Es posible cambiar el prex de una url utilizando el smbolo :as
cuando denimos la ruta, ejemplo:

get 'pages/about_us', as: "hola"

Al hacer rake routes nos dar como resultado:

Prex

Verb

URI Pattern

hola

GET

/pages/about_us(.:format)

Controller#Action
pages#about_us

No es conveniente cambiar el nombre del prex arbitrariamente,


pero en algunos casos Rails no ser capaz de distinguir el prex de
forma automtica y en esos casos lo necesitaremos.

Crear una ruta con un parmetro


Una ruta con parmetro se ve as: localhost:3000/user/:id

Para crear una ruta con parmetros tenemos que hacer dos cosas,
la primera es especicar que la ruta tiene un parmetro y
nombrarlo, esto lo hacemos as:

get 'user/:id'

la segunda

parte consiste en especicar el controller como lo hicimos


previamente, ya que la ruta no tiene la misma estructura que antes
del tipo

controller/pages

controller, uniendo quedara as:

get 'user/:id' => 'users#profile'

ahora hay que especicar el

al realizar rake routes nos daremos cuenta que en esta ocasin el


prex desapareci, como no saba cul era el controller y el
mtodo, menos an tiene la capacidad de autodeterminar un
prejo, entonces recurrimos a especicarlo con el smbolo :as

Prex

Verb

URI Pattern

prole

GET

/user/:id(.:format)

Controller#Action
users#prole

Dentro de las vistas y controllers de Rails ahora debemos pasar el


parmetro id, esto es sencillo, en lugar de utilizar prole_path
utilizaremos prole_path(id: 5)

Para utilizar este parmetro dentro del controller o dentro de una


vista podemos utilizar el hash params de la siguiente forma

params[:id]

Luego este id lo podemos utilizar para buscar registros en nuestra


base de datos o para cualquier funcin que estimemos
conveniente.

Rutas anidadas
Es posible anidar una ruta, o sea una subruta que depende de una
ruta padre. Veamos un ejemplo para entenderlo mejor:

Supongamos que tenemos una red social y queremos obtener


todos los matches de un usuario en especco, pero la ruta
user/:id

ya la estamos ocupando para mostrar el perl,

podramos crear otra que diga

matches/:id

pero luego

matches tambin apuntara al controller de user y eso ya empieza


a causar confusin, entonces una forma de solucionarlo de forma

que quede limpio es anidando la ruta para que resulte en


user/:id/matches

Esa ruta la podemos especicar as:

get 'user/:id/matches' => 'users#matches', as: "matches"

Crear una ruta que recibe dos parmetro


Los parmetros dentro de las rutas tienen nombres, por ejemplo
en el caso anterior estbamos nombrando a la variable :id, pero
qu sucede si queremos utilizar una segundo parmetro, por
ejemplo para ver el detalle del match de un usuario en especco.
Eso lo podemos implementar de la siguiente forma:

get 'user/:user_id/match/:match_id' => 'match#show', as: 'match_show'

Las rutas anidadas y formularios anidados son muy tiles a la hora


de construir aplicaciones, ahondaremos en ms detalle en un
prximo captulo.

Query Strings
Los Query String son los parmetros libres que recibe una
direccin, estos parmetros no se declaran en el archivo routes.rb,
si no que los pasa el usuario cuando realiza un request.

Todo lo que est a continuacin del caracter ? en la url son query


strings, seguramente los has visto cuando haces bsquedas en
google.

Entonces

al

pasar

una

ruta

localhost:3000/users?sort=ok

al

navegador

del

tipo

podemos rescatar tanto el

valor ok simplemente utilizando

desde el

params[:sort]

controller o desde la vista, en este caso nos devolvera ok

Para qu sirven los Query Strings?

Principalmente sirven en una pgina que puede realizar otras


acciones, que no son crticas en los datos, como por ejemplo
paginacin, ordenamiento, etc o bsquedas como lo hace google.

Query String o Parmetro

Para escoger cual utilizar lo que tienes que hacer es preguntarte, que pasa si por algn
motivo no se pasa el valor, si es importante que nunca pase el parmetro lo ponemos
como parte de la ruta, si no, query string

Resources
Hasta

el

momento

hemos

trabajado

todas

las

rutas

individualmente, pero hay forma de crear rutas REST de forma


automtica para un recurso, para eso basta con especicar dentro
del archivo routes:

resources :nombre_recurso

Prex
users

Verb

URI Pattern

Controller#Action

GET

/users(.:format)

users#index

POST

/users(.:format)

users#create

new_user

GET

/users/new(.:format)

users#new

edit_user

GET

/users/:id/edit(.:format)

users#edit

user

GET

/users/:id(.:format)

users#show

PATCH

/users/:id(.:format)

users#update

PUT

/users/:id(.:format)

users#update

DELETE

/users/:id(.:format)

users#destroy

En este caso cuando el prejo no sale mencionado es porque es el


mimo que el anterior.

Limitando las rutas con :only y :except

Es posible adems limitar las rutas rest generada utilizando los


smbolo :only o :except, en el primer caso las rutas quedan
limitadas a las especifcadas, y en el segundo son todas menos las
especicadas. Ejemplo:

resources :users, only: [:index, :create, :update]

Prex
users

Verb

URI Pattern

Controller#Action

GET

/users(.:format)

users#index

POST

/users(.:format)

users#create

PATCH

/users/:id(.:format)

users#update

PUT

/users/:id(.:format)

users#update

Member vs Collection
Adems es posible agregar acciones extras manteniendo la
estructura REST, para eso existen los members y los collections.

Member
Los bloques member permiten agregar acciones a un elemento

especco del recurso, por ejemplo a las rutas rest de user


podramos agregar el mtodo prole de la siguiente forma:

1
2
3
4
5

resources :users, only: [:index, :create, :update] do


member do
get 'profile'
end
end

Fuera de las rutas de user que vimos previamente, obtendremos


una nueva la de prole.

Prex

Verb

URI Pattern

prole_user

GET

/users/:id/prole(.:format)

Controller#Action
users#prole

Los bloques member hacen ms sencillo el agregar rutas nuevas


sin tener que especicar con :as y establecer que controller y que
mtodo, pues es el mtodo respectivo del recurso.

Collection
Los bloques de collections son muy similares a los de members,
pero con una diferencia, el collection no tiene un parmetro :id
involucrado, pues se utiliza principalmente para generar una
accin que involucra a todos los recursos, o a uno independiente
del id.

Por ejemplo crear un mtodo para:

Borrar todos los posts, sera collection.


Obtener el perl del usuario logeado, no depende del id de user
y por lo tanto sera con collection.
Dar un acceso especial a un usuario especco sera member

Votar por un post sera member. (requiere del id del post)

Los bloques collection se utilizan de la misma forma que los


members

1
2
3
4
5

resources :users, only: [:index, :create, :update] do


collection do
get 'profile'
end
end

Con el cdigo anterior obtendramos:

Prex

Verb

URI Pattern

prole_users

GET

/users/prole(.:format)

Controller#Action
users#prole

Preguntas
1. Cul es la diferencia entre un bloque member y un collection?
2. Si se agrega el recurso books, cul ser la ruta para crear uno nuevo?
3. Para crear un ruta para un mtodo que nos muestre todos los libros
parecidos a un libro en particular, ocuparas una ruta con un parmetro o
un Query String?
4. Cul es la diferencia entre las rutas que terminan con _path y las que
terminan con _url?

24) Rutas anidadas y mtodos REST anidados


Objetivos
1. Profundizar en el concepto de rutas anidadas
2. Aprender a crear rutas anidadas para un recurso

Introduccin a rutas anidadas


Las rutas anidadas son cuando ponemos una ruta dentro de otra,
en el captulo anterior lo explicamos supercialmente, en este lo
veremos a fondo debido a que son muy tiles para construir
aplicaciones.

Para entender bien la idea detrs de las rutas anidadas debemos


repasar el propsito de la arquitectura REST, esta sirve para
normalizar la manipulacin de recursos, o sea utilizar las misma
estructura de URL ya sea para verlos, crearlos, modicarlos o
borrarlos.

Si queremos manipular un recurso que depende de otro podemos


inventar muchas rutas para manipularlo, o, podemos ocupar la
arquitectura REST y poner este nuevo recurso anidado dentro del
otro.

De esta forma si tenemos comentarios que depende de posts los


podemos anidar as:

resources :posts do

2
3

resources :comments
end

De esta forma al hacer rake routes obtendremos:

Prex
post_comments

Verb

URI Pattern

Controller#Action

GET

/posts/:post_id/comments(.:format)

comments#index

POST

/posts/:post_id/comments(.:format)

comments#create

new_post_comment

GET

/posts/:post_id/comments/new(.:format)

comments#new

edit_post_comment

GET

/posts/:post_id/comments/:id/edit(.:format)

comments#edit

post_comment

GET

/posts/:post_id/comments/:id(.:format)

comments#show

PATCH

/posts/:post_id/comments/:id(.:format)

comments#update

PUT

/posts/:post_id/comments/:id(.:format)

comments#update

DELETE

/posts/:post_id/comments/:id(.:format)

comments#destroy

GET

/posts(.:format)

posts#index

POST

/posts(.:format)

posts#create

new_post

GET

/posts/new(.:format)

posts#new

edit_post

GET

/posts/:id/edit(.:format)

posts#edit

post

GET

/posts/:id(.:format)

posts#show

PATCH

/posts/:id(.:format)

posts#update

PUT

/posts/:id(.:format)

posts#update

DELETE

/posts/:id(.:format)

posts#destroy

posts

Los bloques collection y member tambin aplican a rutas anidadas,


si hay que tener el cuidado de escoger el recurso completo, por
ejemplo si quisiramos agregar un mtodo para votar un post
deberamos escribir:

1
2
3
4
5
6

resources :posts do
member do
get 'vote'
end
resources :comments
end

Si quisiramos crear uno para votar un comentario, deberamos


agregar:

1
2
3
4
5
6
7

resources :posts do
resources :comments do
member do
get 'vote'
end
end
end

Finalmente si queremos que ambos tengan un mtodo para votos,


simplemente combinamos ambas formas.

1
2
3
4
5
6
7
8
9
10

resources :posts do
member do
get 'vote'
end
resources :comments do
member do
get 'vote'
end
end
end

Al hacer rake routes podremos ver que adicionalmente a las rutas


de posts y comentarios asociados a los posts, aparecen:

Prex
vote_post

Verb
GET

URI Pattern
/posts/:id/vote(.:format)

Controller#Action
posts#vote

vote_post_comment

GET

/posts/:post_id/comments/:id/vote(.:format)

Es perfectamente posible cambiar los verbos dentro de los bloques


member y collection, por ejemplos se podra cambiar los GET por
POST o por PATCH.

Creando un scaffold anidado


Vamos a suponer para este ejercicio que tenemos un modelo que
tiene usuarios y tweets

Un tweet le pertenece a un usuario y un usuario puede tener


muchos tweets.

En el caso de los usuarios lo podemos crear como scaold o no,


pero slo vamos a utilizar el index y el show, ya que nos
enfocaremos en el recurso anidado, el resto se vio en captulos
anteriores.

1
2

rails g scaffold user name:string


rails g model tweet tweet:string user:references

Luego corremos las migraciones con rake db:migrate


Agregamos las relaciones en los modelos.
Agregamos al menos un usuario desde la consola
y creamos el controller de tweets.

Antes de siqueira generar el mtodo index para los tweets vamos a


comenzar creando los tests.

comments#vote

Testeando una ruta anidada


Para testear el acceso a la ruta index, podemos hacer:

1
2
3
4

test "should get index" do


get :index
assert_response :success
end

Pero las rutas anidadas tienen una subruta adentro, o sea el index
de

los

tweets

depende

de

un

usuario

especco

/users/2/tweets entonces debemos testear as:

1
2
3
4

test "should get index" do


get :index, user_id: 1
assert_response :success
end

Agregando la clave primaria


Para evitar errores tambin
debemos agregar id: 1 dentro del
fixture de users.

Ahora en ambos casos (aunque slo el segundo es el correcto)


obtendremos el siguiente error:

1
2
3
4

1) Error:
TweetsControllerTest#test_should_get_index:
ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"
test/controllers/tweets_controller_test.rb:8:in `block in <class:TweetsControllerTes

Agregamos la ruta:

1
2
3

resources :users do
resources :tweets
end

Corremos los tests nuevamente, obviamente fallar, puesto que


todava no tenemos el mtodo en el controller. Observemos el
error:

1
2
3
4

1) Error:
TweetsControllerTest#test_should_get_index:
AbstractController::ActionNotFound: The action 'index' could not be found for TweetsCont
test/controllers/tweets_controller_test.rb:8:in `block in <class:TweetsControllerTes

Agreguemos el mtodo index al tweet controller, y de paso


agreguemos la vista para evitar errores que ya hemos estudiado.

1
2

def index
end

No habramos tenido que pasar por esto si hubisemos creado el


controller como rails g controller tweets index

Si ahora corremos rake, veremos que ya todos los tests pasan, sin
embargo si quitamos el user_id del test veremos que el test deja de
funcionar.

1
2
3
4

1) Error:
TweetsControllerTest#test_should_get_index:
ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"
test/controllers/tweets_controller_test.rb:8:in `block in <class:TweetsControllerTes

Esto se debe a que la ruta que nosotros creamos es anidada, por lo

tanto debemos testear contra una ruta anidada.

Obteniendo y mostrando los resultados.


En el controller bajo una ruta anidada normalmente generamos n
objetos, uno par cada ruta padre y el objeto (u objetos) del mismo
controller.

Para que funcione tenemos que haber establecido las relaciones


belongs_to y has_many en el modelo.

1
2
3
4

def index
@user = User.find params[:user_id]
@tweets = @user.tweets
end

Y luego dentro de la vista mostramos los tweets con:

1
2
3

<% @tweets.each do |t| %>


<%= t.tweet %>
<% end %>

Es necesario obtener el objeto user?, no, otra opcin de hacer lo


mismo sera.

@tweets = Tweet.where(user_id: params[:user_id])

Pero es limpio, elegante y despus nos servir para hacer los


redireccionamientos,

adems

debemos

recordar

que

los

controllers deben tener la menor cantidad de lgica posible, y esto


incluye evitar hacer consultas a la base de datos directamente.

El detalle del tweet


Partamos desde los tests:

1
2
3
4

test "should get show" do


get :show, user_id: 1
assert_response :success
end

la ruta ya est agregada, agregamos todas las rutas REST, ahora en


el controller agregamos el mtodo show.

1
2
3

def show
@tweet = Tweet.find(params[:id])
end

y nalmente lo mostramos en la vista, se deja de tarea para el


lector.

Formulario para un nuevo tweet


Partimos con el test:

1
2
3
4

test "should get new" do


get :new, user_id: 1
assert_response :success
end

Agregamos el mtodo en el controller:

1
2
3
4

def new
@user = User.find params[:user_id]
@tweet = @user.tweets.build
end

La forma que creamos el tweet en el paso anterior puede parecer


rara, de hecho es muy comn, y es lo mismo que @tweet =
Tweet.new (bueno, casi lo mismo, en ciertas ocasiones, como
cuando ocupemos relaciones polimrcas, veremos la utilidad de
ocupar el build)

ya tenemos el controller, veamos la vista:

1
2
3
4

<%= form_for [@user, @tweet] do |f| %>


<%= f.text_area :tweet %>
<%= f.submit :enviar %>
<% end %>

Recordemos que form_for determina automticamente las rutas


para el objeto, pero en este caso las rutas de tweet estn anidadas
dentro de user, para lograr que form_for construya las rutas
automticamente con recursos anidados tenemos que pasarles los
recursos en un arreglo. El orden de los parmetros del arreglos es
el mismo que el de anidamiento, de afuera hacia dentro, o sea si es
del tipo users/2/tweets el orden sera [@user, @tweet]

El formulario no funcionar porque no tenemos el mtodo create


todava.

Creando el mtodo create


Primero un test bsico, simplemente para determinar si logramos
crear el tweet o no, ms adelante veremos el redireccionamiento.

1
2
3
4
5

test "should create tweet" do


assert_difference('Tweet.count') do
post :create, tweet: { tweet: "hola, soy un tweet" }, user_id: 1
end
end

Como se observa el test se hace igual que otros de create, pero al


igual que en todo lo anidado debemos especicar un user_id
existente.

Ahora creamos el mtodo:

1
2
3
4
5

def create
@tweet = Tweet.new(tweet_params)
@tweet.save
render nothing: true
end

Y corremos el test.

Manejando el redireccionamiento del create.


No existe un lugar especco a donde debamos redireccionar,
depende de los requisitos de la plataforma, pero un lugar muy
comn, es al show del objeto padre, en este caso user.

1
2
3
4
5
6
7

test "should create tweet" do


assert_difference('Tweet.count') do
post :create, tweet: { tweet: "hola, soy un tweet"}, user_id: 1
end
assert_redirected_to user_path(users(:one))
end

Ahora tenemos que modicar el mtodo create para lograr la


redireccin:

1
2
3
4
5
6
7

def create
@user = User.find params[:user_id]
@tweet = Tweet.new(tweet_params)
@tweet.user = @user
@tweet.save
redirect_to @user
end

Las lneas 3 y 4 pueden ser sustituidas por:

@tweet = @user.tweets.build(tweet_params)

Se deja de tarea crear los mtodos de edit y update que son


iguales al de new y create. y el de delete que no presenta mayor
dicultad que el show.

Preguntas
1. Qu verbos se pueden especicar dentro de un bloque member?
2. Qu sucede si tenemos un recurso anidado, ejemplo post y comments, y
ponemos al recurso de post only:create?
3. Si tenemos los recursos article y review, cmo deberamos escribir el
form_for para crear un review nuevo?

25) Relaciones N a N
Objetivos
1. Aprender a manejar las relaciones N a N en Ruby on Rails.
2. Descubrir como crear tablas de asociacin intermedia.
3. Evitar problmeas al borrar asociaciones.
4. Entender las diferencias entre hasandbelongstomany y has_many
:through.

Introduccin a relaciones N a N
Tenemos una relacin de N a N cuando un elemento de una tabla
est relacionados con muchos elementos de la otra y viceversa.

Ejemplo tags y pelculas

Una pelcula, por ejemplo Terminator, puede tener el tag Accin, y


el tag Suspenso, y luego el tag Suspenso puede a su vez ser de la

pelcula Terminator y otras pelcula.

En bases de datos relacionales no es posible modelar directamente


una relacin de muchos a muchos, pero se puede lograr utilizando
una tabla intermedia.

La tabla intermedia guarda las referencias a las otras tablas.

Entonces siempre que queramos implementar una relacin de


muchos a muchos en bases de datos relacionales necesitaremos 3
tablas.

Ahora para llevar esto a Rails hay dos formas:

1. Ocupando las relaciones has_and_belongs_to_many


2. Ocupando las relacion has_many :through

La gran diferencia entre la primera y la segunda es que la primera


no requiere de un modelo intermedio y la segunda si, ahora no
confundir modelo con tabla, en ambos casos se requieren 3 tablas.

Creando modelos con has_and_belongs_to_many


Vamos a empezar con un proyecto nuevo, en el vamos a crear dos
modelos, el de pelculas (movies) y el de los gneros.

Primero creamos los modelos.

1
2

rails g model movie name:string


rails g model tag name:string

Luego creamos la tabla intermedia, para eso podemos crear una


migracin y luego crear la tabla manualmente dentro o, podemos
utilizar una ayuda de Rails para crear la migracin que cree la tabla
automticamente.

Para eso:

rails g migration CreateJoinTable movies tags

El archivo de migracin generado debera contener algo as:

1
2
3
4
5
6
7
8

class CreateJoinTable < ActiveRecord::Migration


def change
create_join_table :movies, :tags do |t|
# t.index [:movie_id, :tag_id]
# t.index [:tag_id, :movie_id]
end
end
end

Este archivo generado puede cambiar dependiendo de la versin


de Rails.

Despus de correr la migracin con

rake db:migrate , el

archivo schema.rb debera contener:

1
2
3

create_table
t.string
t.datetime
t.datetime

"movies", force: :cascade do |t|


"name"
"created_at", null: false
"updated_at", null: false

4
5
6
7
8
9
10
11
12
13
14
15
16

end
create_table "movies_tags", id: false, force: :cascade do |t|
t.integer "movie_id", null: false
t.integer "tag_id",
null: false
end
create_table
t.string
t.datetime
t.datetime
end

"tags", force: :cascade do |t|


"name"
"created_at", null: false
"updated_at", null: false

En muchas ocaciones la tabla intermedia no tiene un id como clave


primario, puesto que no es necesaria, adems esta tabla nunca la
accederemos directamente, siempre buscaremos por movie, o por
tag.

Con las tablas hechas y los modelos creados ahora procedemos a


establecer las relaciones.

1
2
3

class Tag < ActiveRecord::Base


has_and_belongs_to_many :movies
end

1
2
3

class Movie < ActiveRecord::Base


has_and_belongs_to_many :tags
end

Podemos probar las relaciones desde la consola de Rails


Movie.new.tags

nos debera devolver un coleccin vaca, y

Genre.new.movies tambin.

Agregando elementos

Hagamos otra prueba para explicar como agregar gneros a las


pelculas.

1
2
3
4

m = Movie.new(name:"la pelicula de los muchas a muchos")


g = Tag.new(name:"Accin")
m.tags << g
m.save

Obtendremos como resultado:

1
2
3
4
5

(4.7ms) begin transaction


SQL (1.7ms) INSERT INTO "movies" ("name", "created_at", "updated_at") VALUES
SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES
SQL (0.4ms) INSERT INTO "movies_tags" ("movie_id", "tag_id") VALUES (?, ?
(3.5ms) commit transaction

Observar que se gener una transaccin donde se guardan tres


cosas simultneamente, la pelcula, el tag, y la asociacin, luego
podemos rescatar los tags de esa pelcula con tags .

Creemos un seed para hacer las pruebas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Movie.destroy_all
Tag.destroy_all
m = Movie.new(name:"Terminator")
g = Tag.new(name:"Accin")
m.tags << g
m.save!
m = Movie.new(name:"Terminator 2")
g = Tag.new(name:"Accin")
m.tags << g
m.save!
m = Movie.new(name:"Buscando a Nemo")
g = Tag.new(name:"Animacin")
m.tags << g
m.save!

18
19
20
21
22
23
24

m = Movie.new(name:"Cars")
g = Tag.new(name:"Animacin")
m.tags << g
g = Tag.new(name:"Accin")
m.tags << g
m.save!

Borrando la asociacin:
Cmo podemos borrar la asociacin?

Para borrar debemos tener el objeto que queremos borrar, por


ejemplo:

1
2
3

tag = Tag.first
movie = Movie.first
movie.tags.delete(tag)

Se debe tener el cuidado de no hacer movie.tags[0].delete, pues en


ese caso se borrara el tag pero no la asociacin, esto en SQLite es
posible pero en PostgreSQL generara un error por la violacin de
clave fornea.

Tests para la relacin


Como todo sucede dentro de una transaccin podemos probarlo
con un simple assert, tambin podramos contar los resultados.

1
2
3
4

test "may have many genres" do


3.times do |i|
@movie.genres << Genre.create(name: "Genero #{i}")
end

5
6

assert @movie.save
end

Implementando relaciones con has_many :through


Existe otra forma distinta de hacer relaciones de muchos a muchos
y es con has_many :through, la implementacin es distinta, pero el
resultado es idntico, con ciertas excepciones que discutiremos en
esta seccin.

A continuacin veremos como hacer la relacin con has_many


through usando el mismo ejemplo de pelculas y tags

1
2

rails g model movie name:string


rails g model tag name:string

Y ahora crearemos un modelo que los una a los otros dos, con:

rails g model movie_tag movie:references tag:references

Corremos las migraciones con rake db:migrate y procedemos


a crear las relaciones:

Agregamos las relaciones al modelo de movie:

1
2
3
4

class Movie < ActiveRecord::Base


has_many :movie_tags
has_many :tags, through: :movie_tags
end

Revisamos las relaciones de movie_tags, (deberan haberse

agregado solas gracias al generador de Ruby)

1
2
3
4

class MovieTag < ActiveRecord::Base


belongs_to :movie
belongs_to :tag
end

Agregamos las relaciones al modelo Tag

1
2
3
4

class Genre < ActiveRecord::Base


has_many :movie_tags
has_many :movies, through: :movie_tags
end

Entramos a rails c y procedemos a probar las relaciones:

Desde pelculas:

1
2

Movie.new.movie_tags #
Movie.new.tags

Desde Tag:

1
2

Tag.new.movie_tags #
Tag.new.movies

Al hacerlo obtendremos colecciones vacas, ya que una pelcula


nueva no tiene gneros y lo mismo en el orden inverso. Si en
alguno de los casos obtenemos un error, hay que revisar los
plurales en los modelos.

1
2

movie = Movie.new(name:"Avengers")
tag = Tag.new(name:"Action")

3
4

movie.tags << tag


movie.save

Obtendremos como resultado:

1
2
3
4
5
6

(0.2ms) begin transaction


SQL (3.6ms) INSERT INTO "movies" ("name", "created_at", "updated_at") VALUES
SQL (0.6ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES
SQL (0.9ms) INSERT INTO "movie_tags" ("movie_id", "tag_id", "created_at"
(8.1ms) commit transaction
=> true

Se puede observar que la utilizacin es la misma y el resultado


tambin, independiente del mtodo utilizado, entonces Cundo
conviene utilizar uno o el otro?

Has_many through vs Has_and_belongs_to_many


EL principal motivo para utilizar has_many through es cuando se
requiere agregar reglas del negocio al modelo intermedio, En el
caso propuesto de pelculas y tags se puede pero no hay necesidad
de hacerlo, pero existen otros casos donde si sera til, como por
ejemplo: un carro de compra donde el carro contenga tems
deseados de una tienda, luego este carro puede estar pagado o no
y si se naliza la compra se da por pagado el carro, esta sera la
regla de negocio.

Ejercicio practico de uso


Aprovechando la relacin creada anteriormente, con has_many
:through, implementaremos la asignacin de gneros a una
pelcula, para eso crearemos el formulario, mtodo y ruta
necesarios.

Primero agregaremos pelculas y tags a nuestra base de datos,


para eso modicaremos nuestro archivo seed.

1
2
3
4
5
6
7
8
9
10
11

tags = ['Action','Comedy','ScyFy','Horror','Drama','Adventure','Thriller','Documental'
tags.each do |t|
Tag.create(name: t)
end
(1..50).each do |m|
random_tag1 = Tag.all.sample
random_tag2 = Tag.all.sample
Movie.create(name: "Pelcula #{m}", tags: [random_tag1, random_tag2])
end

Luego crearemos el mtodo index y edit de pelculas, para eso


primero crearemos el controller con el mtodo index y show desde
el generador con:

rails g controller movies index show

Cambiaremos el archivo de rutas, las rutas generadas por:

resources :movies

Luego dentro del mtodo edit del controller de movies,


obtendremos la pelcula y los generos, necesitamos ambas para
crear nuestro formulario de accesos

1
2
3
4
5
6

class MoviesController < ApplicationController


def show
@movie = Movie.find params[:id]
@tags = Tag.all
end
end

y crearemos un formulario sencillo, que apunte a la misma pgina,


para probarlo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<h2> Tags </h2>


<%= form_for @movie do |f| %>
<table>
<thead>
<tr>
<th>Genero</th>
<th>Selecionar</th>
</tr>
</thead>
<tbody>
<% @tags.each do |tag| %>
<tr>
<td> <%= tag.name %></td>
<td><%= f.check_box 'tag_ids', {multiple: true}, tag.id, nil %>
</tr>
<% end %>
</tbody>
</table>
<%= submit_tag 'Guardar' %>
<% end %>

El formulario resultante queda:

La opcin {mltiple true} es necesaria para que Rails pueda


entender que hay varios tags disponibles, en caso de omitirla solo
se guardar un tag.

Lo ltimo que haremos sera crear una validacin en el modelo


Movie_tag para que la combinacin entre movie_id y tag_id sea
nica y as evitar entradas duplicadas.

1
2
3
4
5
6

class MovieTag < ActiveRecord::Base


belongs_to :movie
belongs_to :tag
validates :movie_id, uniqueness: { scope: :tag_id }
end

26) Haciendo un cloudtag


Objetivos
1. Aplicar de forma prctica lo aprendido en el captulo de relaciones N a N
2. Utilizar los datos de nuestra relacin N a N para gracar una nube de tags

En este captulo crearemos un cloudtag, o nube de tags con Ruby


on Rails igual la que se ilustra en la foto, para eso ocuparemos el
plugin de jqcloud.

Para este ejercicio ocuparemos el proyecto de nuestro captulo


anterior con post y tags.

Creando datos para la nube de tags


Luego crearemos algunos datos para nuestro proyecto

Dentro del archivo seeds vamos a agregar algunos datos

1
2
3
4
5
6
7
8
9
10
11

p = Post.new(title:"Aprendiendo a programar", content:"Lorem Ipsum ...")


p.tags << Tag.new(tag:"Programacin")
p.save
p = Post.new(title:"Introduccin a Ruby on Rails", content:"Lorem, Ruby, Rails"
p.tags << Tag.new(tag:"Programacin")
p.tags << Tag.new(tag:"Ruby")
p.tags << Tag.new(tag:"Rails")
p.save
p = Post.new(title:"el Patrn MVC", content:"MVC, y lorem ipsum")

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

p.tags
p.tags
p.tags
p.tags
p.save

<<
<<
<<
<<

Tag.new(tag:"Programacin")
Tag.new(tag:"Ruby")
Tag.new(tag:"Rails")
Tag.new(tag:"MVC")

p = Post.new(title:"Aprendiendo Rails con desafiolatam", content:"Bootcamps lorem ipsum"


p.tags << Tag.new(tag:"Bootcamps")
p.tags << Tag.new(tag:"Aprender")
p.save

p = Post.new(title:"Lgica para la programacin", content:"Logica, tablas de lorem ipsum


p.tags << Tag.new(tag:"Lgica")
p.tags << Tag.new(tag:"Programacin")
p.save

p = Post.new(title:"Escalando Rails", content:"Rails, tablas de lorem ipsum"


p.tags << Tag.new(tag:"Optimizacin")
p.tags << Tag.new(tag:"Rails")
p.save

Luego

descargamos

el

plugin

de

jQCloude

desde

https://github.com/mistic100/jQCloud y copiamos los archivos de


jqcloud.css

jqcloud.js

en las carpetas respectivas de

assets.

Creando el controller y cargando los datos para hacer la nube de


tags
Ahora crearemos el controller:

rails g controller tags index

Dentro del controller de tags agruparemos los tags por trmino y


los contaremos.

1
2
3
4
5
6

class TagsController < ApplicationController


def index
tags_count = MovieTag.joins(:tag).group(:name).count
@tags = tags_count.collect {|x,y| {text: x, weight: y, height: y}}
end
end

Creando la vista con la nube de tags


En la vista index mostraremos los resultados ocupando un
pequeo script.

1
2
3
4
5
6
7
8
9
10
11

<div class="keywords">
</div>
<script>
tags = <%= (@tags.to_json.html_safe) %>
console.log(tags)
$('.keywords').jQCloud(tags, {
width: 500,
height: 350
});
</script>

27) Devise
La autenticacin es el proceso de validacin de un usuario en un
sistema, es perfectamente posible programar uno desde cero, pero
como en la mayora de los casos estos sistemas son exactamente
iguales en todos los sitios ya existen sistemas muy completos y
seguros que puedes incorporar en tu sitio.

En Ruby on Rails el sistema de autenticacin ms famoso se llama


Devise, y es el que aprenderemos a ocupar en este captulo,
razones para ocuparlo:

1. Soporte autenticacin sobre mltiples modelos.


2. Es seguro, encripta las claves automticamente, no guarda las
claves en texto plano.
3. Incorpora herramientas para validacin del email (mdulo
conrmable)
4. Es combinable con otras soluciones como Login con FB, Twitter,
Linkedin y todo lo que soporte OAUTH.
5. Los tiempos de sesin son congurables.

Empezando con devise


Devise es bastante sencillo de implementar, primero agregamos la
gema al gemle.

gem 'devise'

1
2

bundle
rails generate devise:install

Al correr el generador, Rails nos mostrar el siguiente mensaje:

Some setup you must do manually if you havent yet:

1. Ensure

you

have

dened

default

url

options

in

your

environments les. Here is an example of default_url_options


appropriate

for

development

environment

in

cong/environments/development.rb:
cong.action_mailer.default_url_options = { host: localhost,
port: 3000 }
In production, :host should be set to the actual host of your
application.
2. Ensure you have dened root_url to something in your
cong/routes.rb. For example:
root to: "home#index"
3. Ensure

you

have

ash

messages

in

app/views/layouts/application.html.erb. For example:


<%= notice %>
<%= alert %>
4. If you are deploying on Heroku with Rails 3.2 only, you may want
to set:
cong.assets.initialize_on_precompile = false
On cong/application.rb forcing your application to not access
the DB or load models when precompiling your assets.
5. You can copy Devise views (for customization) to your app by
running:
rails g devise:views

Y ahora debemos cumplir con estos 5 puntos: El primer punto es


para poder enviar los emails, estos emails probablemente sean
bloqueados por Gmail u otros sistemas de todas formas as que
podemos omitirlo por ahora.

El segundo punto es para ingresar la pgina de inicio, esto es


necesario para que devise pueda redirigirte cuando intentes entrar
a una URL y no estas autenticado.

El tercer punto es para mostrar los mensajes de que ingresaste con


xito o fall el ingreso da lo mismo cul pgina se use, por lo
mismo estos mensajes se ponen en la pgina maestra.

El punto cuatro es exclusivo para Rails 3.2 y nosotros estamos


trabajando con Rails 4, as que no nos compete.

El ltimo punto permite generar las vistas para login, actualizar


contraseas y muchas otras, si omitimos este paso igual se van a
mostrar estas vistas pero nosotros no las podremos modicar.

correr rails g devise:views crear las siguientes vistas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

invoke
create
create
invoke
create
create
create
create
create
create
create
create
create
create
create
create
invoke
create
create
create
create

Devise::Generators::SharedViewsGenerator
app/views/devise/shared
app/views/devise/shared/_links.html.erb
form_for
app/views/devise/confirmations
app/views/devise/confirmations/new.html.erb
app/views/devise/passwords
app/views/devise/passwords/edit.html.erb
app/views/devise/passwords/new.html.erb
app/views/devise/registrations
app/views/devise/registrations/edit.html.erb
app/views/devise/registrations/new.html.erb
app/views/devise/sessions
app/views/devise/sessions/new.html.erb
app/views/devise/unlocks
app/views/devise/unlocks/new.html.erb
erb
app/views/devise/mailer
app/views/devise/mailer/confirmation_instructions.html.erb
app/views/devise/mailer/reset_password_instructions.html.erb
app/views/devise/mailer/unlock_instructions.html.erb

Creando el modelo de devise


Una vez con devise instalado tenemos dos opciones, la primera es

agregarlo sobre un modelo existente, la segunda es crear un nuevo


modelo deviseable.

Cul opcin escoger?

Slo depende de si ya tienes creado el modelo de tus usuarios o


no, pero en ambos casos lo que tenemos que hacer es:

rails generate devise user

Es perfectamente posible cambiar el nombre de la tabla y utilizar


admin, u otro nombre en lugar de user, pero es importante el
nombre utilizado puesto que devise genera helpers en base a ese
nombre, as que si lo cambias no olvides cul nombre utilizaste.

Al correr el generador del modelo la consola mostrar lo siguiente:

1
2
3
4
5
6
7
8

invoke
create
create
invoke
create
create
insert
route

active_record
db/migrate/20151015141123_devise_create_users.rb
app/models/user.rb
test_unit
test/models/user_test.rb
test/fixtures/users.yml
app/models/user.rb
devise_for :users

En resumen: se gener una migracin, el modelo de usuario, test


para el modelos de usuarios, los xtures para las pruebas y se
agrega la ruta devise_for :users

Ahora debes correr las migraciones puesto que devise crea la tabla
si no existe o agrega los campos necesarios si no los tiene.

rake db:migrate

y con eso ya terminamos la conguracin inicial, ahora para revisar


el login y el formulario de registro debemos entrar a:

http://localhost:3000/users/sign_in
http://localhost:3000/users/sign_up

Debemos recordar que si cambiamos el modelo de user la ruta


tambin debe reejar el nombre del modelo utilizado en plural.

Ingreso, registro y salida


Las acciones claves de devise son:

1. Sign_in
2. Sign_up
3. Sign_out

Hay otras como cambiar el password, pero por ahora vamos a


abordar las 3 rutas claves, para ver todas las acciones podemos
utilizar el comando rake routes .

A continuacis, las tres rutas que nos permitirn que un usuario se


registre, ingrese y luego cierre la sesin.

Prex

Verb

Url

Controller#method

new_user_registration

GET

/users/sign_up(.:format)

devise/registrations#new

new_user_session

GET

/users/sign_in(.:format)

devise/sessions#new

destroy_user_session

DELETE

/users/sign_out(.:format)

devise/sessions#destroy

Cmo podemos dirigir al usuario al formulario de ingreso? fcil,


agregamos un link a new_user_session_path

<%= link_to "Ingresar", new_user_session_path %>

Para dirigirlo al formulario de creacin es igual, pero ocupamos la


ruta.

<%= link_to "Registrar", new_user_registration_path %>

Para salir debemos ocupar la ruta respectiva, pero adems


debemos especicar el verbo delete.

<%= link_to "Salir", destroy_user_session_path,

method: :delete %>

O en la forma de HTML

1
2
3

<a href="<%= destroy_user_session_path %>" data-method="delete">


Salir
</a>

Login or logout
Es muy comn en un sitio web o aplicacin web que no se muestre
simultneamente el link a ingresar y registrar y el link a salir
simultneamente, normalmente se muestra ingresar y registrar (si
no has ingresado) y salir (si ya estas ingresado)

Esto lo podemos lograr ocupando el helper user_signed_in? de


devise y un un if y else de Ruby.

1
2
3
4
5
6

<% if user_signed_in? %>


<%= link_to "Salir", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Ingresar", new_user_registration_path %>
<%= link_to "Registrar", new_user_registration_path %>
<% end %>

El objeto current_user
Cuando el usuario se registra o ingresa se inicia una sesin, las
sesiones en Rails son un hash que permite identicar al usuario y
guardar junto con el informacin a nuestra voluntad.

La session no queda guardada en la base de datos, si no en las


cookies del navegador, la entidad de Rails encargada de manejar
las sesiones es el ActionDispatch::Session::CookieStore.

Por que son importantes las sesiones en el contexto de devise?,


porque sirven para guardar al usuario actual Cmo lo hace?

El mtodo current_user busca si el usuario est en la sesin, si no


lo encuentra lo busca en la base de datos. Gracias a esa denicin,
nosotros podemos mostrar informacin del usuario logeado, por
ejemplo si quisiramos mostrar el email sera:

<%= current_user.email %>

El logout destruye el objeto current_user.

Modificando los formularios de ingresar y registro


Para modicar los formularios lo primero que debemos hacer (slo
si no lo hicimos previamente) es generar la vistas.

rails g devise:views

Con las vistas generadas ahora podemos modicarlas utilizando


HTML. Las vistas generadas se encuentran en: views/devise/
ah encontraremos varias carpetas, el login est dentro de
views/devise/sessions/new

el

sign_up

dentro

de

views/devise/registrations/new

Dentro de estas vistas encontraremos un formulario que es


ligeramente distinto a lo que hemos vistos hasta ahora.

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|

Este formulario de devise es compatible con mltiples recursos


simultneamente, o sea podemos tener mltiples modelos de
usuarios (obviamente con distintos nombres) y todos con devise.

Adems la mayora de las vistas de devise incluyen un render a la


vista parcial de links

<%= render "devise/shared/links" %>

Agregando un dato al modelo de usuarios


El primer paso ya lo hemos realizado previamente, consiste en
generar una migracin para agregar el dato, para probarlo
agregaremos el campo nombre (name) para el usuario.

1
2

rails g migration addNameToUser name:string


rake db:migrate

El segundo paso es agregar el campo al formulario

1
2
3
4

<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name, autofocus: true %>
</div>

Al recargar la pgina veremos el formulario de registro con el


nombre pero si lo llenamos y lo enviamos veremos en nuestra
base de datos que nuestro usuario no tiene nombre, si revisamos
en el log de rails server veremos:

Parameters: {"utf8"=>"", "user"=>{"name"=>"Gonzalo", "email"=>"gonzalo@desafiolatam.com


<span style="color:red">Unpermitted parameter: name </span>

Cmo podemos arreglar un problema de strong parameters si no


tenemos controller de usuario?

Fcil, utilizaremos el super controller, o sea el application controller

1
2
3
4
5
6
7

before_action :configure_permitted_parameters, if: :devise_controller?


protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end

El

callback

nos

permite

llamar

al

mtodo

congure_permitted_parameters si estamos dentro del controller


de devise, luego el mtodo agrega el campo name a la lista de los
parmetros

sanitizados.

Podramos

agregar

un

segundo

separndolo con una coma.

Despus de este cambio podemos enviar el formulario de nuevo

1
2
3
4
5
6
7

INSERT INTO "users" ("email", "encrypted\_password", "name", "created_at", "updated_at")


VALUES (?, ?, ?, ?, ?)
[["email", "gonzalo2@desafiolatam.com"],
["encrypted\_password",
"$2a$10$k19SMlN60bX4NQXutV3CseToYC/I5TbHeb.6P26N6WzWi0ZXxKa5C"], ["name", "Gonzalo"],
["created_at", "2015-10-19 04:06:23.324424"],
["updated_at", "2015-10-19 04:06:23.324424"]]

Bloqueando el acceso
Es muy comn en un sitio web que un usuario no puede acceder a
una pgina X hasta que ya se haya logeado, en Devise es posible

lograrlo utilizando el callback before_action dentro de un controller


que se desea bloquear.

before_action :authenticate_user!

Ahora supongamos que tenemos un controller llamado pages,


cuyo nico objetivo es mostrar dos pginas, una es home, la otra
es secreto y queremos que la pgina secreto sea slo para
usuarios logeados.

1
2
3
4
5
6
7

class PagesController < ApplicationController


def home
end
def secreto
end
end

Podramos hacer:

1
2
3
4
5
6
7
8

class PagesController < ApplicationController


before_action :authenticate_user!
def home
end
def secreto
end
end

Pero de esta forma limitaramos todas las pginas y no solo home,


para

evitar

esto

podemos

agregar

como

parmetro

before_action los hashs :only o except de esta forma:

1
2
3

class PagesController < ApplicationController


before_action :authenticate_user!, only: [:secreto]
def home

al

4
5
6
7
8

end
def secreto
end
end

De esta forma slo el mtodo secreto de pages depender de que


el usuario se haya logeado y si el usuario intenta entrar a
http://localhost:3000/pages/secreto

sin estar logeado

ser redirigido a la login.

Antes de probarlo no te olvides de agregar las pginas al archivo


routes.rb

1
2

get 'pages/home'
get 'pages/secreto'

Recuperando la contrasea
Cuando se crea un sistema de login con Devise todo funciona
perfecto salido de la caja excepto el recuperar contraseas, la
razn es muy sencilla, esto se hace via email y para que Rails
pueda enviar un email necesita tener un sender (enviador)
congurado, este mdulo en Rails 4 recibe el nombre de Action
Mailer.

Congurando action_mailer para enviar correos con gmail para


hacerlo

basta

abrir

config/application.rb

el

archivo

de

conguracin

(tambin es posible ocupar un

initializer) y agregar las siguientes lneas dentro del module y de


class Application.

1
2

config.action_mailer.default_url_options = { :host => 'localhost:3000' }


config.action_mailer.delivery_method = :smtp

3
4
5
6
7
8
9
10
11
12
13
14

config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default :charset => "utf-8"
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:authentication => :plain,
:domain => 'gmail.com',
:user_name => ENV['email'],
:password => ENV['email_password']
}

Donde dice ENV[email] y password podemos cambiarlas por


nuestras claves de email y al reiniciar la aplicacin ya estara
funcionando pero hay un problema grande con hacer eso,
estaramos dejando las claves del correo electrnico dentro de
nuestro cdigo.

Protegiendo las claves con dot-env Dot-env es una gema que nos
permite agregar variables de entorno de forma sencilla a nuestra
aplicacin, para eso vamos agregar la siguiente gema al gemle

gem 'dotenv-rails'

Luego tenemos que crear un archivo .env (si, el punto es parte del
nombre) dentro de la raz de nuestro proyecto, en el vamos a
agregar las variables de entorno.

1
2

email=tuemail@gmail.com
email_password=tupassword

y ya con eso nuestra aplicacin permite recuperar las contraseas


del usuario desde el sign_in.

Evitando adjuntar el archivo .env por error al repositorio Ahora


debemos de asegurarnos de no adjuntar este archivo por error

cuando hagamos un commit, para eso vamos a abrir el archivo


.gitignore (esto slo aplica si estn ocupando GIT)

/.env

Congurando Heroku para que acepte las variables de entorno Si


ocupas Heroku te estars preguntando como pasar el archivo .env
si no est en el repositorio, el secreto es que no se pasa, vamos a
ocupar la terminal para dar los valores de las variables de entorno.

Entonces desde la terminal dentro de la carpeta del proyecto,


escribimos:

1
2

heroku config:set email=tuemail@gmail.com


heroku config:set email_password=tupassword

y ahora si que si, tus claves estn seguras y tu aplicacin est


funcionando con la opcin de recuperar contraseas.

28) Devise avanzado


Ya hemos cubierto los tpicos bsicos de la gema devise, pero
todava hay varias funcionalidades interesante que veremos en
este captulo.

Agregando el rol de usuario


Lejos una de las funcionalidades ms utilizadas es la de mltiples
roles, o sea existe un usuario que tiene acceso a ciertas pginas y
otros usuarios que tienen acceso a otras.

Para lograr esto tenemos que agregar un campo rol al usuario para
poder

distinguirlo

luego

customizar

nuestro

propio

authenticate_user!

Agregando el campo
Podemos hacerlo de varias formas, con un string, con un integer o
la mejor forma con un enum.

Los strings son buenos para distinguir el rol pero dejas abierta la
posibilidad que en algn momento se ingrese un rol no denido.

Los integers puedes utilizarlo como 0 para admin, 1 para usuario,


etc, y a pesar de que no caen en el problema anterior de crear un
rol usuario en vez de usuario tienen el problema de que no es claro
que hace cada nmero, te obliga a revisar la documentacin y es
un causa problema de errores.

Enums al rescate

Los enums permiten combinar lo mejor de estos dos mundos, los


strings y los integers.

Para crear un enum agregaremos el rol del usuario como integer


en la base de datos.

1
2

rails g migration addRoleToUser role:integer


rake db:migrate

En el modelo especicaremos que role es un enum

enum role: [ :admin, :editor, :user]

Para revisar cambiar el status de un usuario tenemos mtodos


como

1
2

- .admin! (cambia el usuario al rol admin)


- .admin? (devuelve true o false dependiendo de si el usuario es admin)

Por ejemplo queremos revisar si el usuario logeado es editor,


entonces simplemente:

<% if current_user.editor? %>

Para cambiar al primer usuario de la base de datos y darle acceso


de admin haramos

User.last.admin!

El problema que tenemos ahora es que los usuarios que ya existen

en la base de datos quedan con el campo role como nil y los


nuevos usuarios tambin, a menos que le demos la opcin de
elegir el role que tendrn, lo que es una muy mala idea ya que el
administrador del sitio debera ser quien asigne los roles en la
aplicacin.

Lo que debiera pasar es que cualquier usuario nuevo se cree con


un role estndar, en este caso el de user, para eso deniremos un
valor por defecto al campo role y lo haremos a nivel de base de
datos y de modelo.

A nivel de base de datos: Primero creamos una migracin vaca

rails g migration addDefaultRoleToUser

Luego modicamos la migracin recin creada para dejarla as

1
2
3
4
5
6
7
8
9

class AddDefaultRoleToUser < ActiveRecord::Migration


def up
change_column :users, :role, :integer, default: 2, null: false
end
def down
change_column :users, :role, :integer, default: nil
end
end

Y por ltimo ejecutamos la migracin

rake db:migrate

Una vez corrida la migracin todos los usuarios en la base de datos


que tenan role = nil ahora tendrn el role por defecto, en
este caso 2.

Ahora setearemos el default a nivel de modelo, para eso en el


modelo de usuario user.rb aadimos lo siguiente:

1
2
3
4
5
6
7

...
before_save :default_role
def default_role
self.role ||= 2
end
...

Ahora que ya tenemos roles pasaremos a vericar los accesos,


para eso vamos al applicationController y aadimos lo siguiente al
nal del archivo antes del ltimo end .

1
2
3
4
5
6
7

private
def check_admin!
authenticate_user!
unless current_user.admin?
redirect_to root_path, alert: "No tienes acceso"
end
end

De esta manera el mtodo check_admin! estar disponible en


todos nuestros controladores.

Luego en cada uno de los controladores donde queremos que el


usuario logeado sea administrador agregamos lo siguiente.

before_action :check_admin!, only: :secreto

Esto es suciente para sitios chicos donde hay que revisar 3 o 4


pginas contra uno o dos accesos, pero si escalamos de esta forma
nos vamos a ver creando muchos mtodos en cada controller para
revisar los accesos, para evitar esto en le prximo captulo
estudiaremos la gema CanCanCan.

Testeando los accesos


El testeo de los accesos es un test funcional, o sea un test de los
controllers.

Para testear con Devise primero debemos incluir el helper dentro


de la clase del test

include Devise::Test::ControllerHelpers

Luego creamos xtures para los distintos tipos de usuario en el


archivo test/fixtures/user.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14

admin:
id: 1
email: "admin@desafiolatam.com"
role: 0
editor:
id: 2
email: "editor@desafiolatam.com"
role: 1
user:
id: 3
email: "user@desafiolatam.com"
role: 2

y luego de vuelta en el controller denimos nuestros tests.

Principalmente lo que vamos a testear en este punto es si un


usuario tiene acceso a una pgina estando logeado o no, o si tiene
acceso a el mtodo dado el rol que tiene. Siempre tenemos que
realizar los dos tipos de test, los positivos y los negativos, o sea que
no pueda cuando no deba y que pueda cuando deba.

1
2
3
4

test "unlogged user can get home" do


get :home
assert_response :success
end

Despus probamos nuestro primer test, debera pasar, ahora si


obtenemos un error del tipo:

1
2

ActionView::Template::Error:
undefined method `authenticate' for nil:NilClass

Es porque no agregamos el helper de devise para los tests.


include Devise::TestHelpers

Ahora creemos un test para revisar si un usuario logeado puede


entrar a la pgina home

1
2
3
4
5
6

test "logged user can get home" do


user = users(:user)
sign_in(user)
get :home
assert_response :success
end

Este test ser ms interesante, que pasa si un usuario no logeado


intenta entrar a una pgina que no puede.

1
2
3
4

test "logged user can't get secreto" do


get :secreto
assert_response :redirect
end

Y como probamos que un usuario con un rol especco no tenga


acceso, eso lo hacemos con:

1
2
3
4
5
6

test "user without privileges can't get secreto" do


user = users(:user)
sign_in(user)
get :secreto
assert_response :redirect
end

Como mencionamos previamente no es suciente probar que no


tenga acceso, tambin hay que probar que la persona correcta si lo
tiene.

La gran ventaja de tener test para los tipos de acceso es que si en


algn momento llegamos a romper algo por integrar una nueva
funcionalidad

podemos

detectarlo

sin

tener

que

manualmente todas las pginas una a una.

En resumen testear accesos nos permite:

Mejorar los tiempos de desarrollo


Bajar los costos de desarrollo
Implementar cambios y mejoras con menor costo.
Y en el caso de pruebas de acceso, asegurarnos que la
seguridad del sitio est OK

Cdigos completos:
Tests
1
2
3
4
5
6
7
8

require 'test_helper'
class PagesControllerTest < ActionController::TestCase
include Devise::Test::ControllerHelpers
test "unlogged user can get home" do
get :home
assert_response :success

probar

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

end
test "logged user can get home" do
user = users(:user)
sign_in(user)
get :home
assert_response :success
end
test "logged user can't get secreto" do
get :secreto
assert_response :redirect
end
test "user without privileges can't get secreto" do
user = users(:user)
sign_in(user)
get :secreto
assert_response :redirect
end
test "admin can get secreto" do
admin = users(:admin)
sign_in(admin)
get :secreto
assert_response :success
end
end

Pages Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class PagesController < ApplicationController


before_action :check_user, only: :secreto
def home
end
def secreto
end
private
def check_user
authenticate_user!
unless current_user.admin?
redirect_to root_path, alert: "No tienes acceso"
end

16
17
18

end
end

Fixture de usuario
1
2
3
4
5
6
7
8
9
10
11
12
13
14

admin:
id: 1
email: "admin@desafiolatam.com"
role: 0
editor:
id: 2
email: "editor@desafiolatam.com"
role: 1
user:
id: 3
email: "user@desafiolatam.com"
role: 2

Generando los controllers de devise


Al igual que las vistas, los controllers pueden ser generados, y al
generarlos tenemos un mejor control del como funcionan, ya sea
por los strong params, o porque queremos realizar alguna accin
como enviar un email al momento del registro.

Para generar los controllers ocuparemos el generador de devise.

rails generate devise:controllers users

Al correr el generador observaremos que aparece un mensaje que


dice lo siguiente:

1
2
3
4
5
6
7
8
9
10

Some setup you must do manually if you haven't yet:


Ensure you have overridden routes for generated controllers in your routes.rb.
For example:
Rails.application.routes.draw do
devise_for :users, controllers: {
sessions: 'users/sessions'
}
end

Para lograr esto iremos al archivo de rutas y cambiaremos el


devise_for :users por

1
2
3
4

devise_for :users, controllers: {


sessions: 'users/sessions',
registrations: 'users/registrations'
}

En el archivo de rutas debemos especicar el remplazo de lo que


queramos cambiar.

Una vez corrido el generador veremos que se crean diversos


archivos bajo la carpeta user dentro de controllers.

Los mtodos expresados ah dentro son sencillos, ya que user


hereda de RegistrationsController todos los mtodos respectivos
se reducen a hacer un llamado a super para llamar al mtodo
padre.

Cambiando la pgina despus de registrarse


Nosotros vamos a descomentar el primer before_lter, o sea
congure_sign_up_params

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

class Users::RegistrationsController < Devise::RegistrationsController


before_filter :configure_sign_up_params, only: [:create]
# before_filter :configure_account_update_params, only: [:update]
# GET /resource/sign_up
# def new
#
super
# end
# POST /resource
# def create
#
super
# end
# GET /resource/edit
# def edit
#
super
# end
# PUT /resource
# def update
#
super
# end
# DELETE /resource
# def destroy
#
super
# end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

#
#
#
#
#

cancel oauth signing in/up in the middle of the process,


removing all OAuth session data.
def cancel
super
end

# protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.for(:sign_up) << :name
end
# If you have extra params to permit, append them to the sanitizer.
# def configure_account_update_params
#
devise_parameter_sanitizer.for(:account_update) << :attribute
# end
# The path used after sign up.
# def after_sign_up_path_for(resource)
#
super(resource)
# end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
#
super(resource)
# end
end

Cambiando la pgina despus de ingresar


En la ltima versin de devise en la que probamos esto no se
genera el cdigo de redireccin, pero eso no quiere decir que no se
pueda

agregar,

para

eso

dentro

del

controllers/users/sessions_controller.rb

Agregaremos al nal:

1
2
3

protected
def after_sign_in_path_for(resource)
destino_path

archivo

end

Donde destino_path es un path vlido.

Configurando action_mailer para enviar correos con gmail


para

hacerlo

basta

abrir

config/application.rb

el

archivo

de

conguracin

(tambin es posible ocupar un

initializer) y agregar las siguientes lneas dentro del module y de


class Application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

config.action_mailer.default_url_options = { :host => 'localhost:3000' }


config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default :charset => "utf-8"
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:authentication => :plain,
:domain => 'gmail.com',
:user_name => ENV['email'],
:password => ENV['email_password'],
}

Donde dice ENV[email] y password podemos cambiarlas por


nuestras claves de email y al reiniciar la aplicacin ya estara
funcionando pero hay un problema grande con hacer eso,
estaramos dejando las claves del correo electrnico dentro de
nuestro cdigo.

Protegiendo las claves con dot-env

Dot-env es una gema que nos permite agregar variables de


entorno de forma sencilla a nuestra aplicacin, para eso vamos
agregar la siguiente gema al gemle

gem 'dotenv-rails'

luego tenemos que crear un archivo .env (si, el punto es parte del
nombre) dentro de la raz de nuestro proyecto, en el vamos a
agregar las variables de entorno.

1
2

email=tuemail@gmail.com
email_password=tuppassword

y ya con eso nuestra aplicacin permite recuperar las contraseas


del usuario desde el sign_in.

Evitando adjuntar el archivo .env por error al repositorio Ahora


debemos de asegurarnos de no adjuntar este archivo por error
cuando hagamos un commit, para eso vamos a abrir el archivo
.gitignore (esto slo aplica si estn ocupando GIT)

/.env

Configurando Heroku para dot-env


Congurando Heroku para que acepte las variables de entorno Si
ocupas Heroku te estars preguntando como pasar el archivo .env
si no est en el repositorio, el secreto es que no se pasa, vamos a
ocupar la terminal para dar los valores de las variables de entorno.

Entonces desde la terminal dentro de la carpeta del proyecto,


escribimos:

1
2

heroku config:set email=tuemail@gmail.com


heroku config:set email_password=tupassword

y ahora si que si, tus claves estn seguras y tu aplicacin est


funcionando con la opcin de recuperar contraseas.

Quiz
Para qu sirve el objeto current_user?
Donde se ingresan los strong parameters de un objeto
deviseado?
Cmo podemos redireccionar a un usuario despus de
ingresar por devise?
Cmo podemos redireccionar a un usuario despus de
registrarse por devise?

29) Autorizacin con CanCanCan


CanCanCan es una gema para el manejo de accesos de usuarios en
un sitio, y juega muy bien en conjunto con Devise.

Mientras Devise se encarga del login del Usuario, CanCanCan se


encarga de controlar los accesos a las diversas pginas web de tu
sitio.

Hay que tener cuidado a la hora de trabajar con este gema de no confundirla con su
versin anterior llamada CanCan

Cundo no utilizar CanCanCan?


Cuando es un sitio pequeo de pocas pginas y slo se busca
controlar acceso entre uno o dos perles de usuarios distintos.

Big picture

Agregar roles

rails g migration addRoleToUser role:integer


agregar default: 0 a la migracion
agregar el enum y los roles al modelo

Agregar la gema CanCanCan

gem cancancan en el gemfile


bundle

Generar rbol de habilidades

rails g cancan:ability

load_and_authorize_resource
Bloquear el controller

anidado?
load_and_authorize_resource :base
load_and_authorize_resource :nested, through: :base

Manejar el error lanzado por el


controller

class ApplicationController < ActionController::Base


rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, alert: exception.message
end
end

Instalando CanCanCan
Para instalar la gema debemos abrir el gemle y agregar la gema
cancancan para instalarlo corremos

bundle

en la lnea de

comandos.

Despus de la instalacin generamos el rbol de habilidades de


CanCanCan con el generador:

rails g cancan:ability

Si lo hicimos bien, obtendremos:

create app/models/ability.rb

El rbol de habilidades

El rbol de habilidades es el archivo donde se dene que puede


hacer y que no puede hacer cada usuario, este archivo se
encuentra dentro de app/model/ability.rb .

Al abrir el archivo encontraremos comentados diversos ejemplos


de permisos

1
2
3
4
5
6
7
8
9

def initialize(user) # user es sacado del mtodo current_user automticamante por cancan
user ||= User.new # En caso de que el usuario no haya ingresado.
if user.admin?
can :manage, :all
else
can :read, :all
end
end

CanCanCan recibe un user el cual es obtenido del mtodo


current_user , en caso de ser nil debemos manejarlo, una
forma comn es crear una instancia de usuario, que puede ser
vaca o venir con algn rol que nosotros queramos.

El mtodo can
El mtodo can recibe dos parmetros, el primero es la accin o sea
el mtodo en el controller al cual estamos estableciendo el
permiso, y el segundo es el recurso en donde se puede ejecutar
esa accin.

can :metodo1, Recurso

Si queremos dar acceso a ver todos los posts de nuestro blog.

can :index, Post

Para evitar que se borre un post podemos utilizar:

cant :destroy, Post

Los roles
Es comn dar los permisos en funcin de un rol, los roles suelen
asignarse en el modelo de usuario.

1
2
3
4
5

if user.editor?
can :destroy, Post
else
can :read, :all
end

La forma mas frecuentes de crear roles es utilizando el enum que


vimos en un captulo anterior.

rails g migration addRoleToUser role:integer

Modicamos la migracin y agregamos default: 0 y corremos


las migraciones con rake db:migrate

Y nalmente agregamos los roles dentro de nuetro modelo de


usuarios ocupando un enum.

enum role: [:rol1, :rol2, :rol3]

Ya con roles en nuestra base de datos podemos ocuparlos dentro


de nuestro archivo ability.rb

El smbolo

:manage

es un alias que se reere a todas las

acciones y el mtodo :all es un alias para referirse a todos los


recursos de la aplicacin.

Hay otros cuatro alias que usaremos constantemente para denir y


chequear permisos y son:

:read que es un alias para :index y :show


:create que es un alias para :new y :create
:update que es un alias para :edit y :update
:destroy que es un alias para :delete y :destroy

Tambin es posible denir aliases propios siguiendo los principios


de

la

gua

ocial

https://github.com/CanCanCommunity/cancancan/wiki/ActionAliases, pero al menos que tengas muchos mtodos no REST


repetidos en cada controller no tiene sentido.

Revisin de habilidades
Una vez que ya tenemos el rbol de habilidades, podemos
empezar a ocupar el mtodo can? para determinar cuando
mostrar (o cuando no) un link o cierta informacin especca a un
usuario dependiendo de su rol.

1
2
3

<% if can? :update, @movie %>


<%= link_to "Edit", edit_movie_path(@movie) %>
<% end %>

En una lnea:

<%= link_to "Edit", edit_movie_path(@movie) if can? :update, @movie %>

Ocultar el contenido no siempre es suciente, por ejemplo en el caso de un link no slo


nos interesa que la persona no pueda ver el link, tambin nos interesa que no logre
entrar cambiando la url.

Bloqueo y carga
La forma de bloquear un recurso a un acceso no deseado es a
travs

del

controller,

se

especica

la

accin

load_and_authorize_resource.

1
2
3
4
5
6
7
8
9
10
11

class MoviesController < ApplicationController


load_and_authorize_resource
def index
# @movies is already loaded and authorized
end
def show
# @movie is already loaded and authorized
end
end

load_and_authorize_resource realiza dos acciones, una es el


bloqueo (authorize) y la otra es la carga (load).

El bloqueo
El bloque funciona levantando una excepcin en caso de que el
usuario intente acceder a un mtodo que no est denido dentro

del arbol de habilidades.

O sea si un usuario intenta entrar a movie#index pero el arbol de


habilidades dice que no tiene acceso entonces se generar un
error, este error despus lo captutraremos para redirigir el usuario
a una pgina a nuestra eleccin en caso de que suceda.

La carga
La carga de recursos es acorde del modelo correspondiente para
los mtodos REST, por ejemplo en el cdigo anterior, se cargan
Movie.all y Movie.nd(params[:id]) de forma automtica en index y
show.

En otros mtodos fuera de lso REST tenemos que cargarlos.

Manejo de conexiones no autorizadas


El

ltimo

paso

consisten

en

especicar

dentro

del

applicationController a que pgina redirigir en caso de que


una persona intente acceder a una pgina a la que no est
autorizada.

1
2
3
4
5

class ApplicationController < ActionController::Base


rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, alert: exception.message
end
end

Podemos cambiar el mensaje donde se dene el alert por uno


personalizado.

En este punto ya podemos probar nuestra aplicacin, los usuarios

que tienen los accesos podrn ver las pginas mientras otros sern
redirigidos.

Probando las habilidades en la consola


Es posible vericar las habilidades en la consola, y es el primer
lugar en el que deberamos revisar si algo no funciona.

Vericar una habilidad siempre tiene la siguiente forma:

Seleccionamos un usuario.
Vemos si tiene acceso a un recurso.
1
2
3
4
5

user = User.first
ability = Ability.new(user)
ability.can?(:create, Movie)
ability.can?(:edit, Movie)
ability.can?(:destroy, Movie)

Habilidades basadas en la propiedad


Es perfectamente posible limitar el acceso a un recurso a cualquier
otra persona que no sea su dueo, por ejemplo supongamos que
en un foro los editores pueden cambiar todo, pero un usuario solo
puede editar o eliminar los post creador por el, este tipo de
restricciones son bastante comunes y fciles de implementar con
cancancan.

En el rbol de habilidades se puede especicar el acceso en base a


un campo del recurso, por ejemplo:

can [:update, :destroy], [Movie, Review], user_id: user.id

Testear este tipo de habilidades requiere especicar de quien es el


objeto.

ability.can?(:destroy, Review.new(user: user))

Habilidades en recursos anidados


Supongamos un caso donde hayan diversos grupos, y quieres que
un usuario tenga acceso a todo dentro de un grupo, tendramos un
recurso grupo y anidado a un recurso movie, lo otro importante es
que el usuario tenga guardado el group_id.

Podemos limitar el acceso con:

can :index, Movie, {id: user.group_id}

Luego el recurso (en el controller) tambin hay que autorizarlo de


una forma ligeramente distinta.

El de group sera de la misma forma:

load_and_authorize_resource

Pero el de Movie sera:

1
2

load_and_authorize_resource :group
load_and_authorize_resource :through => :group

Construir tests para este tipo de habilidades es igual que para


otros tests de recursos anidados.

1
2
3
4
5

test "users can't access other groups sections" do


sign_in users(:group1)
get :index, {group_id: 2}
assert_redirected_to root_path
end

30) Polimorfismo
Polimorsmo es una tcnica que nos ayuda a no repetir cdigo,
teniendo en cuenta que uno de de los principales causales de
errores y de problemas es la repeticin debemos en lo posible
obtener un enfoque DRY (dont repeat yourselve)

Dnde podemos evitar repeticiones?

En muchas ocasiones nos encontraremos con interacciones entre


modelos que se repiten, por ejemplo:

Votos con post y votos con comentarios


Foto del post y del usuario
Tags de diferentes elementos
Likes a diferentes tablas como movie y review

Para no tener que implementar las reglas del negocio para cada
uno de los elementos y caer en el error de repetir cdigo existe
una tcnica llamada polimorsmo que consiste en una interfaz que
permite interactuar entre modelos que se comportan similar.

La interfaz suena a algo muy complejo, pero realmente consiste en


dos campos de la base de datos que permiten relacionar con el id
del objeto y otro para guardar el tipo de objeto.

La interfaz se agrega sobre el modelo comn, por ejemplo si


queremos implementar likes de usuarios sobre movies y sobre
reviews la interfaz la haramos sobre likes. Agregando la interfaz:

Creando el modelo like desde cero:

rails g model like user:references likable:references{polymorphic}

Convirtiendo el modelo like a polimrco si este ya existe

rails g migration addLikableToLike likable:references{polymorphic}

Esto generara la migracin:

1
2
3
4
5

class AddLikeableToLike < ActiveRecord::Migration


def change
add_reference :likes, :likable, polymorphic: true, index: true
end
end

y ahora marcamos nuestro modelo de like como polimrco y lo


asociamos al usuario

1
2

belongs_to :user
belongs_to :likable, polymorphic: true

Los modelos que interactan con la interfaz deben saber que lo


estn haciendo, para eso agregaremos al modelo de movie y al de
review la relacin con los likes:

1
2

has_many :likes, as: :likable


has_many :user_likes, through: :likes, source: :user

Al modelo User tambin agregamos la relacin

1
2
3

has_many :likes
has_many :movie_likes, through: :likes, source: :likable, source_type: Movie
has_many :review_likes, through: :likes, source: :likable, source_type: Review

Si queremos validar que un usuario pueda hacer like una sola vez a
una movie o review, agregamos la siguiente validacin en el
modelo like:

validates :user_id, uniqueness: {scope: [:likable_id, :likable_type]}

Es necesario hacer la comprobacin usando los tres campos ya


que de otra manera si un usuario hace like a la movie con id 4 no
podr hacer like al review con id 4.

31) Subiendo archivos con carrirewave


La gema carrierwave es una gema bastante sencilla de ocupar que
permite la subida de archivos, exista otra gema que cumple el
mismo propsito llamada paperclip, la conguracin es distinta
pero ambas hacen el trabajo.

Instalando carrierwave
El branch master de la gema todava es experimental, por lo que
recomendamos ocupar la documentacin del tag 0.10

Para instalar la gema agregaremos gem carrierwave al gemle.

Gemle.rb

gem 'carrierwave'

luego bundle.

El siguiente paso es generar un uploader, un uploader dene una


estrategia para subir archivos, si tenemos varios campos que
requieran subir fotos, pero todas las fotos reciben el mismo
tratamiento podemos reutilizar el uploader, pero si tenemos
distintos tipos de archivos que subir y hay que tratarlos de forma
distinta crearemos uno por estrategia.

Generando el uploader
1

rails generate uploader Avatar

Si revisamos el uploader generado, veremos algo como:

1
2
3

class AvatarUploader < CarrierWave::Uploader::Base


storage :file
end

storage le indica que el archivo se guardar dentro de la


aplicacin, dentro de la carpeta public, en el prximo captulo
estudiaremos como subir estos archivos a Amazon S3.

Luego el ltimo paso que nos falta es montar el uploader, para


montarlo necesitamos tener un campo del tipo string para guardar
el nombre del archivo.

Es decir si tuviramos un campo llamado photo dentro del modelo,


entonces:

mount_uploader :photo, AvatarUploader

Probando desde rails console.

Si tenemos un archivo en la raz del proyecto podemos asignrsela


a un objeto as.

1
2
3
4
5
6

post = Post.first
File.open("nombre_archivo") do |f|
post.photo = f
end
post.save

Creando una formulario con archivos


En primer lugar para que un formulario pueda enviar archivos
debe ser del tipo multiparte, podemos convertir a cualquier
formulario

en

multiparte

utilizando

html: {:multipart => true}

Por ejemplo:

= form_for @post, html: {:multipart => true} do |f|

Luego dentro del formulario para agregar un campo que nos


permite utilizar archivos debemos utilizar

f.file_file :photo

32) Amazon S3
Amazon S3 es un sistema de almacenamiento de archivos, es
bastante fcil de usar, lamentablemente no es gratuito pero si es
muy barato.

Hay dos formas de subir archivos, con fog y con carrierwave-aws, la


ventaja de carrierwave-aws es que tiene menor footprint y
debemos tener cuidado de no sobrecargar nuestra aplicacin, sin
embargo fuera de que la conguracin dentro de un archivo es
ligeramente distinta el resto de nuestra aplicacin se mantiene
intacto, sin importar cual de estas dos gemas utilicemos.

Configuracin para la gema de carrierwave-aws


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

if Rails.env.test?
CarrierWave.configure do |config|
config.storage = :file
config.enable_processing = false
end
else
CarrierWave.configure do |config|
config.storage = :aws
config.aws_credentials = {
:access_key_id
=> ENV['aws_access_key_id'],
# required
:secret_access_key
=> ENV['aws_secret_access_key'], # required
:region
=> ENV['aws_region'],
# optional, defaults to
# :host
=> 's3.amazonaws.com'
#:host
=> 's3.example.com',
# optional, defaults to
:endpoint
=> 'http://s3.amazonaws.com' # optional, defaults to nil
}
config.aws_bucket
= ENV['aws_dir']
# required
config.aws_acl
= 'public-read'
config.aws_attributes = {
expires: 1.week.from_now.httpdate,
cache_control: 'max-age=604800'
}

24
25
26

end
end

Separar los entornos no es necesario pero es til, tambin es


posible que en el entorno de desarrollo no utilizar amazon para no
generar gastos innecesarios, pero de todoas formas deberamos
hacer una prueba con amazon localmente antes de subir los
cambios.

Los

valores

de

aws_access_key_id,

aws_secret_access_key,

aws_region, aws_dir deben estar denidos dentro del archivo .env

IAM
I AM es un sistema de permisos de Amazon basado en roles y
plizas, su conguracin es importante puesto que impide a una
persona externa que obtiene tu clave de un servicio utilizarla para
otros. Fuera de que no debemos agregar nuestra clave al cdigo
porque la hace vulnerable, adems debemos congurar estas
plizas para limitar el acceso a mbitos especcos, en este caso
nos interesa nicamente Amazon S3 el cual sirve para guardar y
leer archivos

Debemos crear un usuario, y agregar Amazon S3 full access.

Agregando las claves de entorno a Heroku


1
2
3
4

heroku
heroku
heroku
heroku

config:set
config:set
config:set
config:set

aws_access_key_id=tu_clave_de_acceso
aws_secret_access_key=tu_otra_clave_de_acceso
aws_region=region_de_s3
aws_dir=nombre_del_bucket

Preguntas
1. Para qu sirve amazon S3?
2. Por qu en Heroku no se pueden subir imgenes?
3. Para qu sirve IAM?
4. Por qu motivo debemos agregar las claves de entorno a Heroku?

33) Optimizacin
El Active Record de Ruby on Rails tiene muchas cosas geniales,
pero algunas de ellas si no las manejas bien pueden repercutir
negativamente en el rendimiento de la aplicacin.

En este captulo abordaremos diversos errores y tips que nos


permitirn que nuestras aplicaciones de Rails consuman menos
memoria y corran ms rpido.

find_each
Hacer un loop sobre todos los registros guardados en la base de
datos es ineciente, puesto que intentar instanciar todos los
objetos al mimo tiempo, manejar los resultados por baches (lotes)
es ms eciente y reduce el consumo de memoria, ah es donde
entra nd_each

1
2
3

User.find_each do |u|
u.cambiar_tamao_foto()
end

nd_each toma lotes de mil registros por defecto, as que en el


caso de que tus tablas tengan 100 registros no es una optimizacin
til.

N+1 queries en Ruby on Rails


Pequeo repaso del activerecord

Uno de los errores ms frecuentes de los desarrolladores novatos


de Rails es el problema de las N+1 queries.

Para entender el problema debemos recordar que el activerecord


se encarga de relacionar los datos de la base de datos con objetos
en Ruby, cuando hacemos algo como User.all traemos todos los
usuarios de la base de datos y los devolvemos como una coleccin
de objetos de Ruby que podemos iterar, pero detrs de esto hay
una nica consulta a la base de datos.

Si hacemos User.all.each do {|user| user.name} estaramos


mostrando todos los nombres de los usuarios, los cuales son
trados a memoria a travs de User.all , esto lo podemos ver
en rails c que despus de escribir User.all obtendremos:
SELECT "users".* FROM "users" o sea todos los campos de
todos los usuarios.

Relacionando datos
Digamos ahora que un usuario puede pertenecer a un grupo, y en
un grupo pueden haber muchos usuarios, o sea una relacin de 1
a n.

Si quisiramos mostrar el nombre del grupo junto con el nombre


de cada usuario escribiramos algo como:

1
2
3
4

User.all.each do |user|
puts user.name
puts user.group.name
end

Y esto funcionar perfectamente, sin embargo se gatillar una


nueva consulta por cada usuario.

Group Load (0.2ms)

SELECT

"groups".* FROM "groups" WHERE "groups"."id" = ?

El motivo de esto es que Rails no tiene informacin de la categora


en memoria, a travs del User.all obtuvo la informacin del
usuario, para obtener la categora y por lo mismo debe
recuperarla, En ese caso estaramos gatillando una nueva
consulta por cada user, o sea N+1 consultas en total.

En una base de datos pequea, con 10 grupos, aunque cuando


uno tiene una base de datos con pocos datos el efecto es casi
invisible, a medida de que crece el nmero de datos y el nmero
de clientes este problema puede llegar a impactar de forma muy
dura en el rendimiento de tu aplicacin a tal punto de botarla.

Evitando el problema de n+1


Para evitar este comportamiento indeseado lo que podemos hacer
es decirle a Rails que traiga de antemano los datos a memoria,
esto lo podemos hacer utilizando includes

User.all.includes(:group)

y ahora veremos que la consulta SQL obtenida ser parecida a la


siguiente:

1
2

User Load (0.8ms) SELECT "users".* FROM "users"


Group Load (0.3ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" IN

y si repetimos el experimento anterior utilizando includes, veremos


que slo se hace una nica consulta a la base de datos.

1
2
3

User.includes(:group).each do |user|
puts user.name
puts user.group.name

end

N+1 en conteo
En la misma lnea que el problema de N+1 sucede cuando
contamos datos de elementos relacionados, por ejemplo si
queremos mostrar cada grupo con la cantidad de usuarios que
tiene, haramos lo siguiente:

1
2
3
4

Group.all.each do |group|
puts group.name
puts group.users.count
end

Hay diversas formas de evitar esto, y tienen distintas implicancias

Contando con includes


La forma ms fcil de resolverlo es utilizando includes, pero sin
utilizar count, debemos ocupar el mtodo length, de esta forma
ser Ruby quien se encargar de contar con los datos que ya
trajimos a memoria

1
2
3
4

Group.includes(:users).each do |group|
puts group.name
group.users.size
end

Ocupar count e includes en conjunto es lo peor de dos

mundos, porque con includes traemos los datos a


memoria, pero no lo utilizaremos y volveremos a
consultar a la base de datos.

Contando con SQL


Otra forma de contar es agrupando los datos directamente con
SQL

Group.joins(:users).select("count(users.*) as count_users, groups.*").group

Al utilizar un select de esta forma el objeto group dentro del active


record tendr el campo count_users y este tendr el valor
equivalente a la cuenta de usuarios.

No podemos ocupar includes


includes suele transformase en un preload, este mtodo
de Rails realiza dos queries por separado para cargar los
datos de cada tabla, y de esta forma no podemos no sabe
como sacar los datos.

adems debemos tener cuidado de especicar correctamente el


tipo de join, el inner no nos mostrar la cuenta de grupos sin un
grupo no tiene usuarios.

Contando con left join

Para evitar ese problema podemos hacer un left join

Group.joins("left join users on users.group_id = groups.id").select("count(users.*) as c

La gema bullet
La gema bullet detecta de forma automtica los errores de N + 1
queries, en el momento en que entremos a una pgina que
contengan un error obtendremos una alerta que nos avisa del
problema.

Para instalar la gema agregaremos al gemle:

gem "bullet", :group => "development"

Luego de eso tenemos que congurar el archivo dentro de


config/enviroments/development.rb

para

especicar

como queremos esos reportes, hay varias posibles alertas, un


ejemplo con todas sera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.growl = true
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
:password => 'bullets_password_for_jabber',
:receiver => 'your_account@jabber.org',
:show_online_status => true }
Bullet.rails_logger = true
Bullet.honeybadger = true
Bullet.bugsnag = true
Bullet.airbrake = true
Bullet.rollbar = true

16
17
18
19
20

Bullet.add_footer = true
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
Bullet.slack = { webhook_url: 'http://some.slack.url', foo: 'bar' }
end

Tip

Habilitar todas las noticaciones puede ser muy redundante y bastante molesto, en lo
personal preero ocupar enable, console, footer.

Desnormalizacin y Counter cach


Si tenemos una vista donde siempre mostramos la informacin de
ciertas cuentas no tiene sentido rescatarlas cada vez de la base de
datos,

para

evitarlo

podemos

utilizar

una

tcnica

de

desnormalizacin llamar counter cach.

El truco es agregar una columna a la tabla por el lado de los unos,


para que cuente cuantos elementos tiene del otro lado, o sea
vamos a agregar un columna count_users a la tabla groups.

34) Javascript, jQuery y Turbolinks


Manejar javascript dentro de Rails es un arte, en especial porque
Rails tiene incorporada dos soluciones que le dan gran velocidad y
poder pero generan conictos con javascript.

La primera es turbolinks quien evita que se cargue las


cabeceras de las pginas y esto puede causar que nuestros
scripts no se carguen sin recargar la pgina.
La segunda es sprocket quien concatena todos los archivos js
dentro de uno solo, y eso genera el problema de crear javascript
especcos para un solo archivo.

La solucin mas sencilla es agregar los javascript dentro


de los HTML

El problema con integrar el javascript dentro de los HTML es que


no podemos aprovechar las bondades de sprockets y en el caso de
que un script lo necesitemos en mltiples ocasiones sufriremos de
replicacin de cdigo.

Cmo organizar nuestro javascript?


Generalmente, podemos dividir los comportamientos de javascript
en las siguientes categoras:

Comportamiento del tipo "always on"


Comportamiento que es provocado por el usuario

A su vez estas pueden dividirse en:

Comportamientos globales que estn presentes a travs de


toda la aplicacin.
Comportamientos especcos para una o mas vistas, pero que
no son necesarias en toda la aplicacin.

Los comportamientos globales deberan ser requeridos en el


maniesto para que de esa manera se carguen en toda la
aplicacin.

Los comportamientos especcos NO deberan estar en el


maniesto y se deberan incluir en las vistas correspondientes sin
olvidar de decirle a Rails que compile nuestros archivos (ver punto
2 paso 3).

Otras veces tendremos comportamientos "always on" especcos


para una vista en particular de un controlador. Para evitar tener
que hacer un archivo distinto y usar el archivo javascript
correspondiente al controlador podemos hacer lo siguiente:

En

nuestro

/app/views/layouts/application.html.erb

modicamos la etiqueta <body> y lo dejamos as:

1
2
3

<body class="<%= controller_name %> <%= action_name %>">


<%= yield %>
</body>

Con esto nuestra etiqueta <body> tendr dos clases, una ser el
nombre del controlador y la otra el nombre de la accin que llama
a la vista. Por ejemplo, en una aplicacin donde tenemos posts al
acceder al detalle de un post nuestro <body> quedara as:

1
2

<body class="posts show">


...

3
4
5

resto del contenido


...
</body>

Una vez hecho lo anterior podremos tener un comportamiento


especico a una vista haciendo lo siguiente en nuestro archivo js:

1
2
3
4
5
6
7
8
9
10
11

$(document).ready(function() {
function someBehavior() {
... comportamiento ...
};
if ( $('.posts.show').length > 0 ) {
someBehavior();
}
})

Aqu

lo

importante

es

el

if ( $('.posts.show').length > 0 ) que comprueba si


existe algn elemento que contenga las clases posts y show, si
existe ejecutamos la funcin someBehavior , si no existe no se
ejecutar nada.

Turbolinks
La funcin

$(document).ready()

tiene un comportamiento

errtico en una aplicacin Rails, y los archivos JS no se vuelven a


ejecutar al cambiar de una pgina a otra y como consecuencia
nuestro sitio no funciona como lo esperado. Esto sucede debido a
como turbolinks maneja la carga de las pginas de nuestra
aplicacin.

La funcin de turbolinks es hacer que nuestra aplicacin se sienta


mas rpida y uida. En vez de dejar que el browser cargue y

recompile los JavaScripts y CSS cada vez que nos cambiamos de


pgina, turbolinks mantiene la pgina actual viva y solo remplaza
el contenido del

<body>

o partes de el, y el titulo en el

<head> . Esto quiere decir que no existe una recarga completa


de

la

pgina,

por

DOMContentLoaded

lo

que

no

podremos

jQuery.ready()

conar

para

en

ejecutar

nuestro cdigo. En su lugar turbolinks dispara eventos en el


document que podremos usar para ejecutar nuestro cdigo:

Evento
page:beforechange

Descripcin
La pgina esta por cambiar.

page:fetch

Una nueva pgina est a punto de ser trada desde el servidor.

page:receive

Una pgina ha sido recibida desde el servidor, pero an no analizada.

page:beforeunload

Los nodos estn a punto de ser cambiados.

page:change

Nodos han cambiado. Tambin se ejecuta en DOMContentLoaded.

page:update

Se ejecuta en page:change y ajaxSuccess de jQuery.


Un nuevo elemento body se ha cargado en el DOM. No se dispara en

page:load

sustitucin parcial o cuando una pgina se restaura desde la cach, a n de


no ejecutarse dos veces en el mismo body.

page:partial-

Nuevos elementos han sido cargados en el DOM a travs de la sustitucin

load

parcial

page:restore

Un elemento body en cach se ha cargado en el DOM.

page:afterremove

Un elemento se ha eliminado del DOM.

Ejemplos de uso
1

$(document).on('page:fetch', function() {

2
3
4
5
6

$(".loading-indicator").show();
});
$(document).on('page:change', function() {
$(".loading-indicator").hide();
});

Como regla general, todos nuestros scripts asociados a un


$(document).ready()

deberan ser modicados para usar

$(document).on('page:load')

$(document).on('page:change') segn sea necesario. Otra


opcin es hacer uso de la gema jQuery Turbolinks.

En caso de querer hacer una carga completa al seguir un link por


ejemplo un link a una seccin de administrador que usa otro
archivo de base (layout) que carga scripts que no se usan en las
otras secciones y as evitar tener problemas de que no se carguen
estos nuevos scripts, podemos decirle a Rails que no use turbolinks
en ese links y as ejecutar una carga completa usando la opcin
data-no-turbolink .

Ejemplo:

<%= link_to 'Admin', admin_path,

data: { no_turbolink: true } %>

Otra cosa importante para que nuestros javascript funcionen


correctamente es el orden en el que los requerimos en el
maniesto, primero siempre jQuery, despus las libreras externas,
luego nuestros scripts y al nal turbolinks para que sea el ltimo
en instalar el manejador del evento click y as no interferir otros
scripts.

1
2
3
4
5
6

//
//= require jquery
//= require jquery_ujs
//= libreras externas
//= nuestros scripts

7
8
9

//= require turbolinks


//

En caso de usar la gema

jQuery.turbolinks , esta debe ir

antes

de

jquery_ujs, para que pueda 'secuestrar' la llamada a

(document). ready()o (function() { })`

1
2
3
4
5
6
7
8
9
10

//
//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= libreras externas
//= nuestros scripts
//= require turbolinks
//

35) AJAX, Remotes y UJS


A JAX es Asynchronous JavaScript And XML, y nos permite enviar
informacin del cliente al servidor sin tener que recargar la pgina

Para qu sirve AJAX?


Innite scrolling
Filtros dinmicos
Llamar a un mtodo de crear y borrar sin recargar la pgina
Interactuar con el servidor sin interrumpir la pgina.
Si hay un video corriendo y quieres enviar un comentario no
tener que llamar a otra pgina interrumpiendo el video
Si la pgina es muy pesada, borrar un dato hara recargar y
pedir todos los datos a la base de datos de nuevo, a menos que
sea haga por A JAX

UJS
Hay dos formas princiaples de integrar A JAX en nuestra aplicacin,
la primera es con jquery-ujs a travs de los remotes, la segunda es
a travs de los requests A JAX.

jquery-ujs es una simple tecnologa que permite la utilizacin de


javascript no obstruviso en la pgina, veamos un ejemplo sencillo.

<a href="#" data-confirm="Seguro que quieres borrar esta informacin?"> Borrar

Por dentro lo que hace ujs es capturar diversos posibles atributos


data- y les incorpora el javascript necesario para que funcionen.

Esto es particularmente importante porque utilizaremos A JAX a


travs de data-remote

Dnde se encuentra cargado ujs?


Si nos jamos en el maniesto veremos que jquery_ujs ya se
encuentra cargado.

1
2
3
4

//=
//=
//=
//=

require jquery
require jquery_ujs
require turbolinks
require_tree .

Nuestro primer data remote


Para probarlo crearemos una aplicacin nueva, haremos un
controller con dos mtodos.

rails g controller pruebas index ajax1

Y agregamos dentro de index:

<%= link_to "Prueba Ajax", pruebas_ajax1_path, remote: true %>

Al hacer click en el link esperaramos ver un resultado con A JAX,


pero veremos que nada especial pasa y que en el servidor
veremos:

Started GET "/pruebas/ajax1" for ::1 at 2016-03-06 15:07:09 -0300

2
3
4

Processing by PruebasController#ajax1 as JS
Rendered pruebas/ajax1.html.erb within layouts/application (0.5ms)
Completed 200 OK in 40ms (Views: 38.8ms | ActiveRecord: 0.0ms)

No es que hayamos hecho algo mal, nos falta negociar el


contenido, cuando a Rails no se les especica que datos obtener
devuelve un HTML, pero nosotros no queremos un HTML nuevo,
queremos javascript que podamos ejecutar para poder modicar
la pgina.

Rails es capaz de entender de forma fcil que devolver, si se le pide


javascript devuelve javascript, pero en este caso no lo tiene,
entonces tenemos que crearlo, para hacerlo crearemos un archivo
llamado ajax1.js dentro de la carpeta de pruebas.

Dentro del archivo pondremos un simple javascript para ver que


haya funcionado.

alert("hola");

Si el archivo lo renombramos a

ajax1.js.erb

podremos

ocupar Ruby.

Lo siguiente que tenemos que saber es que dentro de la vista


podemos ocupar las variables de instancias denidas en el mtodo
respectivo. Podemos probar esto agregando al controller una
variable de instancia e imprimiendola en la vista

1
2
3

def ajax1
@valor = 5
end

y luego en la vista ajax1.js.erb

alert("<%= @valor %>")

Los

siguientes

ejercicios

sern

ms

interesantes

porque

utilizaremos modelos, pero por lo mismo tenemos que crearlos


primero, creemos un scaold de post.

rails g scaffold post content:string

y luego migremos la base de datos.

rake db:migrate

Dentro del seed agreguemos unos 10 datos para hacer las


pruebas.

10.times {|i| Post.create(content: "Post #{i}")}

y luego agregamos los seeds

rake db:migrate

Al cargar la pgina http://localhost:3000/posts veremos:

Ahora con los remotes podremos borrar los post sin necesidad de
recargar la pgina. Para lograrlo buscaremos el link a borrar dentro
de post#index y le agregaremos remote

<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?', remo

Si probamos el borrado veremos que no funciona, o sea


efectivamente los links se borran pero para ver los cambios

tenemos que recargar la pgina, la razn es nuevamente


negociacin de contenido, nos falta el js para que Rails pueda
devolverlo.

Dentro del post controller, buscaremos el mtodo destroy y


agregaremos format.js

1
2
3
4
5
6
7
8

def destroy
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.'
format.json { head :no_content }
format.js
end
end

y dentro del js tenemos que asegurarnos de esconder el dato, para


eso necesitamos una id del elemento que podamos seleccionar, asi
que en la vista post#index le daremos un id a cada post

<tr id="post-<%=post.id%>">

y luego en el archivo destroy.js ocultaremos ese dato, eso lo


logramos con un sencillo selector de jQuery:

$("#post-<%=@post.id%>").toggle();

Remotes en los formularios


No solo los links pueden ocupar remotes, los formularios tambin
pueden, para demostrarlo ahora crearemos posts con ajax, si bien
podemos hacerlo desde la vista new, vamos a mover el fomulario
de creacin al index para poder ver como se van agregando los
temes creados.

Para lograrlo tenemos que hacer dos cosas

1. Renderear el formulario
1

<%= render "form" %>

1. En el controller debera cargar un post nuevo para el formulario.

Si no cargamos un post nuevo obtendremos el siguiente error:

Para resolverlo simplemente tenemos que agregar al controller


@post = Post.new

Post#index quedara as:

1
2
3
4

def index
@post = Post.new
@posts = Post.all
end

Ahora haremos el formulario funcionar por A JAX, para eso el


primer paso es agregar remote: true

<%= form_for(@post, remote: true) do |f| %>

Esto ya es suciente para que nuestro formulario funcione por


A JAX, pero no podremos ver los resultados porque no estamos
devolviendo ningn Javascript.

El siguiente paso entonces es agregar un javascript que nos


muestre el post agregado, son los mismos dos pasos del ejercicio
previo:

1. Le decimos al mtodo create del controller post que pueda


devolver un javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Post was successfully created.'
format.json { render :show, status: :created, location: @post }
format.js
else
format.html { render :new }
format.json { render json: @post.errors, status: :unprocessable_entity
format.js
end
end
end

1. Creamos el archivo create.js.erb

$("tbody").append("<tr> <td> <%= @post.content %> </td> </tr>");

Si lo probamos nuestro post se agregar, pero sin las opciones de


ver, editar y borrar, estas deberamos agregarlas tambin.

Entonces el javascript quedara:

1
2
3
4
5
6
7
8

$("tbody").append('\
<tr id="post-<%=@post.id%>">\
<td><%= @post.content %></td>\
<td><%= link_to 'Show', @post %></td>\
<td><%= link_to 'Edit', edit_post_path(@post) %></td>\
<td><%= link_to 'Destroy', @post, method: :delete,\
data: { confirm: 'Are you sure?', remote:true } %></td>\
</tr>');

El problema con hacerlo de esta forma es que el escape de las


comillas puede ser complejo, adems hay que rescribir toda la
vista que ya esta escrita y estamos duplicando cdigo, hay una
forma ms fcil, que es utilizando las vistas parciales, para eso:

1. Ponemos el cdigo del post en una vista parcial


2. rendereamos la vista parcial desde el javascript

Entonces creamos el archivo _post.html.erb dentro de la carpeta


posts. del index sacamos este cdigo y lo ponemos dentro del
nuevo archivo _post.html.erb

1
2
3
4
5
6

<tr id="post-<%=post.id%>">
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?', re
</tr>

Dentro de posts#index borraremos lo siguiente:

1
2
3

<% @posts.each do |post| %>


...
<% end %>

y agregaremos:

render @posts

render

tiene la capacidad de entender cuando se desea

mostrar una coleccin de datos y muestra cada datos, por lo que


no es necesario hacer un each.

Sabremos que lo hemos hecho bien si al recargar la pgina todava


podemos ver los datos.

El paso que nos falta es desde el javascript

create.js.erb

podemos cambiar todo el cdigo que tenamos por:

$("tbody").append('<%= j render @post %>');

La j es necesaria para escapar las comillas.

Preguntas
1. Qu signica A JAX?
2. Cul es la funcin de jquery-ujs?
3. Cul es la funcin de j en el render?
4. Qu sucede si a render le pasamos una coleccin de datos en lugar de

un objeto?
5. Qu sucede si al bloque de respond_to no le agregamos el format.js?
6. Qu sucede si existe el bloque respond_to con format.js pero no existe
el archivo?

36) El mtodo .ajax


Innite scrolling es una tcnica que consiste que a medida te
acerques al nal de la pgina esta cargue nuevos datos y sirve para
remplazar

la

componente

tpica

de

paginacin,

el

si

es

recomendable implementarla o no en una pgina es un tema bien


discutido en la internet, pero en este tutorial vamos a explicar
como implementarla.

Paso 0: Setup

Partamos con una aplicacin vaca de Ruby on Rails, y dentro


haremos el scaold del recurso Post que contendrn los campos
title y content.

Paso 1: Desacoplar el index

En lugar de ocupar el archivo index creado por el scaold vamos a


ocupar una vista parcial que contenga la informacin de cada post,
de esta forma vamos a poder hacer mucho ms con menos cdigo.

As que crearemos la vista parcial _post.html.erb

1
2
3
4
5

<%= div_for(post) do %>


<h2><%= post.id %></h2>
<h2><%= post.title %></h2>
<p><%= post.content %></p>
<% end %>

Luego el archivo posts/index.html.erb queda de esta forma:

1
2

<div id="content">
<%= render(partial: 'post', collection: @posts) %>

</div>

Paso 2: Instalar la gema Kaminari

Por qu necesitamos una gema para la paginacin?, La respuesta


es simple, para escribir menos cdigo, las gemas de paginacin
nos separan los resultados en grupos, dependiendo de la pgina,
la pgina 0 (sin nmero) contiene los primeros x resultados, la
segunda pgina (?page=2) contiene los segundos x resultados. El
truco que haremos con innite scrolling es ir llamando via A JAX a
estas distintas pginas generadas por el paginador (Kaminari)

Para instalar la gema hay que abrir el gemle y agregar la lnea

gem 'kaminari'

Paso 3: Paginando

Para paginar tenemos que modicar el controller de posts para


especicar que los resultados deben ser paginados, esto lo
hacemos modicando el mtodo index.

1
2
3

def index
@posts = Post.page(params[:page]).per(5)
end

Paso 4 Link a la siguiente pgina

En la vista de posts simplemente haremos un link a la pgina


siguiente, para eso ocuparemos el helper de Rails url_for slo
especicando el query string page, o sea este mtodo llamar a la
misma pgina pero aadiendo ?page=2 (o 3, o 4, u otro valor
dependiendo del caso)

1
2
3

<p id="view-more">
<%= link_to('Ver Ms', url_for(page: @posts.current_page + 1)) %>
</p>

Paso 5: Agregando datos Hasta el momento nuestro sitio es un


simple listado de posts pginado pero debera estar funcionando,
para probarlo vamos a agregar datos ocupando el archivo seeds.rb
y una gema espectacular para este propsito llamada Faker.

Paso 5.1: Agregar la gema Faker al gemle y luego correr bundler

Paso 5.2: Agregar las siguientes lneas al archivo db/seeds.rb

1
2
3

50.times.each do |x|
Post.create(:title => Faker::Lorem.sentence, :content => Faker::Lorem.paragraph
end

Paso 5.3: Correr rake db:seeds

Paso 5.4: Probar entrando a localhost:3000/posts y ver los


resultados, hacer click en siguiente y ver que la paginacin
funciona.

Paso 6: Innite Scrolling

Hay libreras que podemos descargarde innite scrolling, pero con


Jquery es posible hacer el efecto de forma sencilla en slo un par
de lneas de cdigo, para hacerlo vamos a crear el archivo
scrolling.js.coee dentro de la carpeta app/assets/javascript

Dentro del archivo vamos a hacer la primera prueba

1
2

jQuery ->
$(window).scroll ->

3
4

if $(window).scrollTop() > $(document).height()$(window).height()50


alert("Final de la pgina")

Al correr el sitio e ir al nal de la pgina va a aparecer una alerta


diciendo Final de la pgina, el valor -50 debera ser ajustado en
casos especiales, como xed footers que sean muy grandes.

Paso 6.1 Haciendo el llamado Ajax

Ahora vamos a ocupar la idea anterior pero cada vez que se llegue
al nal de la pgina vamos a cargar una pgina nueva con posts
ms antiguos, para eso el mtodo $.getScript() de jquery a
lo que el controller responder con un archivo index.js.erb con los
posts nuevos (al pedir un archivo javascript el controller devuelve
lo pedido y lo ejecuta)

1
2
3
4
5
6

jQuery ->
$(window).scroll ->
url = $('#view-more a').attr('href')
if url && $(window).scrollTop() > $(document).height()
$("#view-more").attr('href', '')
$.getScript url

$(window).height

Lo ltimo que falta es aprovechar el archivo javascript devuelto y


utilizarlo para actualizar la pgina.

Paso 6.2 Cargando los posts.

El archivo devuelto, que todava no hemos creado, es index.js.erb


(siguiendo las convenciones de Rails debe llamarse igual que el
mtodo), ahora creemos el archivo y dentro de el carguemos la
vista parcial de post con la informacin de los nuevos post y
actualizemos el link para que cargue posts an ms antiguos.

1
2

$('#content').append("<%= j render @posts %>");


$("#view-more").attr("href", "<%= j posts_path(page: @posts.current_page + 1) %>"

Y con eso logramos Innite Scrolling

Happy Innite Scrolling !!!

Paso 6.3 (Opcional, pero elegante): Removiendo el link al nal del


documento.

Para remover el link tenemos que saber si realemente estamos al


nal,

para

eso

comparamos

@posts.current_page == @posts.total_pages

1
2
3
4
5
6

$('#content').append("<%= j render @posts %>");


<% if @posts.current_page == @posts.total_pages %>
$('#view-more').remove();
<% else %>
$('#view-more').html("<%= j link_to('View More', url_for(page: @posts.current_page + 1)
<% end %>

37) Manejo de grficos


Hay diversas gemas para manejar grcos, usualmente la parte de
grcos es un javascript donde se ponen los datos, la parte
compleja es recuperar los datos especcos que necesitamos para
nuestra aplicacin, en este captulo veremos como recuperar
diversos tipos de datos para generar grcos de barra y de tipo
pie.

Utilizar PostgreSQL
Si bien algunos de los grcos los podemos hacer con
SQLite, la agrupacin de datos no funciona exactamente
de la misma forma en SQLite que en PostgreSQL.

Vamos a necesitar agregar las siguientes gemas para hacer los


grcos

1
2

gem "chartkick"
gem 'groupdate'

Para este captulo vamos a construir un proyecto para administrar


un fundacin donde tenemos donantes y donaciones.

1
2

rails g model donor


rails g scaffold donations

Haciendo los queries

1
2
3
4
5
6
7

@donations = Donation.where(user_id: current_user.id)


unless params[:company_filter].blank?
@donations = @donations.eager_load(:donor).where("donors.company = ?", params
end
@donations_by_company = @donations.eager_load(:donor).group("donors.company"
@donation_by_day = @donations.group_by_day("donations.created_at" ).sum(:amount

Generando los grficos


1
2
3
4
5

<%= javascript_include_tag "//www.google.com/jsapi", "chartkick" %>


<%= line_chart @donation_by_day %>
<%= pie_chart @donations_by_company %>

Construyendo un calendario de eventos con Rails y


Fullcalendar

Setup del proyecto


1

rails new calendario

Vamos a crear un scaold de eventos, nuestros eventos tienen que


tener ttulo, comienzo y n.

rails g scaffold events title:string start:date end:date

Revisamos que nuestra migracin sea correcta y luego la corremos


con:

rake db:migrate

Los campos title y start son obligatorios para mostrar los eventos,
para evitar problemas agregaremos una validacin de presencia en
el modelo de eventos.

De esta forma si intentamos crear un evento sin ttulo


obtendremos un rollback de la operacin.

1
2
3
4
5

2.3.1 :006 > Event.create(title:nil, start:Time.now)


(0.2ms) begin transaction
(0.1ms) rollback transaction
=> #<Event id: nil, title: nil, start: "2016-07-25", end: nil, created_at: nil, updated
2.3.1 :007 >

Vamos a agregar un par de datos para mostrar en nuestro


calendario, para eso dentro del archivo seed.rb agregaremos:

1
2
3
4
5

Event.destroy_all
Event.create!(title:"Evento
Event.create!(title:"Evento
Event.create!(title:"Evento
Event.create!(title:"Evento

de
de
de
de

ayer", start: Time.now - 1.day)


hoy", start: Time.now)
maana", start: Time.now + 1.day)
la semana", start: Time.now - 3.day, end: Time

Y corremos los seeds con:

rake db:seed

Ya

con

los

datos

ingresados

entramos

localhost:3000/events y podemos ver nuestros eventos.

Setup de FullCalendar
Fullcalendar puede ser descargado o agregado como CDN, las
instrucciones

se

encuentran

en

http://fullcalendar.io/download/ .

Para este ejercicio nosotros lo haremos como CDN, para eso


dentro de nuestro layout tenemos que agregar dentro del head los
CDN de los estilos y javascript de fullcalendar y tambin el de la
biblioteca moment.js

1
2
3
4
5
6
7
8

<link href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.9.0/fullcalendar.min.css
rel="stylesheet">
<link href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.9.0/fullcalendar.print.css
rel="stylesheet">
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.14.1/moment.min.js
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.9.0/fullcalendar.min.js
</script>

Dentro de la vista de events#index agregaremos

1
2
3
4

<div id="calendar"> </div>


<script>
$('#calendar').fullCalendar({})
</script>

Y ahora ya tenemos un calendario, aunque todava falta agregar


los eventos.

Agregando eventos al calendario


Hay dos formas de agregar los eventos del calendario, la primera
es pasando los datos de los eventos directamente dentro del
calendario, tenemos que llamar a html_safe para poder pasar los
smbolos dentro de los diccionarios sin que sean codicados.

1
2
3
4
5
6

<div id="calendar"> </div>


<script>
$('#calendar').fullCalendar({
events: <%= @events.to_json.html_safe %>
})
</script>

Para entender la necesidad de .html_safe comparemos como se ve


un evento con y sin el llamado al mtodo.

Sin html_safe

1
2
3
4
5
6

{&quot;id&quot;:1,
&quot;title&quot;:&quot;Evento de ayer&quot;,
&quot;start&quot;:&quot;2016-07-24&quot;,
&quot;end&quot;:null,
&quot;created_at&quot;:&quot;2016-07-25T18:15:25.402Z&quot;,
&quot;updated_at&quot;:&quot;2016-07-25T18:15:25.402Z&quot;}

Con html_safe:

1
2
3
4
5
6

{"id":1,
"title":"Evento de ayer",
"start":"2016-07-24",
"end":null,
"created_at":"2016-07-25T18:15:25.402Z",
"updated_at":"2016-07-25T18:15:25.402Z"}

En la documentacin de FullCalendar puedes encontrar mucha


ms informacin.

http://fullcalendar.io

Para mejorar el estilo del calendario puedes ocupar algn tema de


Jquery UI Themes

http://jqueryui.com/themeroller/

39) Envo de correos con Action Mailer


Intro
Action Mailer nos permite enviar correos desde nuestra aplicacin

utilizando clases y vistas mailer . Estos funcionan muy parecido


a los controladores en donde un mtodo en la clase mailer tiene
una vista asociada.

Creando nuestro mailer


Como casi todo en Rails, existe un generador para crear los
archivos mailer necesarios.

rails generate mailer UserMailer

Esto creara los siguientes archivos:

1
2
3
4
5
6
7
8
9

create
create
invoke
create
create
create
invoke
create
create

app/mailers/user_mailer.rb
app/mailers/application_mailer.rb
erb
app/views/user_mailer
app/views/layouts/mailer.text.erb
app/views/layouts/mailer.html.erb
test_unit
test/mailers/user_mailer_test.rb
test/mailers/previews/user_mailer_preview.rb

Como pueden ver, se gener un mailer y un directorio en las vistas


para este, muy parecido a los controllers.

Si revisamos el mailer generado podemos ver que un mailer


hereda de ActionMailer::Base

1
2

class UserMailer < ApplicationMailer


end

Modificando el mailer y como probarlo


Los mailers son muy similares a los controladores, ellos tienen
mtodos, llamados acciones, y usan vistas para estructurar el
contenido. La diferencia es que en vez de generar contenido tipo
HTML para ser mostrado en el browser, se crea un mensaje para
ser enviado por correo.

Crear un mtodo para dar la bienvenida a quien se registra


en nuestra pgina
1. Aadir un mtodo llamado welcome_user
1
2
3
4
5
6
7
8
9
10
11
12
13

class UserMailer < ApplicationMailer


default from: 'notifications@example.com'
def welcome_email(user)
@user = user
@url = 'http://example.com/login'
mail(
to: @user.email,
subject: 'Welcome to My Awesome Site',
template_path: 'user_mailer', # opcional
template_name: 'welcome_mail') # opcional
end
end

El mtodo default, que acepta un hash como parmetro: aqu


estamos seteando el header from: para todos los mensajes en
esta clase. Si queremos setear el from para todos lo mailer lo
hacemos en el archivo application_mailer El mtodo mail:
es donde armamos el mensaje de correo. aqu estamos pasando a
quien

:to

y el asunto

template_name

:subject .

template_path

setean la carpeta en donde esta la vista y el

nombre de esta respectivamente. Estos son campos opcionales y


solo los agregaremos en caso de tener nombres personalizados o
reutilizar una vista

Al igual que en los controllers, todas las variables de instancia


denidas en este mtodo estarn disponibles para ser usadas en la
vista.

1. Crear la vista

Primero crearemos una vista HTML para el correo

1
2
3
4
5
6
7
8
9

<h1>Welcome to example.com, <%= @user.name %></h1>


<p>
You have successfully signed up to example.com,
your username is: <%= @user.login %>.<br>
</p>
<p>
To login to the site, just follow this link: <%= @url %>.
</p>
<p>Thanks for joining and have a great day!</p>

Y tambin crearemos una vista en texto plano, ya que no todos los


clientes usan HTML por lo que enviar las dos opciones es una
buena practica.

1
2
3
4
5
6
7
8
9

Welcome to example.com, <%= @user.name %>


===============================================
You have successfully signed up to example.com,
your username is: <%= @user.login %>.
To login to the site, just follow this link: <%= @url %>.
Thanks for joining and have a great day!

Al tener las dos vistas, action mailer las detectara y enviara un


correo de tipo multipart/alternative

1. Testeando el mailer

En el ambiente de desarrollo podemos usar ActionMailer Preview


para testear nuestros correos. Para eso iremos al archivo
correspondiente

nuestro

mailer:

test/mailers/previews/user_mailer_preview.rb

En el

llamaremos a nuestro mtodo welcome_email y le pasaremos un


usuario cualquiera como parmetro

1
2
3
4
5

class UserMailerPreview < ActionMailer::Preview


def welcome_email_preview
UserMailer.welcome_email(User.last)
end
end

si

ahora

vamos

la

http://localhost:3000/rails/mailers/user_mailer,
listado

con

los

test

creados,

en

este

siguiente
tendremos
caso

solo

url:
un
uno:

welcome_email_preview . Si entramos a el veremos el correo


que se enviara en sus dos versiones, HTML y Texto Plano, y
podremos revisar si esta todo ok.

Enviando el correo usando ActionMailer y Gmail


Por defecto Rails trata de enviar los correos usando el protocolo
SMTP. Para poder enviar los correos conguraremos nuestra
aplicacin para que use nuestra cuenta de gmail.

Es importante recordar que la informacin sensible, como por ejemplo nuestro nombre
de usuario y contrasea de gmail, nunca deben ser usados de manera explicita en
nuestros archivos de conguracin, ya que existen pequeos programas, llamados web
crawlers o web spiders, que se dedican a buscar este tipo de informacin sensible. Por
lo tanto para usar estos datos los pasaremos de manera implcita a nuestros archivos
de conguracin usando variables de entorno con la gema dotenv-rails

1. Cmo implementar y usar variables de entorno?


Primero instalamos la gema dot-env en nuestro gemle:
1

gem 'dotenv-rails', groups: [:development, :test]

Luego creamos un archivo llamado .env en nuestra


aplicacion
Antes de cualquier otra cosa agregamos este archivo al
.gitignore

ya que NO queremos que git le haga

seguimiento y lo suba a github!!!


Dentro de ese archivo creamos las variables a usar:
1
2

GMAIL_USERNAME=ninombredeusurario
GMAIL_PASSWORD=miclave

El nombre de la variable puede ser cualquier cosa y puede ir en


mayusculas o minsculas. Es importante eso si que entre el
nombre de la variable, el signo igual ( = ) y el valor de de la
variable no tengan espacio entre medio

Ahora para poder usar estas variables en algn archivo las


llamamos de la siguiente forma:
1

ENV['GMAIL_USERNAME']

Donde GMAIL_USERNAME es el nombre de la variable que


queremos usar.

1. Conguracin de ActionMailer

Para congurar ActionMailer la mejor opcin es usar los archivos


de ambiente (como enviroment.rb, production.rb, development.rb,
etc). Pueden revisar las opciones de conguracin aqui.

En

nuestro

caso

usaremos
ya que estamos

/config/environments/development.rb

trabajando en el ambiente de desarrollo, esta conguracin


tambin

se

debe

hacer

/config/environments/production.rb

para

en
cuando

estemos en produccin.

1
2
3
4
5
6
7
8
9
10

config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
user_name: ENV['GMAIL_USERNAME'],
password: ENV['GMAIL_PASSWORD'],
authentication: :login,
enable_starttls_auto: true
}

Para el ambiente de desarrollo recomiendo agregar lo siguiente:

config.action_mailer.perform_deliveries = false

Esto para que al hacer pruebas en nuestra aplicacin no se enven


los correos.

1. Enviar el correo al usuario

Para usar nuestro mailer y enviar el mensaje cuando se crea un


nuevo usuario, lo llamaremos desde el UserController en el
mtodo create que es donde se crea y guarda el usuario. Es
importante que sepan que para que el correo efectivamente se
enve tenemos que terminar de llamarlo con el mtodo
:deliver_now

:deliver_later , la diferencia entre

ambos la veremos mas adelante.

class UsersController < ApplicationController

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

def create
@user = User.new(params[:user])
respond_to do |format|
if @user.save
# Tell the UserMailer to send a welcome email after save
UserMailer.welcome_email(@user).deliver_later
format.html
format.json
else
format.html
format.json
end
end
end
end

{ redirect_to(@user, notice: 'User was successfully created.'


{ render json: @user, status: :created, location: @user
{ render action: 'new' }
{ render json: @user.errors, status: :unprocessable_entity

En el caso de estar creando un nuevo registro, como en este


ejemplo, es importante que el envo del correo se realize una vez
que el registro se ha guardado, no antes ya que no queremos
enviar el correo y despus al tratar de guardar el registro se
presenta un error y este no se guarda.

ActionMailer y Devise
Que pasa si queremos enviar un correo de bienvenida cuando se
registra un nuevo usuario si estamos usando devise? Cuando
usamos devise no tenemos un controlador para User como el del
ejemplo anterior, por lo que tendremos que ver que opciones
tenemos para poder enviar el correo.

Primera Opcin: creando el controlador de devise para el


usuario
1. Crear el controlador de devise Solo necesitamos crear el
controlador de registros de devise, para eso corremos el
siguiente comando, asumiendo que tenemos un modelo user

hecho con devise


1

rails g devise:controllers users -c=registrations

Esto solo creara el controller registrations dentro de la carpeta


user
2. Modicar el controller Ahora que tenemos el controller creado
lo modicaremos y lo dejaremos as:
1
2
3
4
5
6

class Users::RegistrationsController < Devise::RegistrationsController


def create
super
UserMailer.welcome\_email(@user).deliver_later unless @user.invalid
end
end

1. Decirle a devise que use el controller creado anteriormente


En el archivo de rutas ( routes.rb ) le diremos a devise que
use este nuevo controller
1
2
3

...
devise_for :users, controllers: { registrations: 'users/registrations' }
...

Eso es todo, ahora cuando un usuario se registre se enviara un correo de bienvenida.

Segunda Opcin: modificando el modelo de usuario


En esta segunda opcin no es necesario crear nada adicional ya
que solo modicaremos el modelo de usuario

1. Modicar el Modelo de usuario Creamos un mtodo privado


dentro del modelo de usuario
1
2
3

class User < ActiveRecord::Base


# ...
# cdigo omitido

4
5
6
7
8
9
10

private
def send_welcome_email
UserMailer.welcome_email(self).deliver_later
end
end

1. Ejecutar el mtodo cada vez que se crea un nuevo usuario


En el mismo modelo, usaremos un callback para llamar a el
mtodo que creamos anteriormente y lo haremos despus de
que se cree un nuevo usuario por lo que utilizaremos el callback
:after_create
1
2
3
4
5
6

class User < ActiveRecord::Base


after_create :send_welcome_email
# cdigo omitido
# ...
end

Eso es todo en esta segunda opcin.

ActionMailer y ActiveJob: deliver_now, deliver_later?


Como se menciono anteriormente, para enviar el correo hay que
pasar el mtodo
nuestro

mailer,

:deliver_now
en

el

caso

o
del

:deliver_later
ejemplo

seria

as:

UserMailer.welcome_email(user).deliver_now .

La

diferencia entre estos dos mtodos la podemos deducir de su


nombre:

1. deliver_now: (sincrona) enva el correo inmediatamente en el


proceso en el que fue llamado, si han realizado pruebas se
habrn dado cuenta que al crear un nuevo usuario este proceso
demora un poco mas, porque ahora cuando se crea un nuevo
usuario se llama al mailer para que enve el correo, el mailer a

su vez tiene que hacer las conexiones con el servidor de correo


y autenticarse para luego enviar el correo, una vez que se
termina de enviar el correo el proceso de creacin del usuario
puede terminar.
2. deliver_later: (asncrona) a diferencia de su hermano, este
mtodo no enva el correo de manera inmediata al ser llamado,
lo que hace es dejarlo en una cola de trabajo (queue) a la
espera de ser enviado, por lo que el proceso de creacin del
usuario no demora ms ya que no tiene que esperar a que se
enve el correo para poder terminar su proceso. Si han hecho
pruebas con deliver_later se habrn dado cuenta de que an as
el tiempo de creacin del usuario no ha mejorado con respecto
a deliver_now Por qu? La respuesta es ActiveJob

ActiveJob y deliver_later
A partir de la versin 4.2 Rails trae incorporado el framework
ActiveJob para declarar trabajos (jobs) y que estos puedan correr
en alguno de los backends que manejan colas de trabajo
(queueing). Estos trabajos pueden ser cualquier cosa, desde
mantenimientos programados, cobros, envo de correos, etc

Los backends que manejan estas colas de trabajo son gemas que
podemos integrar en nuestra aplicacin, ejemplo de estas son
Delayed Job, Resque, Sucker Punch, Sidekiq, etc Gracias a
ActiveJob podemos usar cualquiera de estas e incluso cambiar en
medio del desarrollo sin tener que reescribir nuestros trabajos.
Para saber mas sobre ActiveJob pueden leer la guia ocial aqui

El punto que nos interesa a nosotros en esta gua es el uso de


ActionMailer y ActiveJob. Gracias a que estos dos son parte de
Rails, ActiveJob esta integrado con ActionMailer, por lo que
podemos enviar correos de manera asncrona en una cola de
trabajo utilizando el mtodo deliver_later

1
2

# Si quieres enviar el correo inmediatamente usa #deliver_now


UserMailer.welcome_email(@user).deliver_now

3
4

# Si quieres enviar el correo mediante ActiveJob usa #deliver_later


UserMailer.welcome_email(@user).deliver_later

Entonces Por qu, si estn integrados, al usar deliver_later se


comporta como deliver_now? Por defecto, cuando no tenemos
ningn backend asociado a ActiveJob su comportamiento sera
ejecutar los trabajos de manera inline, es decir, inmediatamente.

Para poder aprovechar ActiveJob lo que haremos sera integrar un


backend, en este caso usaremos sucker_punch ya que no necesita
de muchos pasos para congurarlo.

1. Agregar la gema sucker_punch


1

gem 'sucker_punch'

Como ya saben despus de agregar una gema corremos bundle en


la terminal

1. Congurar ActiveJob para que use sucker_punch Esto lo


haremos en el archivo
/config/environments/production.rb , si queremos
hacer pruebas en el modo de desarrollo tendremos que hacerlo
tambin en /config/environments/development.rb
1

config.active_job.queue_adapter = :sucker_punch

1. NO HAY PASO 3 En serio, esto es todo lo que hay que hacer


para poder enviar los correos de forma asncrona usando
ActiveJob y deliver_later
Si hacen una prueba ahora se darn cuenta que el proceso de
crear un usuario ya no demora como antes y el correo se enva
igual!!!

40) Testings automatizado con Guard


En Rails es posible automatizar completamente los test ocupando
guard.

Uno se preguntara para que automatizar ms, puesto que con la


simple instruccin rake se corren todos los test denidos, pero
la gema Guard permite que se corran automticamente los tests
respectivo cada vez que modicas un archivo de un controller,
xture, modelo o test, y te avisa si producto de la introduccin de
alguna mejora rompiste alguna funcionalidad ya existente en el
sistema.

Esta gua se ha probado con: * Ruby versin 2.1.2 o mayor * Rails


versin 4.1.5 o mayor * MiniTest versin 5.4.0 o mayor

Instalar Guard en nuestro proyecto


Primero vamos a aadir las gemas necesarias a nuestro Gemle

1
2
3
4
5
6
7
8
9

group :development do
gem 'guard'
gem 'guard-minitest'
gem 'minitest-reporters'
# notificaciones, solo usuarios de Mac OS X 10.8 o mayor
gem 'terminal-notifier'
gem 'terminal-notifier-guard'
end

si van a usar las noticaciones tienen que instalar terminal-notier con


brew (brew install terminal-notier)

Que hace cada gema?

guard : aade soporte para la herramienta Guard que maneja


eventos y modicaciones de archivos.
guard-minitest : encargara de monitorear los cambios y
ejecutar nuestros test usando minitest.
minitest-reporters : nos permite customisar el output de
nuestros test y darle color a los resultados.
terminal-notifier y terminal-notifier-guard : solo
para los usuarios de osx 10.8 o mayor, nos mostrar un
mensaje en el centro de noticaciones cuando se ejecuten los
test.

Aadimos estas gemas al grupo de desarrollo porque se


ejecutarn desde el entorno de desarrollo y no afectan el entorno
de testing.

Configurar Guard
Ahora que tenemos guard instalado en nuestro proyecto tenemos
que generar su archivo de conguracin, para esto ejecutamos lo
siguiente en la terminal:

bundle exec guard init minitest

Esto va a crear un archivo llamado

Guardfile

en el root de

nuestro proyecto. Lo que hace este archivo es decirle a Guard que


archivos tiene que monitorear y que hacer cuando alguno de estos
se ha modicado. En nuestro caso va a monitorear la carpeta
app

y todo su contenido y llamara los test correspondientes

usando minitest.

Al crear el Guardfile ste viene as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard :minitest do
# with Minitest::Unit
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
watch(%r{^lib/(.*/)?([^/]+)\.rb$})
{ |m| "test/#{m[1]}test_#{m[2]}.rb"
watch(%r{^test/test_helper\.rb$})
{ 'test' }
#
#
#
#

with Minitest::Spec
watch(%r{^spec/(.*)_spec\.rb$})
watch(%r{^lib/(.+)\.rb$})
{ |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^spec/spec_helper\.rb$}) { 'spec' }

#
#
#
#
#
#
#
#

Rails 4
watch(%r{^app/(.+)\.rb$})
{ |m| "test/#{m[1]}_test.rb"
watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
watch(%r{^app/controllers/(.+)_controller\.rb$})
{ |m| "test/integration/#{m[
watch(%r{^app/views/(.+)_mailer/.+})
{ |m| "test/mailers/#{m[1]}_m
watch(%r{^lib/(.+)\.rb$})
{ |m| "test/lib/#{m[1]}_test
watch(%r{^test/.+_test\.rb$})
watch(%r{^test/test_helper\.rb$}) { 'test' }

#
#
#
#
end

Rails < 4
watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
watch(%r{^app/helpers/(.*)\.rb$})
{ |m| "test/helpers/#{m[1]}_test.rb" }
watch(%r{^app/models/(.*)\.rb$})
{ |m| "test/unit/#{m[1]}_test.rb" }

Estamos usando Rails 4 as que descomentaremos las lneas 16 a la


22 y borraremos el resto de las lneas comentadas dejando el
archivo as:

1
2
3
4
5
6
7
8

# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard :minitest do
# with Minitest::Unit
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
watch(%r{^lib/(.*/)?([^/]+)\.rb$})
{ |m| "test/#{m[1]}test_#{m[2]}.rb"
watch(%r{^test/test_helper\.rb$})
{ 'test' }

9
10
11
12
13
14
15
16
17
18

# Rails 4
watch(%r{^app/(.+)\.rb$})
{ |m| "test/#{m[1
watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers'
watch(%r{^app/controllers/(.+)_controller\.rb$})
{ |m| "test/integration/
watch(%r{^app/views/(.+)_mailer/.+})
{ |m| "test/mailers/
watch(%r{^lib/(.+)\.rb$})
{ |m| "test/lib/#{
watch(%r{^test/.+_test\.rb$})
watch(%r{^test/test_helper\.rb$}) { 'test' }
end

Qu hace cada lnea?

1
2
3

guard :minitest do
# ...
end

Aqu le decimos a Guard que el siguiente bloque se tiene que


ejecutar con Minitest. Guard se puede congurar con multiples
plugins que pueden realizar muchas otras cosas.

watch(%r{^test/(.*)\/?test_(.*)\.rb$})

Esta lnea monitorea todos los archivos .rb en las subcarpetas


de test/ y ejecuta el archivo que se ha modicado.

watch(%r{^lib/(.*/)?([^/]+)\.rb$})

{ |m| "test/#{m[1]}test_#{m[2]}.rb"

Esta lnea monitorea todos los archivos .rb que se encuentran


en el directorio lib y ejecutar el test correspondiente si es que
existe alguno.

watch(%r{^test/test_helper\.rb$})

{ 'test' }

Esta lnea monitorea el archivo

test_helper.rb

y si hay un

cambio ejecuta todos los test.

watch(%r{^app/(.+)\.rb$})

{ |m| "test/#{m[1]}_test.rb" }

Esta lnea monitorea todos los archivos en el directorio

app

ejecuta el test correspondiente. En una aplicacin tpica de Rails


estos serian los models, controllers, helpers y mailers.

watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers'

Esta lnea monitorea el archivo aplication_controller.rb y


ejecuta todos los test de controladores si se modica.

watch(%r{^app/controllers/(.+)_controller\.rb$})

{ |m| "test/integration/

Esta lnea monitorea los controladores y ejecuta el test de


integracin correspondiente cuando hay modicaciones.

watch(%r{^app/views/(.+)_mailer/.+})

{ |m| "test/mailers/

Esta lnea monitorea la carpetas de las vitas de los mailers y


ejecuta el test correspondiente cuando hay modicaciones.

watch(%r{^lib/(.+)\.rb$})

Esta lnea monitorea la carpeta

{ |m| "test/lib/#{m

lib

y ejecuta el test

correspondiente cuando hay modicaciones si es que lo existe un


test asociado.

1
2

watch(%r{^test/.+_tests\.rb$})
watch(%r{^test/test_helper\.rb$}) { "test" }

Estas lneas monitorear todos los archivos terminados en


_test.rb y test_helper.rb y si hay un cambio en alguno
de ellos ejecutar el test.

Esta es la conguracin bsica de nuestro Guardle y como ven es


muy completa. Sin embargo podemos agregar unos monitores
extras, que en lo personal encuentro de bastante utilidad.

Agregaremos las siguientes lneas a nuestro Guardle:

1
2
3
4
5
6
7
8
9
10
11
12

# agregar la siguiente lnea al inicio del archivo


require 'active_support/inflector'
guard :minitest do
# ... cdigo anterior omitido

# extra tests
watch(%r{^app/views/(.+)/.+}) { |m| "test/controllers/#{m[1]}_controllet_test.rb"
watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/models/#{m[1].singularize
watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/controllers/#{m[1]}_controller_test.
end

El detalle de los ajustes adicionales

require 'active_support/inflector'

Aqu aadimos la clase inector de Rails para poder singularizar


los nombres de los xtures. Esto nos permite convertir Posts en
Post para cualquier modelo o controlador.

watch(%r{^app/views/(.+)/.+}) { |m| "test/controllers/#{m[1]}_controller_test.rb"

Esta lnea correra el el test de controlador si cambia alguna de las


vistas asociadas.

watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/models/#{m[1].singularize}_test.rb"

Esta lnea ejecutar los tests de modelo si los xtures cambian.


Aqu es donde usamos la clase inector de Rails

watch(%r{^test/fixtures/(.+)\.yml}) { |m| "test/controllers/#{m[1]}_controller_test.rb"

Esta lnea ejecutar los tests de controlador si los xtures cambian.

Configurar Minitest-Reporters
Antes de continuar con Guard vamos a congurar nuestro
ambiente de testing para que haga uso de minitest-reporters y as
el output de nuestros test se vera mejor y con colores.

1. Decirle a nuestros test que usaremos minitest-reporters


En el archivo test/test_helper.rb aadimos lo siguiente:
1
2
3
4
5
6

ENV['RAILS_ENV'] ||= 'test'


require File.expand_path('../../config/environment'
require 'rails/test_help'
require "minitest/reporters" # lnea que hay que aadir
# ... resto del archivo omitido

2. Iniciar minitest reporters y congurarlo


En el mismo archivo que en el paso anterior aadimos lo
siguiente:
1
2

# ... cdigo omitido

3
4
5
6
7

class ActiveSupport::TestCase
# ... cdigo omitido
Minitest::Reporters.use! Minitest::Reporters
end

La primera parte

Minitest::Reporters.use!

le dice a

nuestro test_helper que usaremos minitest-reporters.


La

segunda

Minitest::Reporters::SpecReporter.new

parte
le dice que

estilo de reporte queremos usar, en este caso SpecReporter.

Para que vean la diferencia:

Sin minitest-reporters

1
2
3
4
5
6
7
8
9

Run options: --seed 34387


# Running:
...
Finished in 0.327633s, 70.2005 runs/s, 109.8791 assertions/s.
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

Con minitest-reporters

1
2
3
4
5
6
7
8
9
10
11
12

Started with run options --seed 28340


ProductsControllerTest
test_should_get_new

PASS (0.19s

ProductTest
test_should_not_create_product_withoud_description

PASS (0.00s

UserTest
test_user_owns_products

PASS (0.02s

Finished in 0.34634s

13

3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

Correr Guard para automatizar el testing


Ahora que nuestro Guardle esta completo solo falta ejecutarlo
desde nuestra lnea de comando:

bundle exec guard

Esto iniciara Guard, analizara el Guardle y los plugins instalados y


carrera nuestros test de manera automtica. Al correr Guard
nuestra consola quedara secuestrada por este proceso, al igual
que cuando corremos el servidor de Rails. Para salir de Guard
escribimos exit

Una vez iniciado Guard veremos un output similar a este en


nuestra consola:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

13:07:02 - INFO - Guard::Minitest 2.4.4 is running, with Minitest::Unit 5.8.2


13:07:02 - INFO - Running: all tests
Started with run options --seed 46327
ProductsControllerTest
test_should_show_product
test_should_get_edit

PASS (0.18s
PASS (0.03s

UserTest
test_should_not_create_user_withoud_username
test_should_not_create_user_without_name

PASS (0.01s
PASS (0.00s

ProductTest
test_should_not_create_product_withoud_description
test_price_should_be_a_float

PASS (0.00s
PASS (0.00s

Finished in 0.31300s
6 tests, 6 assertions, 0 failures, 0 errors, 0 skips

20
21

13:07:05 - INFO - Guard is now watching at '/Users/Username/Path/To/Project'


[1] guard(main)>

Con guard cualquier cambio que hagamos en los archivos


monitoreados gatillar los test relevantes y se ejecutarn
automticamente, y si estas en OSX e instalaste terminal-notier
recibirs una noticacin.

Si quieres forzar la ejecucin de los test, simplemente presiona


enter

en el prompt de Guard ( [1] guard(main)> ) en la

consola.

Eso es todo, recuerda que para salir del prompt de Guard (y


obviamente del testing automatizado) escribe exit en l.

Eso es todo. No te olvides de siempre usar testing cuando


desarrolles un proyecto!

Seccin V: Deployment avanzado con Amazon y VPS


En esta seccin aprenderemos todo lo necesario para congurar
nuestro propio servidor y preparar nuestra aplicacin para su
publicacin.

41) Rails y Nginx con Passenger en Ubuntu: Preparando


nuestro entorno de produccin (DigitalOcean).
Introduccin
Ruby on Rails (RoR) es un framework de desarrollo que le entrega
a los desarrolladores una fcil y rpida herramienta para crear
aplicaciones web, y Nginx es un servidor web ligero de alto
rendimiento. Estos dos programas puedes ser congurados
fcilmente para que trabajen en conjunto en un VPS (Virtual Private
Server) con Phusion Passenger.

Pushion Passenger es un servidor web y de aplicaciones,


diseado para integrarse con Nginx o Apache. Originalmente
creado para aplicaciones hechas con RoR, lo que hace que sea la
recomendada por la comunidad de RoR, ademas de ser estable,
rpido y escalable.

Otros servidores de aplicaciones son Unicorn y Puma.

Acerca de esta gua.


Esta gua esta pensada como un recetario para congurar y
preparar nuestro servidor utilizando Nginx, Passenger, RVM, Ruby,
Rails y Postgres; y se espera que ya tengan creada su maquina
virtual (VPS) con Ubuntu 14.04 y 15.04 1 en DigitalOcean o Linode.

Se espera que el lector de esta gua sepa como utilizar la terminal y


tenga conocimiento de al menos comandos bsicos de este. As
como tambin se espera sepan usar postgres.

Convenciones.
1. Los trminos servidor, server o VPS hacen referencia a su
maquina virtual.
2. El termino local hace referencia a su computador.
3. Para determinar el entorno en que tenemos que ejecutar los
comandos y en cual estaremos trabajando en cada seccin se
usara: local para la maquina local o servidor para la maquina
virtual.
4. Se usara para los ejemplos la IP: 111.11.111.11, esta tienen que
ser remplazada por la ip de su maquina virtual.
5. Para mostrar las instrucciones a ejecutar en el terminal se
antepondr el signo $ , que es la representacin de su lnea
de comandos que esta lista para recibir una instruccin (no hay
que tipearlo), y tendrn el siguiente estilo:
1

$ gem install postgres

*Cada lnea que empieza con $ se ejecuta por separado.


Para mostrar las respuestas, errores o advertencias que nos
arroja el terminal al ejecutar una instruccin no se antepone el
signo $ y tendrn el siguiente estilo:
1

Agent pid 32877

6. Se espera que tengan una llave ssh creada.

Paso 0 Como acceder a nuestro servidor


local

Para acceder a nuestro VPS usaremos el Terminal como interface


de conexin mediante SSH.

En nuestra terminal:

$ ssh root@111.11.111.11

La primera vez que nos tratemos de conectar se nos mostrar


un mensaje como este:

1
2
3

The authenticity of host 'xx.xx.xx.xx (xx.xx.xx.xx)' can't be established.


ECDSA key fingerprint is 79:95:46:1a:ab:37:11:8e:86:54:36:38:bb:3c:fa:c0.
Are you sure you want to continue connecting (yes/no)?

Este nos advierte que no se puede establecer la autenticidad del


host y si queremos conectarnos de todas formas. Obviamente le
decimos que Yes.

Sabremos que estamos iniciados porque veremos algo como esto


en nuestra terminal servidor

$ root@ip-111-11-111-11:~$

Esto no indica que estamos logueados en la maquina con ip


111.11.111.11 en el usuario root, todo lo que escribamos ahora se
ejecutar en el servidor.

Para desconectarnos del servidor y "volver" a nuestra maquina


local escribimos lo siguiente:

servidor

$ exit

Invalid Locale Warning


servidor

En algunos casos puede ser que al entrar a su maquina les


muestre esta advertencia:

1
2
3

WARNING! Your environment specifies an invalid locale.


This can affect your user experience significantly, including the
ability to manage packages.

O que al instalar un paquete les muestre esto:

1
2
3
4
5
6
7
8
9
10
11

perl: warning: Setting locale failed.


perl: warning: Please check that your locale settings:
LANGUAGE = "en_US:en",
LC_ALL = (unset),
LC_MESSAGES = "en_US.UTF-8",
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory

Este es un error que tenemos que arreglar o si no tendremos


problemas con los paquetes a instalar, sobre todo con Postgres.
Para arreglarlo hacemos lo siguiente:

1. Primero generamos el locale que no esta denido:


1

$ sudo locale-gen "en_US.UTF-8"

2. Reconguramos lo locales:
1

$ sudo dpkg-reconfigure locales

Solo si por alguna razn con los comandos anteriores no se arregla


el error haremos lo siguiente:

1. Abrimos el archivo environment con algn editor (vim, nano,


emacs, etc):
1

$ sudo vim /etc/environment

2. Agregamos las siguientes lneas:


1
2

LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8

Ahora ya no deberamos tener ese error al instalar un paquete

Configurar el timezone en nuestro servidor


Por qu? Cuando revisamos nuestros logs, estos tienen la marca
de tiempo en GMT y no en nuestra zona horaria, lo que hace que
revisar nuestros logs sea un poco mas difcil.

Para congurar el timezone del servidor a nuestra zona horaria


local haremos lo siguiente:

1. Revisaremos el timezone del servidor:


1

$ date

Esto imprime la fecha, hora y zona horaria de nuestro servidor:

Mon Aug 31 16:52:18 MST 2015

O podemos revisar solo la zona horaria, as:

$ more /etc/timezone

Que imprimira el timezone del servidor:

US/Arizona

1. Si el timezone del servidor no corresponde a nuestra zona


horaria ejecutamos lo siguiente:
1

sudo dpkg-reconfigure tzdata

y seguimos las instrucciones en pantalla:

Una vez elegida nuestra zona horaria, podemos volver a


comprobar ejecutando los comandos del paso anterior.

Acerca del usuario Root


servidor

En linux el usuario root es el usuario administrador y tiene


demasiados privilegios. Debido a esto se recomienda no usarlo de
manera regular, ya que por accidente podemos hacer cambios
destructivos en nuestro servidor.

Que hacer entonces?

Para evitar lo anterior y seguir las recomendaciones y buenas


practicas crearemos un nuevo usuario para el uso diario, al que le
daremos los privilegios necesarios para cuando lo necesitemos.

Dentro de nuestro VPS haremos lo siguiente:

1. Creamos un nuevo usuario, este se llamara deploy. Se nos


pedir crear una contrasea, esta no se nos puede olvidar, ya
que con ella nos tendremos que conectar al VPS y ejecutar
comandos sudo, y opcionalmente se nos pedir informacin
adicional.
1

$ adduser deploy

(El nombre deploy es un buen nombre para el usuario de


deployment, pero no es necesario que sea este)
2. Aadimos el nuevo usuario al grupo de sudoers. En este
paso al agregar al usuario al grupo de los sudoers le daremos la
posibilidad

de

ejecutar

comandos

con

privilegio

de

administrador cuando sea necesario, eso si tendr que


anteponer la palabra sudo (super user) al comando y se le
pedir su clave.
1

$ gpasswd -a deploy sudo

3. Salimos del servidor


1

$ exit

4. * y nunca ms entramos como root


1

$ ssh deploy@111.11.111.11

*Esta vez se nos pedir la contrasea que pusimos al crear el


usuario.

Cmo ingresar sin tener que ingresar la clave cada vez que nos
queremos conectar a nuestro servidor?
local

Para no tener que poner la clave cada vez que queremos


conectarnos al servidor y tambin as evitar problemas en un
futuro al usar Capistrano para hacer deploy, haremos lo siguiente:

Copiamos nuestra llave publica al llavero del usuario deploy.

$ ssh-copy-id deploy@111.11.111.11

En esta etapa, que demora un poco, se nos pedir la clave del


usuario deploy para poder copiar la llave ssh en el llavero del
usuario deploy

Solo si el paso anterior falla porque no encuentra el comando sshcopy-id, lo instalaremos de la siguiente forma:

En Mac.

$ brew install ssh-copy-id

En Linux.

$ apt-get install ssh-copy-id

Ahora nos podremos conectar al servidor sin tener usar la


contrasea!!!

$ ssh deploy@111.11.111.11

Paso 1 Instalacin de RVM


servidor

Ahora que ya podemos entrar a nuestro servidor con un usuario


diferente a root, vamos a hacer el primer paso para congurar
nuestro entorno, instalaremos RVM (Ruby Version Manager) el cual
nos permitir instalar Ruby y manejar distintas versiones de ste.

1. Antes de hacer cualquier cosa haremos un update para


asegurarnos que todos los paquetes que bajaremos a
nuestro VPS estn al da
1

$ sudo apt-get update

2. Instalacin de RVM
1

$ curl -L get.rvm.io | bash -s stable

En este paso nos mostrar un warning y si leemos bien veremos


las siguiente lneas:
1
2
3
4
5
6
7
8
9
10
11
12
13

...
gpg: Can't check signature: public key not found
Warning, RVM 1.26.0 introduces signed releases and automated check of sig
Assuming you trust Michal Papis import the mpapis public key (downloading

GPG signature verification failed for '/home/deploy/.rvm/archives/rvm-1.2


try downloading the signatures:

gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A170311


or if it fails:
command curl -sSL https://rvm.io/mpapis.asc | gpg --import -

14
15
16
17
18

the key can be compared with:


https://rvm.io/mpapis.asc
https://keybase.io/mpapis

Ahi mismo nos dice que tenemos que descargar la rma para
autenticar el paquete antes de instalar, y eso lo hacemos
copiando la que dice:
gpg

keyserver

hkp://keys.gnupg.net

recv-keys

409B6B1796C275462A1703113804BB82D39DC0E3
Y la pegamos en el terminal
1

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703

despus volvemos a ejecutar el instalador de RVM


1

$ curl -L get.rvm.io | bash -s stable

Ahora si se instalara sin problemas RVM. Eso si para poder


usarlo tenemos que cargarlo a nuestro terminal.
1

$ source /home/deploy/.rvm/scripts/rvm

(esto solo se hace una sola vez)


3. Ahora le diremos a RVM que instale todas las dependencias
que necesita.
1

$ rvm requirements

Paso 2 Instalacin de Ruby y de Rails


servidor

Ahora que tenemos RVM instalado, lo usaremos para instalar Ruby.

1. Le pedimos a RVM que instale la versin de Ruby que


necesitamos
1

$ rvm install 2.3.1

A la fecha de la gua Ruby-2.3.1 es la ltima versin.

1. Ahora le diremos a RVM que use esa versin por defecto


1

$ rvm use 2.3.1 --default

2. Ahora nos aseguraremos de que tenemos todos los


componentes requeridos por RoR
1

$ rvm rubygems current

Si todo sale bien ahora podremos instalar RoR y otras gemas, pero
antes de eso le diremos a nuestra maquina que no descargue la
documentacin de las gemas al instalarlas, ya que ellas demoran el
proceso y usan espacio innecesariamente.

$ echo "gem: --no-ri --no-rdoc" > ~/.gemrc

Ahora si instalamos RoR

$ gem install rails -v 4.2.7

Por un problema con la ltima versin de Rubygems y Ruby (2.3.1),


en el proceso de instalacin de Rails tendremos un problema con
la gema nokogiri , para solucionarlo haremos lo siguiente:

$ sudo apt-get install libgmp-dev

Despus de eso instalamos la gema nokogiri :

$ gem install nokogiri

Y por ltimo volvemos a instalar Rails:

$ gem install rails -v 4.2.7

Ahora si la instalacin se har correctamente.

Paso 3 Instalacin de Nginx y Passenger


servidor

Una vez que tenemos RVM y Ruby instalaremos Nginx y Passenger,


pero antes tenemos que preparar el servidor.

1. Lo primero que tenemos que hacer es instalar la llave GPG


de Phusion Passenger
1

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 561F9B9CA

2. Despus descargaremos un paquete que le dar soporte


HTTPS a APT
1

$ sudo apt-get install apt-transport-https ca-certificates

3. Luego aadimos el repositorio de passenger al source list


de nuestra maquina para poder usarlo
Para Ubuntu 14.04
1

$ sudo sh -c "echo 'deb https://oss-binaries.phusionpassenger.com/apt/pas

Para Ubuntu 15.04


1

$ sudo sh -c "echo 'deb https://oss-binaries.phusionpassenger.com/apt/pas

4. Despus de agregarla tenemos que cambiar el grupo y


permisos para poder hacer un update a los paquetes
disponibles para incluirlo a ellos
1
2
3

$ sudo chown root: /etc/apt/sources.list.d/passenger.list


$ sudo chmod 600 /etc/apt/sources.list.d/passenger.list
$ sudo apt-get update

Ahora que tenemos el servidor preparado podemos instalar Nginx


con Passenger:
1

$ sudo apt-get install nginx-full passenger

Y hacemos correr el servidor nginx

$ sudo service nginx start

Paso 4 Habilitando Passenger en Nginx


servidor

Para hacer el siguiente paso pueden usar vim, nano o emacs como

editor de texto.

En el archivo de conguracin de Nginx (nginx.conf) se tiene que


especicar donde est passenger. Para eso se tienen que
descomentar (quitar el signo # que se antepone) las lneas que
empiezan con:

1
2

# passenger_root ....
# passenger_ruby ....

1. Primero abrimos el archivo de conguracin de nginx con


algn editor. (Nginx se encuentra en la carpeta /etc del
servidor)
1

$ sudo vim /etc/nginx/nginx.conf

2. Ahora descomentamos las lneas antes nombradas y las


dejamos asi:
1
2

passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger
passenger_ruby /home/deploy/.rvm/wrappers/ruby-2.3

Ojo que en la segunda lnea la versin de Ruby, que en este caso


es ruby-2.3.1, tiene que decir la versin que tienen instalada

Hecho esto guardamos los cambios y cerramos el archivo, y


tenemos que reiniciar el servidor nginx para que aplique los
cambios.

$ sudo service nginx restart

En caso de tener un problema al reiniciar el server puedes revisar


los logs as:

$ sudo tail /var/log/nginx/error.log

Paso 5 Instalacin y configuracin de Postgres.


servidor

Instalacin
Para poder trabajar con bases de datos lo primero que tenemos
que hacer es instalar Postgresql en nuestra maquina virtual junto
con algunas dependencias necesarias. Para ello ejecutamos lo
siguiente:

$ sudo apt-get install postgresql postgresql-contrib libpq-dev

Ahora para entrar al entorno de trabajo de postgres ejecutamos:

$ sudo -u postgres psql

El entorno de trabajo de postgres esta indicado con el siguiente


prompt: postgres=#

Para salir del entorno postgres y volver a nuestro usuario usamos


\q .

Creacin de un usuario en postgres


servidor

Ahora crearemos un superusuario en nuestro motor de base de


datos, ste sera capas de crear bases de datos y tendr todos los
privilegios sobre esta, en lo personal utilizo el nombre de la
aplicacin con alguna variante o simplemente uso deploy.

Crear el usuario:

$ sudo -u postgres createuser -s nombreUsuario

Ahora vamos a asignar un password al usuario de postgres que


acabamos de crear (para el password usa uno seguro que sea
distinto al del usuario de la VPS y que no se te olvide).

Primero entramos al entorno de postgres:

$ sudo -u postgres psql

Para comprobar que el usuario se creo correctamente los vamos a


listar usando \du , si nuestro usuario se encuentra en el listado
es porque se creo correctamente.

Ahora asignamos el password a nuestro usuario:

\password nombreUsuario

Se nos pedira ingresar la password y conrmarlo.

Creacin de la base de datos


En el entorno de postgres crearemos la base de datos asociada al
usuario que creamos en el paso anterior.

CREATE DATABASE nombreBaseDatos OWNER nombreUsuario;

Una vez terminado salimos del entrono de postgres usando \q .

Estos datos, nombre de usuario, contrasea y base de datos de


postgres, son los que usaremos para congurar nuestro archivo
database.yml en Rails (en la gua de capistrano), por lo que es muy
importante no olvidarlos.

Paso 6 Crear un Server Block


servidor

Cuando se usa Nginx, los server blocks (similar a los virtual hosts
en Apache) se usan para encapsular los detalles de conguracin y
servir mas de un dominio en un nico servidor.

Ahora veremos como congurar los server blocks en nuestra


maquina virtual.

1. Crear la carpeta donde se guardara nuestro proyecto o


proyectos en caso de tener mas de uno.
Nginx, por defecto, esta congurado para servir los documentos
que estn en el siguiente directorio:
1

/var/www/html

Nosotros no usaremos el default ya que es mas fcil trabajar


desde nuestro directorio del usuario. Para eso nos podemos
crear una nueva carpeta llamada

/www

/apps

(En esta

gua vamos a trabajar con la carpeta /www ). En este directorio

es donde cada uno de nuestros proyectos tendr su propia


carpeta, ej:
1
2

~/www/example/
~/www/test/

Crear el directorio:
1

$ mkdir -p ~/www/example

**La opcin -p le dice a mkdir que cree todos los directorios


padres necesarios si estos no existen
Ahora que tenemos creado el directorio vamos a continuar.
2. Crear una pgina de prueba
Vamos a crear una pgina de ejemplo, para tener algo que
mostrar al crear el server block. Ya que aun no subimos un
proyecto de Rails.
Crear un archivo index.html dentro de nuestro proyecto.
1

$ vim ~/www/example/index.html

Dentro del archivo escribiremos esto:


1
2
3
4
5
6
7
8

<html>
<head>
<title>Bienvenidos a Example.com</title>
</head>
<body>
<h1>El server block esta funcionando!</h1
</body>
</html>

Ahora guardamos y cerramos el archivo index.html


3. Crear el server block para nuestro proyecto
Ahora que tenemos contenido para servir, necesitamos crear el
server block que le dir a Nginx como hacer esto.
Por defecto Nginx viene con un server block llamado
default

que usaremos como base para nuestros propios

servers.

Este

se

encuentra

en

el

directorio

/etc/nginx/sites-available/ ,

en

este

directorio

creamos los server block que necesitamos. Para hacer esto


copiaremos el archivo default, y el nombre que usaremos es el
mismo nombre de la carpeta que nos creamos anteriormente:
1

$ sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/e

Ahora abrimos el archivo recin creado:


1

$ sudo vim /etc/nginx/sites-available/example

Y eliminamos todo lo que esta en el y escribimos lo siguiente:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name example.com www.example.com;
passenger_enabled on;
rails_env production;
root /home/deploy/www/example;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

Importante: todas las declaraciones terminan con un punto y


coma ;
Las lneas 2 y 3 le dicen que
puerto tiene que escuchar, en
el caso de un request del tipo
http el puerto es 80, y con el
parmetro default_server
le decimos que, en el caso de

que se haga un request a un


server_name que no coincide
con ninguno de los server block
disponibles, se cargue este
server block; Solo uno de
nuestros server block puede
tener la especicacin de
default_server!!!
En la lnea 5 seteamos a que
requests responder este
server block, en este caso
example.com, y ademas
podemos aadir alias, en este
caso www.example.com,
separados por un espacio.
En la lnea 7 activamos
passenger para este server
block.
En la lnea 8 seteamos en
ambiente de Rails que vamos a
ejecutar, en este caso el
ambiente de produccin.
En la lnea 10 con la directiva
root apuntamos al directorio
de nuestro proyecto, el path
tiene que ser absoluto. OJO
Cuando estamos trabajando
con una aplicacin de Rails la
ruta queda as:
root /home/deploy/www/example/current/public;
De la lnea 12 a la 16 le
estamos diciendo que las
pginas de error de servidor
apunten a una pgina esttica.
Guardamos el archivo y lo cerramos.
4. Habilitar el server block

Ahora que tenemos nuestro server block creado, tenemos que


habilitarlo. Esto se hace creando un enlace simblico de nuestro
server

block,

que

se

encuentra

/etc/nginx/sites-available/ ,

en
en

el
el

directorio
directorio

/etc/nginx/sites-enabled , este es el directorio que


Nginx lee cuando se inicia el servidor.
Podemos crear los links de la siguiente manera:
1

$ sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/

Ahora nuestro archivo se encuentra habilitado, pero tambin se


encuentra habilitado el archivo

default

que usamos para

crear nuestro server block y esto nos dar problemas ya que


como

lo

mencione

anteriormente

el

parmetro

default_server solo puede estar en un server block. Para


arreglar esto simplemente eliminamos el enlace simblico a
este:
1

$ sudo rm /etc/nginx/sites-enabled/default

Ahora solo falta reiniciar Nginx:


1

$ sudo service nginx restart

5. Probando nuestro server block


Para probar si todo sali bien, en el navegador vamos a visitar
nuestro servidor, como aun no tenemos un dominio usaremos
la ip de la maquina, en el caso de esta gua seria
http://111.11.111.11. Deberamos ver el mensaje que pusimos
en nuestro archivo index.

Paso 7 ltimos detalles


servidor

Para que nuestra aplicacin RoR funcione bien y en caso de usar


capistrano para hacer el deployment, tenemos que instalar los
siguientes paquetes:

1
2
3
4

$
$
$
$

sudo
sudo
sudo
sudo

apt-get
apt-get
apt-get
apt-get

update
install git # necesario para el uso de capistrano
install nodejs # obligatorio tener un ambiente js en el VPS
upgrade

Con esto ya hemos terminado de congurar y dejar lista nuestra


VPS para el deployment de una aplicacin RoR

Extras
Algunos comandos importantes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

$ ssh deply@ip_del_servidor
$ sudo apt-get install paquete
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo -u postgres psql
$
$
$
$

sudo
sudo
sudo
sudo

service
service
service
service

nginx
nginx
nginx
nginx

stop
start
reload
restart

$ sudo ln -s ruta/original/archivo ruta/destino


$ sudo tail -n 50 /var/log/nginx/error.log

42) Deployment con Capistrano


Introduccin
Cuando yo estaba aprendiendo Rails, como un novato, no tena ni
idea de cmo llegar de esta cosa que estaba trabajando en mi
mquina de desarrollo a un verdadero servidor web que otras
personas pudieran ver. Todas estas cosas Unix CLI (Command Line
Interface o terminal) parecan como magia negra para m
(probablemente, en parte porque mi terminal es de color negro) y
me sent como que necesitaba un doctorado en Ciruga Robtica
para poder hacerlo. Rails ha hecho tan fcil el desarrollo de
aplicaciones! Seguramente existe una manera de hacer deploy a
mis creaciones sin que explote mi cabeza!!!

Bueno, si y no. Servicios como Heroku estn tratando de quitar una


gran parte de la complejidad a la hora de hacer deploy una
aplicaciones web, y estn haciendo un buen trabajo. Pero como yo
estaba tratando de aprender sent que aquellos servicios no me
ayudaban a comprender lo que se estaba haciendo. Senta que al
menos en la CLI, estaba en control y podia ver con mis propios ojos
lo que estaba pasando (o lo que no estaba pasando). Y as, poco a
poco logre comprender un poco mas cmo mi servidor web
funcionaba.

En un principio hice todo manualmente (la copia de archivos, la


migracin de bases de datos, la instalacin de las gemas, reiniciar
servicios, etc). Pero rpidamente me di cuenta de por qu nadie
hace esto!!! En primer lugar, es terriblemente propenso a errores
escribir todos los comandos con mis torpes manos humanas. En
segundo lugar, a veces las cosas no funcionaban y yo no saba por
qu, y tenia que pasar horas averiguando dnde fue que perd un
signo o puse uno de mas. La moraleja de la historia es: encontrar la

manera de hacerlo una vez, y despus, guardarlo en un script que


se pueda repetir sin problemas (aparentemente los computadores
(ordenadores) son muy buenos haciendo lo mismo una y otra vez;
quin sabe?). Y es por eso que uso Capistrano y Git!

Conguraremos nuestra aplicacin de Rails para hacer deploy en el


servidor remoto utilizando Capistrano y Git para que este proceso
de implementacin sea automatizado, rpido y libre de dolores de
cabeza.

Creo que los dos aspectos clave de cualquier proceso de


implementacin son la velocidad y consistencia. Velocidad signica
que podemos repetir y corregir errores rpido y mantener nuestro
cdigo de produccin en sintona con nuestro cdigo de desarrollo.
Consistencia signica que sabemos que va a hacer lo mismo cada
vez, as que no tendremos miedo de hacerlo y estar al da. El uso
de un sistema de control de versiones como Git, junto con las
recetas de implementacin automatizadas de Capistrano satisface
estos criterios con facilidad.

Acerca de esta gua.


En esta gua usaremos un servidor remoto que tenga Ubuntu,
Passenger y Nginx instalados y congurados, as como tambin
acceso SSH a este. Si no tienes un servidor remoto con los
requerimientos antes mencionados, te recomiendo que sigas la
gua "Rails y Nginx con Passenger en Ubuntu: Preparando
nuestro entorno de produccin", que disponible para servidores
Amazon, DigitalOcean o Linode.

Se espera que el lector de esta gua sepa como utilizar la terminal y


tenga conocimiento de al menos comandos bsicos de este. As
como tambin se espera sepan usar postgres.

Convenciones.

1. Los trminos servidor, server o VPS hacen referencia a su


maquina virtual.
2. El termino local hace referencia a su computador.
3. Para determinar el entorno en que tenemos que ejecutar los
comandos y en cual estaremos trabajando en cada seccin se
usara: local para la maquina local o servidor para la maquina
virtual.
4. Se usara para los ejemplos la IP: 111.11.111.11, esta tienen que
ser remplazada por la ip de su maquina virtual.
5. Debes tener Git instalado y tener una cuenta en Github o en
Bitbucket. Y saber usarlos.
6. Para mostrar las instrucciones a ejecutar en el terminal se
antepondr el signo $ , que es la representacin de su lnea
de comandos que esta lista para recibir una instruccin (no hay
que tipearlo), y tendrn el siguiente estilo:
1

$ gem install postgres

*Cada lnea que empieza con $ se ejecuta por separado.


Para mostrar las respuestas, errores o advertencias que nos
arroja el terminal al ejecutar una instruccin no se antepone el
signo $ y tendrn el siguiente estilo:
1

Agent pid 32877

Paso 0 La Aplicacin
local

Para empezar, vamos a necesitar algo para implementar


Capistrano y hacer deploy. Para eso vamos a crear una aplicacin
sencilla aqu (el proceso de implementacin debe ser ms o menos

el mismo, independientemente de lo que est haciendo su


aplicacin). Mi objetivo, aqu, es explicar un mtodo muy simple
para la automatizacin de sus deployments para darle un lugar
donde empezar. Quizs esta no es la manera ms rpida o la
manera ms elegante, pero va a hacer su proceso coherente, y sin
duda ser mucho ms rpido que hacerlo manualmente. Mi
pensamiento es que si funciona, al menos ustedes pueden darse el
tiempo para aprender las tcnicas ms avanzadas.

Empecemos:

1. Crear una aplicacin de tareas (usando Postgres porque eso


es lo que he instalado en mi servidor):
Vamos a crear una pequea aplicacin de tareas con Rails y
vamos a hacer un scaold y vamos a revisar que funcione!
1
2
3
4
5
6

$
$
$
$
$
$

rails new todoapp -d postgresql


cd todoapp
bundle install
rails g scaffold todo name:string finished:boolean
rake db:migrate
rails s

Listo tenemos nuestra aplicacin creada, no hace mucho, pero


nos servir para lo que necesitamos.
De ahora en adelante trabajaremos en la carpeta de
nuestra app en la terminal
2. Iniciar GIT y el repo en Github
Antes de instalar capistrano, es muy importante que nuestra
aplicacin este en un sistema de control de versiones. Para eso
vamos a "gittear" nuestra app.
Recuerden que es muy importante , antes de hacer cualquier
commit, crear el archivo .gitignore y aadir los archivos con
informacin

sensible

config/database.yml ,

este.

Por

ejemplo

config/secrets.yml

el
y

.env en caso de estar usando la gema dotenv-rails .


1
2

$ git init
$ git add --all

el

$ git commit -m 'Primer Commit'

Ahora que nuestra app esta "gitteada", tienen que ir a su cuenta


en Github o en bitbucket y crear un repositorio nuevo en donde
pushearemos nuestra app, yo los espero aqu mientras tanto
Ok ahora que tenemos nuestro repo creado vamos a
congurar nuestra app para linkearla con el. En los ejemplos
usare github!
1

$ git remote add origin git@github.com:username/your-repo-name.git

Una vez linkeado, pushearemos la app para que este disponible


en nuestro repo.
1

$ git push -u origin master

Listo, podemos seguir con capistrano!!!

Paso 1 Aadir Capistrano a nuestra app.


Entonces, qu es Capistrano? Capistrano es una aplicacin opensource escrita en Ruby para automatizar tareas en uno o varios
servidores remotos via SSH e incluye un conjunto de ujos de
trabajo de implementacin predeterminados.

La instalacin de Capistrano es tan fcil como aadir la gema al


Gemle de nuestra aplicacin y ejecutar bundle install. Nosotros
no necesitamos Capistrano en el servidor de produccin, por lo
que la aadimos bajo el grupo de "desarrollo" del Gemle. Como
referencia, estoy usando la ltima versin 3.4.0

1. Abrimos nuestra app en nuestro editor de texto favorito


(para mi ese es Sublime Text 3.) y vamos a editar nuestro
archivo gemle y aadimos lo siguiente:

1
2
3
4
5
6
7
8

group :development do
gem 'capistrano'
gem 'capistrano-bundler'
gem 'capistrano-rails'
gem 'capistrano-rvm'
gem 'capistrano-passenger'
gem 'capistrano-ssh-doctor'
end

Oye, pero ahi aadimos mas de una gema!!!


Tranquilos, ahora voy a explicar que es cada una de ellas.
gem 'capistrano' : es la
que nos permitir instalar
capistrano y ejecutar sus
tareas.
gem 'capistrano-bundler' :
aade la tarea
bundler:install a
Capistrano, y se ejecuta
automticamente en el
servidor remoto como parte de
las tareas que se realizan
cuando hacemos un deploy con
capistrano.
gem 'capistrano-rails'
: aade 2 tareas especicas a
Capistrano,
deploy:migrate y
deploy:compile_assets , y
se ejecutan automticamente
en el servidor remoto como
parte de las tareas que se
realizan cuando hacemos un
deploy con capistrano.
gem 'capistrano-rvm' :
Asegura que todas las tareas
usen la versin de Ruby

correcta y le dice a capistrano


que use rvm ... do ...
para correr rake, bundle, gem y
ruby.
gem 'capistrano-passenger' :
Aade la tarea
passenger:restart , y
reiniciar el servidor passenger
despus de hacer un deploy
con capistrano.
gem 'capistrano-ssh-doctor' :
Aade la tarea ssh:doctor ,
para vericar si las conexiones
mediante ssh estn correctas y
si no ayudarnos a resolverlas.
2. Ahora el respectivo bundle
Recuerden que estamos trabajando en el directorio de nuestro
proyecto
1

$ bundle install

3. Y ahora instalamos Capistrano (o "capify" nuestro proyecto)


1

$ cap install

Esto va a crear los siguientes archivos:


1
2
3
4
5
6
7
8
9

"##
"##
$
$
$
$
%##

Capfile
config
"## deploy
$
"## production.rb
$
%## staging.rb
%## deploy.rb
lib
%## capistrano
%## tasks

En nuestro caso solo queremos tener un solo stage, el de

produccin, para eso lo instalamos as:


~~~bash $ cap install STAGES=production ~~~
Esto va a crear los siguientes archivos:
1
2
3
4
5
6
7
8

"##
"##
$
$
$
%##

Capfile
config
"## deploy
$
%## production.rb
%## deploy.rb
lib
%## capistrano
%## tasks

Para que tener distintos stages? Sirven por si queremos tener


conguraciones especicas para diferentes deployments de
nuestro proyecto.

Listo, nuestro proyecto esta "capify". En la siguiente seccin


prepararemos nuestro proyecto!

Paso 2 Preparacin de nuestro proyecto


En esta seccin vamos a revisar cada archivo que la instalacin de
capistrano creo, vamos a entender que hacen y los vamos a editar
con nuestras preferencias.

1. Caple
El archivo

Capfile

que se encuentra en la raz de nuestro

proyecto es la primera capa de conguracin de capistrano, es


decir, contiene la instrucciones iniciales que le dirn a
Capistrano que plugins incluir y que tareas tendremos
disponibles para usar. El archivo recin creado se ve as:
1
2
3

# Load DSL and set up stages


require 'capistrano/setup'

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Include default deployment tasks


require 'capistrano/deploy'
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

Include tasks from other gems included in your Gemfile


For documentation on these, see for example:
https://github.com/capistrano/rvm
https://github.com/capistrano/rbenv
https://github.com/capistrano/chruby
https://github.com/capistrano/bundler
https://github.com/capistrano/rails
https://github.com/capistrano/passenger
require
require
require
require
require
require
require

'capistrano/rvm'
'capistrano/rbenv'
'capistrano/chruby'
'capistrano/bundler'
'capistrano/rails/assets'
'capistrano/rails/migrations'
'capistrano/passenger'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined


Dir.glob('lib/capistrano/tasks/*.rake').each { |

Como vemos, tenemos varias lneas comentadas por lo que


vamos a editar el archivo e incluir los plugins que necesitamos
en nuestro proyecto, estos estn relacionados con las gemas
que agregamos al gemle y limpiar lo que no necesitamos.

El archivo tiene que quedar asi:

1
2
3
4
5
6
7
8
9
10
11
12

# Load DSL and set up stages


require 'capistrano/setup'
# Include default deployment tasks
require 'capistrano/deploy'
# Include tasks from other gems included in your Gemfile
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails'
require 'capistrano/passenger'
require 'capistrano/ssh_doctor'

13
14
15

# Load custom tasks from `lib/capistrano/tasks` if you have any defined


Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

Ahora tenemos todas las tareas extras, de las gemas de capistrano que agregamos, disponi

1. deploy.rb
El

archivo

deploy.rb

config/deploy.rb

que

se

encuentra

en

de nuestro proyecto (no confundir con

la carpeta deploy que tambin esta dentro de cong), es donde


conguraremos las variables globales de Capistrano; globales
porque afectan a todos los stages que tengamos creados. Al
igual que el archivo Caple, este viene con contenido por
defecto y lo vamos a remplazar por lo siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# config valid only for current version of Capistrano


lock '3.4.0'
set :rvm_type, :user
set :rvm_ruby_version, '2.3.1'
set
set
set
set
set

:application, 'YourApplicationName'
:deploy_to, "/home/username/#{fetch(:application
:scm, :git
:repo_url, 'git@github.com:your-username/your-repository-name.git'
:branch, 'master'

set :linked_files, %w{config/database.yml config/secrets.yml .env}


set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle
set :keep_releases, 4

Ahora les voy a explicar que es lo que estamos haciendo en este


archivo:
lock : seteamos la versin de
capistrano que estamos
usando.
set :rvm_type : le decimos
cual es el path de rvm a usar.

En nuestro caso el el path


desde el user.
set :rvm_ruby_version :
seteamos que versin de Ruby
queremos usar de las que
tenemos instaladas con rvm en
el servidor.
set :application : el
nombre de nuestra aplicacin.
set :deploy_to : la ruta en
el servidor donde se har el
deploy. Remplazar username
por "deploy" para digitalocean
o linode y "Ubuntu" para
amazon.
set :scm : que sistema de
control de versiones estamos
usando, en este caso git.
set :repo_url : la url de
nuestro repositorio donde
tenemos pusheada la app.
set :branch : el branch que
queremos que se use para
hacer el deploy. En este caso
master.
set :linked_files :
listado de archivos que
necesitamos que sean
persistentes entre cada deploy
mediante links simblicos, aqu
aadimos los archivos
config/database.yml ,
config/secrets.yml y
.env . Estos archivos
TIENEN que estar en el
.gitignore ya que NO los

debemos subir a nuestro repo


porque que contienen
informacin sensible.
set :linked_dirs : listado
de directorios que necesitamos
que sean persistentes entre
cada deploy mediante links
simblicos, por ejemplo: para
no perder los archivos que se
han subido a nuestra app
cuando hacemos un nuevo
deploy aadimos
public/uploads .
keep_releases : le decimos
a capistrano que solo
mantenga los ltimos X deploy
y borre todo lo demas. En este
caso 4.
Existen ms variables que se pueden congurar, pero estas son
las que, en la mayora de los casos, vamos a necesitar cambiar.
Para conocer que otras variables y profundizar en el tema les
recomiendo

que

lean

http://capistranorb.com/documentation/getting-started/configuration/
2. production.rb
Para las conguraciones que son especicas de cada stage,
editamos cada uno de los archivos que tengamos en
config/deploy/ . En este caso solo tenemos el stage
production,

que

se

encuentra

en

config/deploy/production.rb . Al igual que antes el


archivo creado viene con contenido por defecto y lo vamos a
remplazar por lo siguiente:
1
2
3
4

set :stage, :production


set :rails_env, :production
server '111.11.111.11', user: 'username', roles:

Aqui va la explicacin:
:stage : le damos el nombre a nuestro stage, en este caso
produccin, que usaremos al hacer deploy.

:rails_env : le

decimos a rail que corra en el ambiente que necesitamos, en


este caso produccin.
server... : En esta lnea le decimos a Capistrano como tiene
que acceder a nuestro vps. Le damos el ip de la mquina y el
usuario con el cual conectarse, "deploy" en DigitalOcean o
Linode y "Ubuntu" en Amazon. La variable :roles le dice a
Capistrano que el server de PostgreSQL ( db ), el server de
Nginx ( web ) y el server de Passenger ( app ) corren el la
misma mquina. La opcin

primary:

true

le dice a

Capistrano que este es nuestro server de base de datos


primario y correr todas las migraciones en este.

Estamos casi listos para hacer deploy, pero antes vamos a aadir
unas tareas personalizadas a capistrano.

Paso 3 Tareas personalizadas


Por si no lo saben, Capistrano hace mucho de su trabajo con la
ayuda de tareas. Por ejemplo, cuando hicimos cap install lo
que hicimos fue invocar una tarea llamada "install" que crea los
archivos y carpetas que hemos estado editando.

Ahora nosotros vamos a crear nuestras propias tareas, para esto


crearemos

archivos

.rake

en

la

siguiente

carpeta

lib/capistrano/tasks para cada una de ellas. La primera nos


ayudara a setear algunos archivos en el server, la segunda sera
para limpiar nuestros assets en el server y la tercera para
comprobar que nuestro repo esta al da antes de hacer deploy.

1. setup.rake

Para el primer grupo de tareas crearemos el archivo


setup.rake en lib/capistrano/tasks y escribimos lo
siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

namespace :setup do
desc "Upload database.yml file."
task :database do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
upload! StringIO.new(File.read("config/database.yml"
end
end
desc "Upload secrests.yml file."
task :secrets do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
upload! StringIO.new(File.read("config/secrets.yml"
end
end
desc "Upload .env file."
task :env do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
upload! StringIO.new(File.read(".env")), "
end
end
desc "Seed the database."
task :seed do
on roles(:app) do
within "#{current_path}" do
with rails_env: :production do
execute :rake, "db:seed"
end
end
end
end
end

Aqu hemos creado cuatro tareas bajo un namespace llamado


:setup .
Recuerdan

que

los

archivos

config/database.yml ,

config/secrets.yml y .env estn en el .gitignore ?


Bueno, esto nos va a traer problemas y errores ya que no
estarn disponibles al hacer deploy, por eso las tres primeras
tareas se encargaran de subir estos archivos directamente al
servidor sin pasar por nuestro repo.
La primera tarea es
:database . Esta se encarga
de subir el archivo
config/database.yml al
servidor.
La segunda tarea es
:secrests . Esta se encarga
de subir el archivo
config/secrets.yml al
servidor.
La tercera tarea es :env .
Esta se encarga de subir el
archivo /.env al servidor.
En el caso de la tarea

:env , solo lo usaremos si estamos

trabajando con la gema dotenv-rails , y si es as, esta gema


tiene que estar disponible en el ambiente de produccin, sea,
sacarla del grupo development de nuestro gemle.
La cuarta tares es

:seed . Esta se encarga de ejecutar

rake db:seed en el servidor si lo necesitamos.


2. assets.rake
Ahora

crearemos

el

archivo

assets.rake

en

lib/capistrano/tasks y escribimos lo siguiente:


1
2
3
4
5
6
7
8
9
10
11

namespace :clean do
desc 'Runs rake assets:clobber on server to remove compiled assets'
task :assets do
on roles(:app) do
within "#{current_path}" do
with rails_env: :production do
execute :rake, 'assets:clobber'
execute :touch, release_path.join('tmp/restart.txt'
end
end
end

12
13

end
end

La tarea que hemos creado se llama :assets y esta bajo el


namespace :clean , esta tarea lo que hace es eliminar todos
los assets en el servidor en caso de que estos nos estn
causando problemas.
3. deploy.rake
Ahora

crearemos

el

archivo

deploy.rake

en

lib/capistrano/tasks y escribimos lo siguiente:


1
2
3
4
5
6
7
8
9
10
11
12
13
14

namespace :deploy do
desc "Makes sure local git is in sync with remote."
task :check_revision do
unless `git rev-parse HEAD` == `git rev-parse origin
puts "WARNING: HEAD is not the same as origin/master"
puts "Run `git push` to sync changes."
exit
end
end
before :deploy, "deploy:check_revision"
end

Esta tarea se encargara de revisar si nuestro repo esta al da con


los cambios locales antes de hacer el deploy. Si no es as, se
cancelara el deploy y nos dar una advertencia.

Estamos listos con las tareas personalizadas!

Paso 4 Conectando el servidor con el repositorio


Para este paso es necesario tener el servidor congurado y haber
seguido la gua "Rails y Nginx con Passenger en Ubuntu:

Preparando nuestro entorno de produccin"

Como ya sabemos, capistrano usa git y un repositorio para


automatizar nuestro deployment. Es por esto que es necesario que
nuestro servidor tenga acceso a este repositorio y se pueda
autenticar automticamente cuando se hace el deployment. Para
esto usaremos ssh y deploy keys

*Todos los comando que usaremos a continuacion tienen que ser


ejecutados en el servidor remoto (AWS o DO).

1. Lo primero que tenemos que hacer es revisar si existe alguna


llave ssh en nuestro servidor. Para eso entramos al servidor
desde nuestro terminal y ejecutamos:
1

$ ls -al ~/.ssh

Si en el listado tenemos alguna llave publica ssh, del tipo


id_rsa.pub o terminado en .pub , podemos usarla para la
conexin. En caso contrario crearemos una nueva llave.
2. Creacin de una llave ssh en nuestro servidor
1

$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

Es recomendable usar las opciones por defecto como estan, por


lo que cuando nos pregunte "en que archivo queremos guardar
la llave" simplemente de damos Enter
1

Enter file in which to save the key (/Users/you/.ssh/id_rsa

Se nos pedir crear una password


1
2

Enter passphrase (empty for no passphrase): [Type a passphrase


Enter same passphrase again: [Type passphrase again

Finalmente se nos mostrar la huella o id de nuestra llave ssh.

Sera algo parecido a esto:


1
2
3
4

Your identification has been saved in /Users/you/.ssh/id_rsa.


Your public key has been saved in /Users/you/.ssh/id_rsa.pub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com

3. Copiar la llave publica de nuestro servidor


Para copiar la llave en Ubuntu tendremos que installar xclip
1

$ sudo apt-get install xclip

Una vez instalado ejecutamos:


1

$ xclip -sel clip < ~/.ssh/id_rsa.pub

4. Pegar la llave publica en nuestro repositorio como una


deploy key
Una

deploy key

es una llave ssh que se guardara en el

repositorio de nuestro proyecto (github o bitbucket) y permitir


que capistrano se pueda autenticar en el. Esta llave solo esta
vinculada con el repositorio y no con nuestra cuenta. Lo
primero que tenemos que hacer es entrar a nuestra cuenta de
Github o Bitbucket e ir al repositorio del proyecto al cual le
vamos hacer deployment

Aadir la llave en Github

Aadir la llave en Bitbucket

Una vez aadidas las llaves a nuestro repositorio estamos listos


para continuar.

Paso 5 Deploy! Cmo se hace? y qu hace?


Ok, tenemos todo listo para seguir, Capistrano instalado y
congurado y tareas personalizadas creadas. Solo nos falta hacer
el push a nuestro repo con todos los cambios que hemos echo
hasta ahora.

1
2
3

$ git add --all


$ git commit -m 'Add Capistrano and custom task'
$ git push origin master

Listo, ahora si, como hacemos el deploy. Hacer un deploy es tan


fcil como escribir esto en la consola en el root de nuestro
proyecto:

$ cap production deploy

La instruccin anterior esta compuesta de 3 partes,


referencia a Capistrano,
usar, y

deploy

production

cap

hace

es el stage que vamos a

es la tarea a realizar en el stage. Podemos

ejecutar cualquier tarea que tengamos disponible. Para ver cuales


son (incluidas las nuestras) ejecutamos lo siguiente:

$ cap -T

Esto nos devolver un listado de todas las tareas que podemos


usar y su descripcin; de toda esa lista las tareas que ms
usaremos y las que nos interesan son:

1
2
3
4
5
6
7

cap
cap
cap
cap
cap
cap
cap

deploy
deploy:check
setup:database
setup:secrets
setup:env
setup:seed
ssh:doctor

#
#
#
#
#
#
#

Deploy a new release


Check required files and directories exist
Upload database.yml file
Upload secrests.yml file
Upload .env file
Seed the database
Perform ssh doctor

Si ya ejecutaron cap production deploy lo mas probable es


que les arrojara un error y no se completara la tarea. No nos
preocuparemos de eso por ahora. Primero vamos a entender que
pasa cuando ejecutamos cap production deploy .

Capistrano utiliza una jerarqua de directorios estrictamente


denido en cada servidor remoto para organizar el cdigo fuente y
otros datos relacionados con el deployment. La ruta raz de esta
estructura es la denida en la variable de conguracin:
:deploy_to

que

modicamos

en

el

archivo

config/deploy.rb

Si revisamos la ruta raz e inspeccionamos los directorios


veremos algo como esto:

1
2
3
4
5
6
7
8
9
10
11
12

"##
"##
$
$
$
$
$
"##
$
"##
%##

current -> /var/www/my_app_name/releases/20150120114500/


releases
"## 20150080072500
"## 20150090083000
"## 20150100093500
"## 20150110104000
%## 20150120114500
repo
%## <VCS related data>
revisions.log
shared
%## <linked_files and linked_dirs>

/releases : cada vez que se hace un deploy un nuevo


directorio se creara aqu, y contiene todo el cdigo de ese
deploy.
/current : es un enlace simblico que apunta al ltimo
directorio creado en /releases .
/shared : mantiene los archivos y directorios que son
persistentes a lo largo de los deploy.
/repo : contiene un clon de su .git.

Dentro de la carpeta /shared , encontraremos:

1
2
3

%## shared
"## .env
"## config

4
5
6
7
8
9

"##
"##
"##
"##
"##
%##

public
log
tmp
bin
bundle
vendor

Los que nos interesan son:

.env : el archivo que contendr nuestras variables privadas.


/config : tendr nuestro database.yml y secret.yml .
/log : contiene el production.log . Este tendr todo el
historial de nuestra app, no solo del ltimo deploy.
/public : contiene todos los assets y tambin la carpeta
upload en nuestro caso.

Cuando corremos

cap production deploy

lo que estamos

haciendo es llamar una tarea de Capistrano llamada deploy, quede


manera secuencial invocara otras tareas. Las principales son:

starting : crea la estructura de directorios y comprueba que


puede obtener el repo de github.
updating : copia el repo de github a un nuevo directorio en
/releases , y aade los links simblicos que apuntan a
/shared , corre Bundler, las migraciones y compila los assets.
publishing : crea el links simblico entre /current y el
nuevo directorio en /releases . Solo si no hubo errores en
alguna de las tareas anteriores.
finishing : elimina los directorios mas antiguos de
/releases .

En caso de que Capistrano se encuentre con un error en el


momento de hacer deploy y no termine la tarea completa,
/current

siempre

apuntar

al

ltimo

directorio

de

/releases

que estaba funcionando, de esta manera el sitio

siempre estar disponible.

Ahora que sabemos cmo y qu hace Capistrano haremos un


deploy de nuestra app

1. Al momento de escribir esta gua no era compatible con Ubuntu 14.10