Introduccin a
Ruby on Rails
desde cero
[Link]
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 [Link]
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 [Link]
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
[Link]
Muestra todas las dependencias, generado a partir del Gemle
[Link]
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
[Link]
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/[Link]
test_unit
test/controllers/pages_controller_test.rb
helper
app/helpers/pages_helper.rb
test_unit
assets
coffee
app/assets/javascripts/[Link]
scss
app/assets/stylesheets/[Link]
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/[Link]
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
[Link]
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/[Link]
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 .[Link]
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
[Link],
el
encargado
de
manejar
todos
los
enrutamientos.
Dentro del archivo
config/[Link]
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/[Link]
dentro
de l agregaremos get y la ruta a nuestra nueva pgina (about) Se
debera ver as:
1
2
3
4
5
[Link] 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/[Link] ) e
intentamos entrar a la pgina obtendramos este error:
Cada vez que tengamos un error de rutas, sabemos que el archivo
culpable es [Link].
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/[Link] .
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/[Link] 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="[Link]">
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/[Link]">
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 '[Link]' %>">
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 '[Link]' %>
image_tag adems puede recibir otros parmetros como el id o la
clase.
<%= image_tag '[Link]', 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 .[Link] (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 [Link]
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/[Link] 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 [Link] 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 [Link]
en el caso de que hubiese.
Para probarlo vamos a denir un CSS sencillo, al que vamos a
llamar
[Link]
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/[Link]
defecto
es
y funciona de la
misma forma que [Link]. 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 [Link] el cual pone una
imagen como background de fondo, entonces debemos nombrarlo
[Link] y luego dentro de la url([Link]) cambiarlo por:
url('<%= asset_path "[Link]" %>')
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/[Link]
[Link] << [Link]('mis_assets', 'assets'
Tambin para hacer ms fcil las referencias puedes agregar
subcarpetas
1
2
[Link] << [Link]('mis_assets', 'assets'
[Link] << [Link]('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:
[Link]
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/[Link]
modicar
el
archivo
y dentro de la clase
vamos a agregar lo siguiente:
1
2
[Link] << [Link]('app', 'assets', 'fonts'
[Link] += %w( *.svg *.eot *.woff)
Finalmente cuando se modica el archivo [Link] 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/[Link]
1
2
3
4
5
6
7
8
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="[Link]
<!-- Optional theme -->
<link rel="stylesheet" href="[Link]
<!-- Latest compiled and minified JavaScript -->
<script src="[Link]
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="[Link]
<link rel="stylesheet" href="[Link]
<%= 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="[Link]
</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:
[Link]
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
[Link]
[Link]
van
en
vendor/assets/stylesheets , luego copiamos el archivo
[Link] en vendor/assets/javascript .
El ltimo paso que nos queda es mencionar dentro del
[Link] los archivos de Bootstrap, puesto como habamos
explicado
previamente
la
lnea
require_tree
app/assets/stylesheets/[Link]
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/[Link]
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/[Link]
[Link] << [Link]('vendor', 'assets'
En el mismo archivo hay que agregar los nuevos formatos al
proceso de precompilacin
1
2
[Link] << [Link]('vendor', 'assets'
[Link] += %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/[Link]');
src: url('../fonts/[Link]?#iefix') format('embedded-opentype
url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),
url('../fonts/[Link]') 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 "[Link]" %>');
src: url('<%= asset_path "[Link]?#iefix" %>') format
url('<%= asset_path "glyphicons-halflings-regular.woff2" %>') format('woff2'
url('<%= asset_path "[Link]" %>') format('woff'
url('<%= asset_path "[Link]" %>') format('truetype'
url('<%= asset_path "[Link]#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 .[Link] 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.[Link]
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 [Link]
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:
[Link]
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/[Link]
test_unit
test/models/user_test.rb
test/fixtures/[Link]
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 = [Link]
O lo podemos instanciar directamente con un email:
1
2
user = [Link](email: 'gonzalo@[Link]')
=> #<User id: nil, email: "gonzalo@[Link]", created_at: nil, updated_at: nil>
Tambin podemos cambiar el email del usuario simplemente con:
[Link] = "nuevoemail@[Link]"
Estos datos no persisten en la base de datos hasta que los
guardemos, eso lo hacemos con:
[Link]
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
[Link](email: "creando_usuario_directamente@[Link]")
Leyendo los usuarios desde la base de datos
Para ver todos los usuarios creados llamamos:
[Link]
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@[Link]", created_a
Como los resultados son una array podemos iterarlo con un .each
de tal manera que:
[Link] {|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@[Link]", 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?
[Link](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%[Link]"
for ::1 at 2015-11-20 14:45:46 -0600
Processing by LandingsController#index as HTML
Parameters: {"q"=>"gonzalo@[Link]"}
(0.5ms) begin transaction
SQL (0.7ms) INSERT INTO "users" ("email", "created_at", "updated_at") VALUES
(0.6ms) commit transaction
Rendered landings/[Link] 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%[Link]"
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/[Link]
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/[Link]
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/[Link] 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@[Link]",
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
[Link](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 = [Link](email: params[:email])
@[Link]
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 = [Link](email: params[:email])
if @[Link]
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/[Link] .
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/[Link]
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 = [Link]
render json:@leads
end
Podemos
probar
entrando
[Link]
[Link] 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.[Link]
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 = [Link]
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>
<% @[Link] do |u| %>
<li> <%= [Link] %> </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 <%= @[Link] do |u| %> y <%
@[Link] 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 '[Link]
# 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 [Link]). 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 [Link]. 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 [Link] 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 [Link]?
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 [Link] 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 [Link].
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 [Link] "YOUR NAME"
git config --global [Link] "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
[Link] | [Link]
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.
[Link]
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 '[Link]
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 [Link]
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 [Link]) 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 [Link] (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)://[Link] 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 [Link] `heroku pg:backups public-url`
Esto descargar un archivo que se llama [Link] 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 [Link]?
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
[Link]
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/.[Link].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
[Link] 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.
[Link]
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 [Link];
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 "[Link]" 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 [Link] = 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 ([Link] = 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 [Link] = movies.category_id;
O si probamos:
SELECT * FROM movies INNER JOIN categories ON ([Link] = 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 ([Link] = 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 ([Link] = 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 ([Link] = 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 [Link] = movies.category_id; ?
3. Cul es la diferencia entre SELECT * FROM movies, categories WHERE
[Link] = 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 [Link].
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 [Link]
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 [Link] 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/.[Link].5432"?
Esto normalmente se debe a que el servidor de postgreSQL no est
corriendo.
Preguntas
1. Por qu es necesario congurar el archivo [Link] 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/[Link]
invoke test_unit create test/models/task_test.rb create test/xtures/[Link] ~~~ 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 [Link] #Obtiene
todas las tareas en la base de datos [Link](1) #Busca la tarea con id 1 [Link] #Devuelve el
primer task de la base de datos [Link] #Devuelve el ltimo task de la base de datos
[Link](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 = [Link](user:
Gonzalo) [Link] ~~~ O podemos crearlas directamente utilizando el mtodo create ~~~bash
[Link](user: Gonzalo) ~~~ Para actualizar un dato de la base de datos tenemos que traerlo
a memoria primero, luego podemos modicarlo y guardarlo. ~~~bash t = [Link] [Link] =
R2D2 [Link] ~~~ Hay mtodos que traen a memoria un objeto, en ese caso ese objeto ser de
ese tipo, como por ejemplo [Link] 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
[Link] {|t| puts [Link]} ~~~
Se debe tener cuidado de no confundir los mtodos de
instancia con los de clase, por ejemplo no tiene sentido
obtener
`[Link]`
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/[Link]
test_unit
test/models/comment_test.rb
test/fixtures/[Link]
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 [Link]
El
secreto
para
entender
las
migraciones
db/[Link] , el cual a esta altura luce as:
es
el
archivo
1
2
3
4
5
6
7
8
ActiveRecord::[Link](version: 20150819042819) do
create_table "tasks", force: :cascade do |t|
[Link]
"task"
[Link] "created_at", null: false
[Link] "updated_at", null: false
[Link]
"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
[Link]
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/[Link] , 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:
[Link]
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|
[Link] :users, index: true, foreign_key: true
[Link] 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 = [Link](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
[Link] y el valor de si es administrador con [Link] 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 [Link] que [Link]?
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 = [Link]([{ name: 'Chicago' }, { name: 'Copenhagen' }])
#[Link](name: 'Emanuel', city: [Link])
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:
[Link]([{name:"El rey leon"}, {name:"terminator"}])
Dentro del seed es posible agregar Ruby:
1
2
c = [Link]("Disney")
[Link](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 [Link]?
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 [Link]?
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 < [Link]
[Link](: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
[Link] = [Link]
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 = [Link](name: "Sin categora").first
[Link] = 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
[Link] 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 = [Link]
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 = [Link]
@[Link]
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 [Link] agregaremos
has_one :company
En el modelo [Link] 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 = [Link](name:"Rick Hunter")
c = [Link](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
[Link]
=> #<Company id: nil, name: "SDF-1 Macross", user_id: nil, created_at: nil, updated_at:
[Link]
#<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 = [Link](name:"Rick Hunter")
c = [Link](name:"SDF-1 Macross", user:u)
[Link]
Veremos que justo despus de hacer el destroy obtendremos:
1
2
3
4
5
6
2.2.2 :004 > [Link]
(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/[Link]
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
[Link]
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 [Link]
has_many :movies
y luego abriremos el modelo de [Link] 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 = [Link](name: "Children and Family")
m = [Link](name:"Hercules", category: c)
Al igual que en las relaciones 1 a 1 podemos hacer
[Link]
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
[Link]
#=> #<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 = [Link](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.
[Link]
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 [Link]
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 = [Link](name:"Usuario 1")
u2 = [Link](name:"Usuario 2")
u3 = [Link](name:"Usuario 3")
p1 = [Link](content:"Articulo 1 del usuario 1", user: u1)
p2 = [Link](content:"Articulo 2 del usuario 1", user: u1)
p3 = [Link](content:"Articulo 3 del usuario 1", user: u1)
[Link](content:"Comentario del usuario 1 en el articulo 1", user: u1
[Link](content:"Comentario del usuario 1 en el articulo 1", user: u1
[Link](content:"Comentario del usuario 2 en el articulo 1", user: u2
[Link](content:"Comentario del usuario 2 en el articulo 1", user: u2
[Link](content:"Comentario del usuario 3 en el articulo 1", user: u3
[Link](content:"Comentario del usuario 3 en el articulo 1", user: u3
[Link](content:"Comentario del usuario 2 en el articulo 1", user: u2
[Link](content:"Comentario del usuario 3 en el articulo 2", user: u3
Para ingresar los datos ahora debemos escribir rake db:seed
1
2
user = [Link]
[Link]
Tambin
podemos
[Link]
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 [Link] pero sera
un error, puesto que [Link] 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 = []
[Link] {|p| comments << [Link] }
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.
[Link](: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 = [Link](title: nil)
assert_not [Link]?
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
[Link]
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 [Link]?
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 [Link]?
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 @[Link]?
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 = [Link](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, [Link]
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 = [Link]()
assert_equal 1, [Link]
end
Existe otra opcin que no abordaremos en este libro, que consiste
en ocupar factories, la gema factory_girl resuelve este problema
[Link]
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)
[Link] = nil #decimos que no tiene email
assert_not [Link]?, "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
@[Link] = nil #decimos que no tiene email
assert_not @[Link]?
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 [Link]?, "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 = [Link](email: @[Link])
assert_not [Link]?, "user with email #{[Link]} 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 @[Link], 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 [Link]
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 @[Link]?
end
test "can update before 5 minutes" do
13
14
15
16
17
@post = posts(:two)
assert @[Link]?
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 = [Link] - self.created_at
if delta > [Link]
[Link](: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 [Link]? en
lugar de assert [Link]?, 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/[Link]
test_unit
test/models/task_test.rb
test/fixtures/[Link]
resource_route
resources :tasks
scaffold_controller
app/controllers/tasks_controller.rb
erb
app/views/tasks
app/views/tasks/[Link]
app/views/tasks/[Link]
app/views/tasks/[Link]
app/views/tasks/[Link]
app/views/tasks/_form.[Link]
test_unit
test/controllers/tasks_controller_test.rb
helper
app/helpers/tasks_helper.rb
test_unit
jbuilder
app/views/tasks/[Link]
app/views/tasks/[Link]
assets
coffee
app/assets/javascripts/[Link]
scss
app/assets/stylesheets/[Link]
scss
app/assets/stylesheets/[Link]
El ltimo archivo creado [Link] 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 = [Link]
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/[Link] 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.[Link] .
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 @[Link]? %>
<div id="error_explanation">
<h2><%= pluralize(@[Link], "error") %> prohibited this task from being
<ul>
<% @[Link].full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= [Link] :task %><br>
<%= f.text_field :task %>
17
18
19
20
21
</div>
<div class="actions">
<%= [Link] %>
</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 /[Link]
def create
@task = [Link](task_params)
respond_to do |format|
if @[Link]
[Link] { redirect_to @task, notice: 'Task was successfully created.'
[Link] { render :show, status: :created, location: @task }
else
[Link] { render :new }
[Link] { render json: @[Link], status: :unprocessable_entity
end
end
end
El
mtodo
create
parte
[Link](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 [Link] 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 [Link]
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
[Link](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
[Link](: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
[Link](: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.[Link] 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 @[Link]? %>
<div id="error_explanation">
<h2><%= pluralize(@[Link], "error") %> prohibited this task from being
<ul>
<% @[Link].full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= [Link] :task %><br>
<%= f.text_field :task %>
</div>
<div class="field">
<%= [Link] :user %><br>
22
23
24
25
26
27
28
29
<%= f.text_field :user %>
</div>
<div class="actions">
<%= [Link] %>
</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.
[Link]
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 [Link] 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 [Link]
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:
[Link](: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 [Link]
El archivo [Link] 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 [Link],
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 = [Link] params[:user_id]
@tweets = @[Link]
end
Y luego dentro de la vista mostramos los tweets con:
1
2
3
<% @[Link] do |t| %>
<%= [Link] %>
<% end %>
Es necesario obtener el objeto user?, no, otra opcin de hacer lo
mismo sera.
@tweets = [Link](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 = [Link](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 = [Link] params[:user_id]
@tweet = @[Link]
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 =
[Link] (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 %>
<%= [Link] :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('[Link]') 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 = [Link](tweet_params)
@[Link]
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('[Link]') 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 = [Link] params[:user_id]
@tweet = [Link](tweet_params)
@[Link] = @user
@[Link]
redirect_to @user
end
Las lneas 3 y 4 pueden ser sustituidas por:
@tweet = @[Link](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|
# [Link] [:movie_id, :tag_id]
# [Link] [: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 [Link] debera contener:
1
2
3
create_table
[Link]
[Link]
[Link]
"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|
[Link] "movie_id", null: false
[Link] "tag_id",
null: false
end
create_table
[Link]
[Link]
[Link]
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
[Link]
nos debera devolver un coleccin vaca, y
[Link] tambin.
Agregando elementos
Hagamos otra prueba para explicar como agregar gneros a las
pelculas.
1
2
3
4
m = [Link](name:"la pelicula de los muchas a muchos")
g = [Link](name:"Accin")
[Link] << g
[Link]
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 = [Link](name:"Terminator")
g = [Link](name:"Accin")
[Link] << g
[Link]!
m = [Link](name:"Terminator 2")
g = [Link](name:"Accin")
[Link] << g
[Link]!
m = [Link](name:"Buscando a Nemo")
g = [Link](name:"Animacin")
[Link] << g
[Link]!
18
19
20
21
22
23
24
m = [Link](name:"Cars")
g = [Link](name:"Animacin")
[Link] << g
g = [Link](name:"Accin")
[Link] << g
[Link]!
Borrando la asociacin:
Cmo podemos borrar la asociacin?
Para borrar debemos tener el objeto que queremos borrar, por
ejemplo:
1
2
3
tag = [Link]
movie = [Link]
[Link](tag)
Se debe tener el cuidado de no hacer [Link][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
[Link] do |i|
@[Link] << [Link](name: "Genero #{i}")
end
5
6
assert @[Link]
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
[Link].movie_tags #
[Link]
Desde Tag:
1
2
[Link].movie_tags #
[Link]
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 = [Link](name:"Avengers")
tag = [Link](name:"Action")
3
4
[Link] << tag
[Link]
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'
[Link] do |t|
[Link](name: t)
end
(1..50).each do |m|
random_tag1 = [Link]
random_tag2 = [Link]
[Link](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 = [Link] params[:id]
@tags = [Link]
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>
<% @[Link] do |tag| %>
<tr>
<td> <%= [Link] %></td>
<td><%= f.check_box 'tag_ids', {multiple: true}, [Link], 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 = [Link](title:"Aprendiendo a programar", content:"Lorem Ipsum ...")
[Link] << [Link](tag:"Programacin")
[Link]
p = [Link](title:"Introduccin a Ruby on Rails", content:"Lorem, Ruby, Rails"
[Link] << [Link](tag:"Programacin")
[Link] << [Link](tag:"Ruby")
[Link] << [Link](tag:"Rails")
[Link]
p = [Link](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
[Link]
[Link]
[Link]
[Link]
[Link]
<<
<<
<<
<<
[Link](tag:"Programacin")
[Link](tag:"Ruby")
[Link](tag:"Rails")
[Link](tag:"MVC")
p = [Link](title:"Aprendiendo Rails con desafiolatam", content:"Bootcamps lorem ipsum"
[Link] << [Link](tag:"Bootcamps")
[Link] << [Link](tag:"Aprender")
[Link]
p = [Link](title:"Lgica para la programacin", content:"Logica, tablas de lorem ipsum
[Link] << [Link](tag:"Lgica")
[Link] << [Link](tag:"Programacin")
[Link]
p = [Link](title:"Escalando Rails", content:"Rails, tablas de lorem ipsum"
[Link] << [Link](tag:"Optimizacin")
[Link] << [Link](tag:"Rails")
[Link]
Luego
descargamos
el
plugin
de
jQCloude
desde
[Link] y copiamos los archivos de
[Link]
[Link]
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 = [Link](: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) %>
[Link](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/[Link]:
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/[Link]. For example:
root to: "home#index"
3. Ensure
you
have
ash
messages
in
app/views/layouts/[Link]. For example:
<%= notice %>
<%= alert %>
4. If you are deploying on Heroku with Rails 3.2 only, you may want
to set:
[Link].initialize_on_precompile = false
On cong/[Link] 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.[Link]
form_for
app/views/devise/confirmations
app/views/devise/confirmations/[Link]
app/views/devise/passwords
app/views/devise/passwords/[Link]
app/views/devise/passwords/[Link]
app/views/devise/registrations
app/views/devise/registrations/[Link]
app/views/devise/registrations/[Link]
app/views/devise/sessions
app/views/devise/sessions/[Link]
app/views/devise/unlocks
app/views/devise/unlocks/[Link]
erb
app/views/devise/mailer
app/views/devise/mailer/confirmation_instructions.[Link]
app/views/devise/mailer/reset_password_instructions.[Link]
app/views/devise/mailer/unlock_instructions.[Link]
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/[Link]
test_unit
test/models/user_test.rb
test/fixtures/[Link]
app/models/[Link]
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:
[Link]
[Link]
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">
<%= [Link] :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@[Link]
<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@[Link]"],
["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
[Link]
sin estar logeado
ser redirigido a la login.
Antes de probarlo no te olvides de agregar las pginas al archivo
[Link]
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/[Link]
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 => "[Link]",
:port => 587,
:authentication => :plain,
:domain => '[Link]',
: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@[Link]
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@[Link]
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
[Link]!
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 [Link] aadimos lo siguiente:
1
2
3
4
5
6
7
...
before_save :default_role
def default_role
[Link] ||= 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/[Link]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
admin:
id: 1
email: "admin@[Link]"
role: 0
editor:
id: 2
email: "editor@[Link]"
role: 1
user:
id: 3
email: "user@[Link]"
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@[Link]"
role: 0
editor:
id: 2
email: "editor@[Link]"
role: 1
user:
id: 3
email: "user@[Link]"
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 [Link].
For example:
[Link] 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/[Link]
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 => "[Link]",
:port => 587,
:authentication => :plain,
:domain => '[Link]',
: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@[Link]
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@[Link]
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: [Link]
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/[Link]
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/[Link] .
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 ||= [Link] # En caso de que el usuario no haya ingresado.
if [Link]?
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 [Link]?
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 [Link]
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
[Link] 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
[Link] y [Link](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: [Link]
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 = [Link]
ability = [Link](user)
[Link]?(:create, Movie)
[Link]?(:edit, Movie)
[Link]?(: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: [Link]
Testear este tipo de habilidades requiere especicar de quien es el
objeto.
[Link]?(:destroy, [Link](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.
[Link]
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 = [Link]
[Link]("nombre_archivo") do |f|
[Link] = f
end
[Link]
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 [Link]?
[Link] do |config|
[Link] = :file
config.enable_processing = false
end
else
[Link] do |config|
[Link] = :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
=> '[Link]'
#:host
=> '[Link]',
# optional, defaults to
:endpoint
=> '[Link] # optional, defaults to nil
}
config.aws_bucket
= ENV['aws_dir']
# required
config.aws_acl
= 'public-read'
config.aws_attributes = {
expires: [Link].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 [Link] 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 [Link] do {|user| [Link]} estaramos
mostrando todos los nombres de los usuarios, los cuales son
trados a memoria a travs de [Link] , esto lo podemos ver
en rails c que despus de escribir [Link] 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
[Link] do |user|
puts [Link]
puts [Link]
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 [Link] 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
[Link](: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
[Link](:group).each do |user|
puts [Link]
puts [Link]
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
[Link] do |group|
puts [Link]
puts [Link]
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
[Link](:users).each do |group|
puts [Link]
[Link]
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
[Link](: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
[Link]("left join users on users.group_id = [Link]").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/[Link]
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
[Link] = true
[Link] = true
Bullet.bullet_logger = true
[Link] = true
[Link] = true
[Link] = { :account => 'bullets_account@[Link]',
:password => 'bullets_password_for_jabber',
:receiver => 'your_account@[Link]',
:show_online_status => true }
Bullet.rails_logger = true
[Link] = true
[Link] = true
[Link] = true
[Link] = true
16
17
18
19
20
Bullet.add_footer = true
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
[Link] = { webhook_url: '[Link] 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/[Link]
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 ( $('.[Link]').length > 0 ) {
someBehavior();
}
})
Aqu
lo
importante
es
el
if ( $('.[Link]').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
[Link]()
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
[Link] , 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 [Link]
//= 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/[Link] 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 [Link] 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
[Link]
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 [Link]
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.
[Link] {|i| [Link](content: "Post #{i}")}
y luego agregamos los seeds
rake db:migrate
Al cargar la pgina [Link] 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 [Link]
1
2
3
4
5
6
7
8
def destroy
@[Link]
respond_to do |format|
[Link] { redirect_to posts_url, notice: 'Post was successfully destroyed.'
[Link] { head :no_content }
[Link]
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-<%=[Link]%>">
y luego en el archivo [Link] ocultaremos ese dato, eso lo
logramos con un sencillo selector de jQuery:
$("#post-<%=@[Link]%>").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 = [Link]
Post#index quedara as:
1
2
3
4
def index
@post = [Link]
@posts = [Link]
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 = [Link](post_params)
respond_to do |format|
if @[Link]
[Link] { redirect_to @post, notice: 'Post was successfully created.'
[Link] { render :show, status: :created, location: @post }
[Link]
else
[Link] { render :new }
[Link] { render json: @[Link], status: :unprocessable_entity
[Link]
end
end
end
1. Creamos el archivo [Link]
$("tbody").append("<tr> <td> <%= @[Link] %> </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-<%=@[Link]%>">\
<td><%= @[Link] %></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.[Link] dentro de la carpeta
posts. del index sacamos este cdigo y lo ponemos dentro del
nuevo archivo _post.[Link]
1
2
3
4
5
6
<tr id="post-<%=[Link]%>">
<td><%= [Link] %></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
<% @[Link] 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
[Link]
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 [Link]?
6. Qu sucede si existe el bloque respond_to con [Link] 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.[Link]
1
2
3
4
5
<%= div_for(post) do %>
<h2><%= [Link] %></h2>
<h2><%= [Link] %></h2>
<p><%= [Link] %></p>
<% end %>
Luego el archivo posts/[Link] 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 = [Link](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 [Link]
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/[Link]
1
2
3
[Link] do |x|
[Link](:title => Faker::[Link], :content => Faker::[Link]
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
[Link] 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 [Link] 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 [Link]
(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 = [Link](user_id: current_user.id)
unless params[:company_filter].blank?
@donations = @donations.eager_load(:donor).where("[Link] = ?", params
end
@donations_by_company = @donations.eager_load(:donor).group("[Link]"
@donation_by_day = @donations.group_by_day("donations.created_at" ).sum(:amount
Generando los grficos
1
2
3
4
5
<%= javascript_include_tag "//[Link]/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 > [Link](title:nil, start:[Link])
(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 [Link] agregaremos:
1
2
3
4
5
Event.destroy_all
[Link]!(title:"Evento
[Link]!(title:"Evento
[Link]!(title:"Evento
[Link]!(title:"Evento
de
de
de
de
ayer", start: [Link] - [Link])
hoy", start: [Link])
maana", start: [Link] + [Link])
la semana", start: [Link] - [Link], 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
[Link] .
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 [Link]
1
2
3
4
5
6
7
8
<link href="//[Link]/ajax/libs/fullcalendar/2.9.0/[Link]
rel="stylesheet">
<link href="//[Link]/ajax/libs/fullcalendar/2.9.0/[Link]
rel="stylesheet">
<script src="//[Link]/ajax/libs/[Link]/2.14.1/[Link]
</script>
<script src="//[Link]/ajax/libs/fullcalendar/2.9.0/[Link]
</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
{"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"}
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.
[Link]
Para mejorar el estilo del calendario puedes ocupar algn tema de
Jquery UI Themes
[Link]
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/[Link]
app/views/layouts/[Link]
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@[Link]'
def welcome_email(user)
@user = user
@url = '[Link]
mail(
to: @[Link],
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 [Link], <%= @[Link] %></h1>
<p>
You have successfully signed up to [Link],
your username is: <%= @[Link] %>.<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 [Link], <%= @[Link] %>
===============================================
You have successfully signed up to [Link],
your username is: <%= @[Link] %>.
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([Link])
end
end
si
ahora
vamos
la
[Link]
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 [Link], [Link], [Link],
etc). Pueden revisar las opciones de conguracin aqui.
En
nuestro
caso
usaremos
ya que estamos
/config/environments/[Link]
trabajando en el ambiente de desarrollo, esta conguracin
tambin
se
debe
hacer
/config/environments/[Link]
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: '[Link]',
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 = [Link](params[:user])
respond_to do |format|
if @[Link]
# Tell the UserMailer to send a welcome email after save
UserMailer.welcome_email(@user).deliver_later
[Link]
[Link]
else
[Link]
[Link]
end
end
end
end
{ redirect_to(@user, notice: 'User was successfully created.'
{ render json: @user, status: :created, location: @user
{ render action: 'new' }
{ render json: @[Link], 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
[Link]\_email(@user).deliver_later unless @[Link]
end
end
1. Decirle a devise que use el controller creado anteriormente
En el archivo de rutas ( [Link] ) 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/[Link] , si queremos
hacer pruebas en el modo de desarrollo tendremos que hacerlo
tambin en /config/environments/[Link]
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 [Link]
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 [Link]
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::[Link]! Minitest::Reporters
end
La primera parte
Minitest::[Link]!
le dice a
nuestro test_helper que usaremos minitest-reporters.
La
segunda
Minitest::Reporters::[Link]
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: [Link], 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@[Link]
La primera vez que nos tratemos de conectar se nos mostrar
un mensaje como este:
1
2
3
The authenticity of host '[Link] ([Link])' 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
[Link] 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@[Link]
*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@[Link]
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@[Link]
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 [Link] | 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://[Link] --recv-keys 409B6B1796C275462A170311
or if it fails:
command curl -sSL [Link] | gpg --import -
14
15
16
17
18
the key can be compared with:
[Link]
[Link]
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://[Link]
recv-keys
409B6B1796C275462A1703113804BB82D39DC0E3
Y la pegamos en el terminal
1
$ gpg --keyserver hkp://[Link] --recv-keys 409B6B1796C275462A1703
despus volvemos a ejecutar el instalador de RVM
1
$ curl -L [Link] | 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 [Link] --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 [Link]
Para Ubuntu 15.04
1
$ sudo sh -c "echo 'deb [Link]
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/[Link].d/[Link]
$ sudo chmod 600 /etc/apt/[Link].d/[Link]
$ 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 ([Link]) 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/[Link]
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/[Link]
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
[Link] 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 [Link] dentro de nuestro proyecto.
1
$ vim ~/www/example/[Link]
Dentro del archivo escribiremos esto:
1
2
3
4
5
6
7
8
<html>
<head>
<title>Bienvenidos a [Link]</title>
</head>
<body>
<h1>El server block esta funcionando!</h1
</body>
</html>
Ahora guardamos y cerramos el archivo [Link]
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 [Link] [Link];
passenger_enabled on;
rails_env production;
root /home/deploy/www/example;
# redirect server error pages to the static page /[Link]
error_page 500 502 503 504 /[Link];
location = /[Link] {
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
[Link], y ademas
podemos aadir alias, en este
caso [Link],
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
[Link] 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/[Link]
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: [Link], 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/[Link] ,
este.
Por
ejemplo
config/[Link]
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@[Link]:username/[Link]
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
$
"## [Link]
$
%## [Link]
%## [Link]
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
$
%## [Link]
%## [Link]
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:
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
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
[Link]('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
[Link]('lib/capistrano/tasks/*.rake').each { |r| import r }
Ahora tenemos todas las tareas extras, de las gemas de capistrano que agregamos, disponi
1. [Link]
El
archivo
[Link]
config/[Link]
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@[Link]:your-username/[Link]'
:branch, 'master'
set :linked_files, %w{config/[Link] config/[Link] .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/[Link] ,
config/[Link] 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
[Link]
2. [Link]
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/[Link] . 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 '[Link]', 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. [Link]
Para el primer grupo de tareas crearemos el archivo
[Link] 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 [Link] file."
task :database do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
upload! [Link]([Link]("config/[Link]"
end
end
desc "Upload [Link] file."
task :secrets do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
upload! [Link]([Link]("config/[Link]"
end
end
desc "Upload .env file."
task :env do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
upload! [Link]([Link](".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/[Link] ,
config/[Link] 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/[Link] al
servidor.
La segunda tarea es
:secrests . Esta se encarga
de subir el archivo
config/[Link] 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. [Link]
Ahora
crearemos
el
archivo
[Link]
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/[Link]'
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. [Link]
Ahora
crearemos
el
archivo
[Link]
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@[Link]"
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@[Link]
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 [Link] file
Upload [Link] 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/[Link]
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>
[Link]
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 [Link] y [Link] .
/log : contiene el [Link] . 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