Documente Academic
Documente Profesional
Documente Cultură
583
1) Temario:
1) Temario:
2) Motivación
¿Qué es Rails?
¿Por qué utilizar ruby on rails para mi aplicación?
Deventajas de utilizar Ruby on Rails
¿Cómo está estructurado este libro?
Sección I
3) Instalando las herramientas
OSX
Linux
En Linux y OSX
Consola secuestrada.
Creando nuestra primera página
Introducción a controllers
Errores de novato
Introducción a las vistas
Cómo convertir esta página en la página principal
Creando la ruta
Creando un método en el controller
Creando la vista.
Agregando un link a una página interna
Los Assets
El asset path
Incorporando imágenes
El método asset_path
El método image_tag
Consecuencias de Sprockets
Incorporando bootstrap.
CDN
Descargando los CSS
Glyphicons
Vistas parciales
El tag form
Creando un usuario
Leyendo los usuarios desde la base de datos
9) Formulario 2.0
10) El gemfile
Sección II
11) Deployment en Heroku
Tipos de hosting
Servidor propio
Hosting clásico
VPS
PAAS
Configurando GIT
Creando el repositorio git
Desafío
Desafio
Sección III
13) SQL
PostgreSQL
Instalando PostgreSQL en OSX
Instalando PostgreSQL en Linux
Entrando a PSQL
Insertando valores
Leyendo valores de una tabla.
Actualizando valores de una tabla
Borrando datos de una tabla
Constraints
Distinct y count
Guía de ejercicios
Peliculas
CRUDS
Sorting
Conteo
Productos
Integridad referencial
Joins
Tipos de joins
Left Outer Join
Right Outer Join
Full Outer Join
Tipos de relaciones
La clave foránea
Ejercicios
Shopping
movieDB
Relaciones n a n
Exportando datos
Convención de nombres
Creando un modelo vacío
Creando un modelo con diversos campos
Migraciones
El archivo schema.rb
Creando migraciones.
Con helper
Sin helper
Relaciones 1 a 1
El método build
Múltiples relaciones de 1 a n
Relaciones n a n
Los fixtures
Estructura de un test de modelo
Corriendo tests
Cargando los fixtures en los tests
Construyendo el modelo
Construyendo los tests
Desafios
21) MVC
Arquitectura REST
Scaffold
Strong parameters
El archivo routes.rb
Rake routes
Prefix: El prefijo
Verb, el verbo
URI pattern
URI Controller#Action
Resources
member vs collection
member
Collection
22) Relaciones n a n
Borrando la asociación:
Testeando la relación
25) Devise
Agregando el campo
Enums al rescate
28) Polimorfismo
29) Subiendo archivos con carrirewave
Instalando carrierwave
Generando el uploader
Probando desde rails console.
Creando una formulario con archivos
30) Amazon S3
31) Optimización
34) Fullcalendar
Agregando el plugin
Cargando el fullcalendar
Intro
Creando nuestro mailer
Modificando el mailer y como probarlo
ActiveJob y deliver_later
37) Rails y Nginx con Passenger en Ubuntu: Preparando nuestro entorno de producción
(DigitalOcean).
Introducción
Acerca de esta guía.
Convenciones.
Paso 0 – Como acceder a nuestro servidor
Ahora que tenemos el servidor preparado podemos instalar Nginx con Passenger:
Instalación
Creación de un usuario en postgres
Creación de la base de datos
Introducción
Paso 0 – La Aplicación
Paso 1 – Añadir Capistrano a nuestra app.
Paso 2 – Preparación de nuestro proyecto
Paso 3 – Tareas personalizadas
Paso 4 – Conectando el servidor con el repositorio
Ahora que sabemos cómo y qué hace Capistrano haremos un deploy de nuestra app
2) Motivación
¿Qué es Rails?
Rails en un framework especializado en la construcción de aplicaciones web, su principales ventajas es
que es sólido, rápido y escalable.
Rails no es lo mismo que Ruby, Ruby es un lenguaje de programación orientado objetos y Rails es un
conjunto de herramientas construídas en Ruby para la construcción de aplicaciones web.
Por otro lado también es sorprendemente rápido para la construcción de prototipos, para una persona
entendida en el tema es posible crear un prototipo funcional de un proyecto y dejarlo subido en internet
en sólo un par de horas.
Otro aspecto a considerar es la curva de aprendizaje de este framework que es bastante dura. Ruby on
Rails tiene muchas componentes que vamos a dicutir a lo largo de este libro y muchas cosas dan la
impresión de funcionan solas y por arte de magia y es por eso que es difícil de aprender pero muy
poderoso cuando se le domina.
Finalmente está el tema del rendimiento, Ruby on Rails es un framework con muchas componentes por lo
mismo tiene un gran footprint, o sea que es pesado, y eso conlleva a que se necesitan servidores más
potentes para atender la misma cantidad de usuarios, por otro lado es bastante más sencillo construir
aplicaciones que en otros framework.
Tanto Ruby como Ruby on Rails están pensandos en la felicidad del programador en lugar de la eficiencia
del programa.
Donde aprenderemos todo lo necesario para crear páginas web con rails, y a manejar correctamente el
asset_path, crear pequeños formularios y entender los conceptos básicos de MVC.
Donde aprenderemos a subir aplicaciones a la web, y aprenderemos a configurar una página .com o
cualquier otro tipo de dominio.
Donde aprenderemos a manejar bases de datos en la suficiente medida para entender como funciona
Rails, como realiza las consultas y como optimizarlas.
Donde aprenderemos a construir aplicaciones web con bases de datos y autenticación de usaurios con
controles de acceso y sistemas de pago.
Finalmente aprenderemos como subir nuestra aplicación a entornos enterprise como Amazon y VPS y
configurar nuestros propios servidores ocupando NginX.
Sección I
Front-end
OSX
Uitlizando la versión de ruby que viene instalada de fábrica instalaremos Homebrew, un administrador de
paquetes para OSX.
Linux
No es necesario instalar ningún administrador de paquetes, al mayoría de las distribuciones viene con
alguno incluído, ubuntu ocupa apt-get
En Linux y OSX
Ahora instalaremos RVM, un programa que nos permite tener instalada diversas versiones de ruby y
actualizarlo sin dolor, existen otros como Rbenv y chruby, las diferencias son menores y sólo afectarán en
la última parte de deployment así que para uniformar el proceso recomendamos a todos instalar RVM.
Es importante leer siempre los logs de la instalación para asegurarnos que haya terminado exitosamente
o poner los comandos que nos pida en caso de que no.
Con RVM instalado y los logs leídos (y asegurado de que todo terminó OK) ahora hay que reiniciar la
terminal, luego podremos correr:
1 rvm install 2.2.3
Nuevamente esperaremos que termine de correr, esto puede demorar ya que necesita compilar los
binarios de ruby. Una vez terminado el proceso debemos verificar que quedó instalado, esto lo podemos
hacer con el comando:
1 rvm list
Eso nos muestra todas las versiones de ruby instaladas con RVM, de las cuales podemos escoger una
con:
Para asegurarnos que estamos sobre la versión correcta debemos correr el comando:
1 ruby -v
Las gemas que se instalan dependen de la versión de ruby, si cambiamos la versión tendremos que
instalar rails nuevamente.
Rails trabaja por defecto con SQLite es cuál es suficiente para construir prototipos pero queda corto a la
hora de construir aplicaciones potentes, durante el capítulo de Heroku veremos como instalar la gema de
postgreSQL
Preguntas
1. ¿Qué es RVM?
2. ¿Cómo mostramos todas las versiones que tenemos instaladas de ruby?
3. ¿Cómo cambiamos la versión que estamos ocupando de ruby?
4. ¿Con que motor de base de datos trabaja rails por defecto?
4) Estructura de un proyecto en rails
Archivo Para qué sirve
bin Contiene los binstubs, los cuales son wrappers sobre gemas ejecutables
config Directorio con archivos de configuración de rails, los entornos y la base de datos
Objetivos
Rails es un framework MVC, esto quiere decir que divide la información a lo largo de 3 capas principales,
el modelo, la vista y el controlador.
En este capítulo abordaremos las componentes de vista y controlador creando una página estática con
Ruby on Rails.
Para empezar desde el terminal crearemos nuestro primer proyecto una simple página estática pero
sobre rails.
1 cd landing1
2 rails s
Para verificar que todo funcione abriremos el navegador y entramos a la página 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 tab nuevo para poder seguir trabajando.
Para terminar el servidor, o sea cerrarlo sin cerrar el tab podemos utilizar ctrl + c y eso terminará el
programa devolviendonos el control para poder ejecutar comandos.
Introducción a controllers
Para empezar con rails vamos a partir creando una página estática, para eso vamos a crear un controller
que por ahora lo entenderemos como un grupo de páginas.
localhost:3000/grupo1/inicio
localhost:3000/grupo1/formulario
Tanto los controllers como los modelos y vistas (los cuales veremos despúes) se pueden crear
manualmente o con el generador de rails.
Por ejemplo si queremos crear únicamente la página inicial (index), lo hacemos de la siguiente forma:
Es convención de rails que los controller siempre tengan un nombre plural, y hace sentido pensando en
que son un grupo de páginas.
Esto nos muestra todos los archivos creados y modificados, por ahora nos interesan sólo dos, el archivo
de rutas route get 'pages/index' y la línea que dice donde se creó la vista
app/views/pages/index.html.erb
Antes de proceder a explicar las vistas y el archivo de rutas, veamos lo creado, para poder hacerlo,
podemos acceder a la página que creada a través de la URL:
http://localhost:3000/pages/index
Esto sólo funcionará si el servidor está corriendo (hay una consola con rails s y si no hay errores en
el código.
Errores de novato
1) No tener el server corriendo
Revisar rails s
Actualmente sólo tenemos un título y un párrafo pero aquí es donde podemos empezar a agregar nuestro
contenido.
Para escribir ruby tenemos que hacerlo dentro de las expresiones en <%= %> y <% %>, la primera
expresión se ocupa para mostrar el contenido, el segundo sirve para evaluar, miremos los siguientes
ejemplos:
1 <% a = 2 %>
2 <%= a %>
3 <%= b = 2 %>
Esto mostraría dentro del navegador 2 2 el primer dos correspondiente al valor de 2, y el segundo al
valor de b, pero la primera expresión no se muestre porque <% %> no incluye el signo igual =
¿Esto quiere decir que podemos enviar ruby al navegador del cliente?
No, todo el contenido del archivo será transformado a HTML por rails antes de enviarlo, por lo
que el navegador jamás ve nada relacionado con rails.
Dentro del archivo config/routes.rb encontraremos una línea comentada que dice:
1 # root 'welcome#index
Esta línea es un recordatorio de rails que nos dice como podemos definir una página inicial, para utilizarla
vamos a descomentarla (remover el ‘#’) y luego apuntar al controller y la página respectiva, o sea en
nuestro caso debería decir:
1 # root 'pages#index'
Una solución sería ccrear un segundo controller pero no es necesario crear un controller por página.
Creando la ruta
Para agregar la ruta nueva debemos abrir el mismo archivo de rutas que abrimos anteriormente
config/routes.rb dentro de el agregaremos un get, sin los comentarios que vienen por defecto el
archivo de rutas, este se debería ver así:
1 Rails.application.routes.draw do
2 get 'pages/index'
3 get 'pages/about'
4 root 'pages#index'
5 end
Existe un comando que nos permite verificar las rutas creadas, este comando se llama rake routes
y lo tenemos que correr en alguna terminal que no esté secuestrada dentro de la carpeta del proyecto.
Más adelante de este libro analizaremos más a fondo esta información, por ahora sabemos que es
correcta porque se incluyó dentro del columna que dice URI Patten la página /pages/about , y con
esto terminamos el paso de agregar la ruta de la página.
Si nos hubiésemos saltado este paso, (o comentamos el get ‘pages/about’ que pusismos en el archivo
config/routes.rb e intentamos entrar a la página obtendríamos este error.
Cada vez que tengamos un error de rutas, sabemos que el archivo culpable es routes.rb.
Anteriormente habíamos hablado del controller, pero asumimos que funcionaba de forma mágica, ahora
vamos a tener que modificarlo para agregar una página nueva, para eso abriremos el archivo
app/controllers/pages_controller en el editor, y veremos:
Entonces si ya tenemos una ruta para about el paso siguiente es agregar un método (que es lo mismo
que una cción) dentro de la clase:
1 def about
2 end
Creando la vista.
Con la ruta y el método del controller creado ahora procederemos a crear el archivo
views/pages/about.html.erb y dentro de el podemos agregar lo que queramos.
Rails crea variables para todas nuestras páginas internas, para poder ver como se llaman esas variables
tenemos que ocupar rake routes
La columna que dice prefix son los nombres de la variable, bueno casi, es un prefijo 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 <%=
y dentro de la vista de abouts_us podemos agregar un link a la página principal de la siguiente forma:
Preguntas
1. ¿Si tenemos un error del tipo el método 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 acción?, en qué archivo va?
5. ¿Para qué sirve el formato .erb?
6. ¿Cuál es la diferencia en <%= %> <% %>?
7. ¿Qué tipo de navegador se necesita para ver un proyecto con ruby on rails?
8. ¿Cuántas páginas puede tener un controller?
9. ¿Qué comando muestra todas las rutas que existen en rails?
10. ¿Cómo se crea un controller con tres páginas?
11. ¿Cómo se destruye un controller?
12. ¿Cuál es la diferencia entre pages_index_path y pages_index_url?
13. ¿Qué hace el método link_to?
6) Agregando imágenes a nuestra primera
página
Objetivos
Los Assets
Los assets son todas las imágenes, sonidos, tipografías, películas, hojas de estilos y archivos javascript
que insertemos en nuestra página web.
En rails los assets se deberían 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 librerías 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 conflictos futuros.
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 imágenes
En una página web normal podemos ingresar imágenes de ruta relativa (dentro de nuestro computador)
simplemente usando el tag:
<img src="imagen-prueba.png">
Pero esto no lo podemos hacer en rails, bueno realmente podemos hacer algo como:
<img src="assets/images/imagen-prueba.png">
Pero, siempre y cuando tengamos una imagen dentro de la carpeta /assets/images/ y funcionará
sólo en el entorno local y fallará cuando queramos correr nuestra aplicación en un servidor, por lo mismo
no lo debemos hacer.
La forma correcta de incluir imágenes en rails es ocupando los métodos image_tag y asset_path
El método asset_path
El método asset_path sirve para todos los tipos de assets así que partiremos explicando este.
Para utilizar asset_path, el cuál es un método de rails, tenemos que utilizar el formato erb, o sea
incorporar la zanahoria <%= %> dentro del tag <img> de HTML o sea quedaría así:
Hay que tener cuidado con un par de cosas, primero, la imagen si tiene que existir dentro de la carpeta
/app/assets/images/, pero además debemos tener mucho cuidado con la sintaxis de asset_path, la
zanahoria (<%= %>), esta debe estar pegada a la primera comilla y el cierre de la zanahoria debe estar
pegada a la segunda, esto para evitar dejar espacios vacíos en el nombre del archivo que no forman
parte del nombre.
El método image_tag
Hay una forma más breve de incluir imágenes, pero no aplica a los otros assets, y esta es ocupando el
método image_tag.
Una ventaja de ocupar image_tag es que automáticamente genera un atributo alt utilizando el nombre del
archivo.
Obviamente que para que cualquiera de estas soluciones funcione tenemos que estar ocupando la
extensión erb, o sea nuestro archivo debe ser .html.erb (por defecto en ruby)
7) Nuestra primera página con layout.
Objetivos
El Layout
Si observamos detenidamente las vistas nos daremos cuenta que no están completas, no tienen la
estructura típica de un documento HTML, y es así, ¿de adonde sale el título de la página?
Dentro de la carpeta views existe una subcarpeta llamada layouts, estas son las páginas maestras,
podemos tener varias pero por defecto es una sola y esta se llama application.html.erb
Y si modificamos el layout modificaremos todas las otras vistas simultaneamente, probemos que esto sea
cierto. Al abrir el layout veremos lo siguiente:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Basic</title>
5 <%= stylesheet_link_tag 'application',
6 media: 'all', 'data-turbolinks-track' => true %>
7 <%= javascript_include_tag 'application',
8 'data-turbolinks-track' => true %>
9 <%= csrf_meta_tags %>
10 </head>
11 <body>
12 <%= yield %>
13 </body>
14 </html>
Esta es la página maestra y en ella podemos definir el contenido y estilo que queramos compartir a lo
largo de todas las páginas de nuestra aplicación.
Para probar que así sea cierto vamos agregar el siguiente texto antes de la instrucción yield
1 HOLA !!!
2 Modificando el layout
Entonces como cambiamos el estilo de nuestro sitio?, para eso tenemos que agregar CSS
Incorporando CSS
Los archivos CSS son fáciles de incorporar, simplemente debes colocarlo dentro de la carpeta
apps/assets/stylesheets, todos los archivos de ahí se cargaran en cada página gracias a que dentro de
nuestro layout viene incorporada la siguiente línea.
1 *
2 *= require_tree .
3 *= require_self
4 */
En primer lugar hay que aclarar que estas líneas están comentadas al estilo CSS para evitar que la página
web las cargue, puesto que require no es un instrucción de CSS válida, pero en este archivo si
puede haber CSS.
Cabe destacar que este archivo recibe el nombre de manifiesto, y cada una de las líneas
require dentro recibe el nombre de directivas.
Lo siguiente que vemos es require_tree, esta es la línea 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 especificar en este mismo archivo a través de la instrucción require y nombrando los css, por
ejemplo:
1 /* …
2 *= require reset
3 *= require layout
4 *= require chrome
5 *= require_tree .
6 *= require_self
7 */
Mientras que require_self carga el CSS (si es que hay) contenido dentro del archivo application.css en la
posición mencionada, en el ejemplo, primero se cargaría el archivo reset, luego layout y luego chrome y
finalmente todo el resto, si hubiese CSS dentro de este archivo application.css quedaría al final.
Para probarlo vamos a definir un CSS sencillo, al que vamos a llamar style.css, y tiene que estar dentro
de la carpeta app/assets/css
1 body{
2 background-color:#ddd;
3 font-color:#333;
4 width:80%;
5 margin:20px auto;
6 font-family: 'helvetica';
7 }
Si ahora cargamos nuestra página veremos que el fondo es gris, los márgenes y que la tipografía cargó.
La librería encargada de procesar estas directivas y convertir todos los CSS (y otros assets) en
ú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 línea que carga todos los js dentro del layout es:
1 <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
O sea rails incluye la librería de jQuery, y además, incluye la librería jquery_ujs que sirve para trabajar con
javascript no intrusivo, además incluye turbolinks del cual hablaremos más adelante.
O sea por ejemplo si tenemos un archivo style.css el cual pone una imagen como background de fondo,
entonces debemos nombrarlo style.css.erb y luego dentro de la url(fondo1.png) cambiarlo por
Es muy importante que nos fijemos bien en la sintaxis de nuestro archivo CSS. Si bien CSS es tolerante a
fallos debido al principio de graceful degradation, la precompilación de archivos CSS no es tan tolerante y
un punto y coma sobrante causara problemas a la hora de enviar nuestro proyecto a producción.
La precompilación de assets es un proceso por el cual los assets son preparados para producción.
1. Preprocesadores
Transforma los lenguajes a su forma compartible, por ejemplo los archivos coffescript se convierten
en javascript, los archivos SASS se convierte en CSS.
2. Concatenados
Todos los CSS se juntan en un CSS final, todos los JS se juntan en un JS final, esto reduce el número
de requests hechos al servidor traduciéndose en una página más rápida
3. Minificados
Tanto al CSS final como al JS final se le remueven todos los caracteres innecesarios, como por
ejemplo los saltos de líneas y los espacios, eso se traduce en archivos de menor peso y por lo mismo
páginas más rápidas.
4. Fingerprint
Consiste en agregar una secuencia de números para convertir el nombre de un asset en un nombre
único y realizar de forma más sencilla el caching de los assets.
El entorno
En ruby on rails existen distintos entornos de funcionamiento, por defecto son tres, desarrollo, testing y
producción.
La razón por la que existen diversos entornos es porque tienen distintos funcionamientos, cuando
trabajamos en nuestro computador quremos ver los errores con el mayor detalle posible, pero cuando
nuestra aplicación está online y la está ocupando una persona externa y sucede algún error queremos
estar seguros de que no expondremos una falla de seguridad de nuestro sistema y por lo mismo en rails
se enconden los errores en modo de producción.
El modo testing sirve para correr pruebas, lo ocuparemos más adelante en este libro.
El modo staging se agrega cuando se quiere un paso previo a producción (a veces con clientes reales, o
a veces solo homologando el ambiente para prueba) y prevenir errores inesperados al dar el paso a
producción.
Cuando uno trabaja en el entorno de desarrollo no se realizan todos los procesos de
precompilación de assets, sólo se realiza el preprocesado para poder utilizar sass y
coffeescript, el resto de los procesos sólo se procesan cuando pasamos al entorno de
producción o si modificamos nuestro entorno de desarrollo para que lo haga.
Podemos correr rails en modo de producción dentro de nuestro computador cargado el server con el
parámetro -e production
1 rails s -e production
Pero esto nos generará conflictos ya que todavía no tenemos configurado algunos archivos, así que el
intentar cargar la página obtendremos el siguiente error.
En los capítulo de heroku tanto como en los últimos capítulos de este libro hablaremos con más detalle
del paso a producción.
Rails trae tres carpetas de assets incluídas en el asset_path, hasta el momento sólo hemos mencionado
la que se encuentra dentro de app, pero dentro de la carpeta vendor y dentro de la carpeta lib también es
posible agregar assets.
La idea es que la carpeta app/assets contenga los assets específicos del proyecto, la carpeta vendor
contenga los assets de librerías de terceros, como por ejemplo los de bootstrap, y finalmente la carpeta
lib para otros casos.
Cuando colocamos los assets en otras carpetas el require_tree no es suficiente para cargarlos, ya que
este tree se refiere al de app/assets/stylesheets, entonces además tenemos que mencionarlos dentro del
archivo, veremos un caso específico de esto en el ejemplo de bootstrap.
También para hacer más fácil las referencias puedes agregar subcarpetas
Verificando el asset_path
Rails tiene una consola que nos permite verificar este tipo de configuraciones, para entrar a ella dentro de
la carpeta del proyecto tenemos que escribir rails c o rails console y luego podemos
verificar nuestros assets con:
1 Rails.application.assets
Esto nos devolverá una lista con todas las carpetas que se encuentra dentro del asset_path
Si realizamos un cambio dentro del archivo de configuración es necesario salir de la consola y volver a
entrar para poder ver los cambios.
Para incorporar tipografías todavía nos falta aprender una cosa más, a manipular que archivos y que
carpetas son parte del asset_path, para esto vamos a modificar el archivo /config/initializers/assets.rb y
dentro de la clase vamos a agregar lo siguiente:
Consecuencias de Sprockets
Al unirse todos los CSS y cargarse en cada página podemos tener consecuencias indeseadas,
especialmente cuando tenemos plantillas que cargan diversos archivos CSS en cada páginas. Para
enfrentar este problema hay dos posibles soluciones, la primera consiste en darle un namespace a la
página, la segunda en utilizar un layout distinto, ahora abordaremos la solución del namespace y más
adelante en este capítulo estudiaremos como incorporar diversos layouts.
Para dar un namespace a la página lo que debe hacerse es ocupar el hash params dentro de la vista,
este hash contiene información general que se realiza en cada request, una información importante que
tiene es el nombre del controller y del método 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.
1 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 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Namespace Trick :)</title>
5 <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 <%= csrf_meta_tags %>
8 </head>
9
10 <body class="<%= params[:controller]%>">
11 <%= yield %>
12 </body>
13 </html>
De esta forma si cargamos alguna página dentro del controller pages, obtendremos
<body class="pages">
Queda un paso que en teoría es sencillo, ahora cada CSS que sea específico a una grupo de página debe
tener junto a cada marca un .nombre-controller y cada CSS específico a una sóla página debe contener
un .nombre-controller .nombre-acción
Incorporando bootstrap.
Hay varias formas de incorporar bootstrap, la primera y más 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 nuesta página maestra en views/layouts/application.html.erb
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 modificarlo (o si no bootstrap rescribirá nuestros
cambios) y los JS debemos cargarlo después de la instrucción javascript_include_tag pues esta
instrucción es la que carga jQuery en nuestro sitio y los JS de boostrap depende de jQuery. Entonces
nuestro layout con bootstrap quedaría así:
1
2 <!DOCTYPE html>
3 <html>
4 <head>
5 <title>Basic2</title>
6
7 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.m
8
9 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-t
10
11 <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
12 <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
13 <%= csrf_meta_tags %>
14
15 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js
16
17 </head>
18 <body>
19
20 <%= yield %>
21
22 </body>
23 </html>
Es posible descargar los CSS de boostrap dentro de la carpeta vendor/assets, tenemos que tener
cuidado de pones 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
Copiaremos los archivos CSS en lugar de los .min puesto que rails tiene la capacidad de minificarlos, o
sea los archivos bootstrap.css y bootstrap-theme.css van en vendor/assets/stylesheets, luego copiamos
el archivo bootstrap.js en vendor/assets/javascript
El último paso que nos queda es mencionar dentro del application.css los archivos bootstrap, puesto
como habíamos explicado previamente la línea require_tree dentro de
app/assets/stylesheets/application.css sólo carga el tree de stylesheets, por lo que debemos hacer el
require de forma explícita.
1 *= require bootstrap
2 *= require bootstrap-theme
3 *= require_tree .
4 *= require_self
Glyphicons
Este tema puede ser un poco más complejo porque en primer lugar hay que agregar la carpeta fonts
dentro de vendors/assets y copiar los archivos ahí, luego hay que agregar la carpeta fonts al asset_path
en el archivo config/initializers/assets.rb
En el mismo archivo hay que agregar los nuevos formatos al proceso de precompilación
1 Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets', 'fonts'
2 Rails.application.config.assets.precompile += %w( *.svg *.eot *.woff *.ttf *.woff2)
Pero además 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-
halflings, y buscar donde se cargan, debería aparecer con url() cerca de la línea 266 encontraremos:
1 src: url('../fonts/glyphicons-halflings-regular.eot');
2 src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'
3 url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),
4 url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-hal
Ahí es donde tenemos que eliminar el ../fonts y luego envolverlo ocupando <%= asset_path
"nombrefuente" %>
Vistas parciales
Una vista parcial es una parte de un archivo HTML que simplemente se pone en otro archivo.
La vistas parciales se caracterizan son archivos .html.erb pero tienen una carecterística especial,
empiezan con el prefijo _ y esto ayuda a los programadores a distinguir que vistas no están completas,
o sea son parciales y son para ser insertadas en otros archivos.
Para cargar una vista parcial sólo tenemos que especificar el nombre de un archivo HTML que empieze
con el prefijo _
Las vistas parciales sirven mucho en dos casos, desacoplar y mantener ordenadas las vistas y para evitar
repetir código, el primer caso lo estudiaremos en este capítulo, el segundo lo veremos más adelante.
Si queremos desacoplar una vista, o sea por ejemplo si queremos copiar la barra de navegación de
bootstrap que consisten en más de 40 líneas de código HTML es mejor copiarlas dentro de una vista
parcial y de esa forma no ensuciar el layout, debemos crear un archivo dentro de app/views/layouts que
llamaremos _navbar.html.erb , dentro de el copiaremos la barra de bootstrap que aparece en la
página.
1 <nav class="navbar navbar-default">
2 <div class="container-fluid">
3 <!-- Brand and toggle get grouped for better mobile display -->
4 <div class="navbar-header">
5 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse
6 <span class="sr-only">Toggle navigation</span>
7 <span class="icon-bar"></span>
8 <span class="icon-bar"></span>
9 <span class="icon-bar"></span>
10 </button>
11 <a class="navbar-brand" href="#">Brand</a>
12 </div>
13
14 <!-- Collect the nav links, forms, and other content for toggling -->
15 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
16 <ul class="nav navbar-nav">
17 <li class="active">
18 <a href="<%= landings_efrain_path %>">
19 Efrain <span class="sr-only">(current)</span>
20 </a>
21 </li>
22 <li>
23 <a href="<%= landings_gonzalo_path%>">Gonzalo</a>
24 </li>
25 </ul>
26
27 <form class="navbar-form navbar-left" role="search">
28 <div class="form-group">
29 <input type="text" class="form-control" placeholder="Search" name="color
30 </div>
31 <button type="submit" class="btn btn-default">Submit</button>
32 </form>
33 <ul class="nav navbar-nav navbar-right">
34 <li><a href="#">Link</a></li>
35 <li class="dropdown">
36 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button
37 <ul class="dropdown-menu">
38 <li><a href="#">Action</a></li>
39 <li><a href="#">Another action</a></li>
40 <li><a href="#">Something else here</a></li>
41 <li role="separator" class="divider"></li>
42 <li><a href="#">Separated link</a></li>
43 </ul>
44 </li>
45 </ul>
46 </div><!-- /.navbar-collapse -->
47 </div><!-- /.container-fluid -->
48 </nav>
y finalmente dentro del layout antes del yield escribiremos <%= render ‘layouts/navbar’ %>
Cargando un layout distinto
Cada controller se encarga de cargar el layout, si no se específica uno se carga el layout por defecto, el
cuál es application.html.erb
Dentro del método correspondiente podemos especificar que layout o cargar, o si no queremos ningún
layout también podemos hacerlo
1 def index
2 render layout: false
3 end
Podemos hacer lo mismo a nivel de controller para todos los métodos internos
Para mostrar un layout distinto es la misma idea, supongamos que queremos hacer un layout distinto
para las landings page:
A nivel de acción:
1 def index
2 render 'landings'
3 end
A nivel de controller:
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Layout Distinto</title>
5 <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 <%= csrf_meta_tags %>
8 </head>
9 <body>
10
11 hola
12 <%= yield %>
13
14 </body>
15 </html>
Desafío
Cargar una plantilla de HTML dentro de rails.
Preguntas
Objetivos
En el capítulo anterior vimos las componentes de vistas y controles de rails, en este capítulo abordaremos
la introducción a modelos creando nuestro primer formulario.
1) Creamos el proyecto
El tag form
En Ruby on Rails al igual que en HTML podemos agregar formularios utilizando los tags de forms.
Para probarlo construiremos un formulario simple dentro del index de la aplicación que ya tenemos
definida.
1 <form method="">
2 <input name="q">
3 <input type="submit" value="Enviar">
4 </form>
Después de guardar, dentro de la página web deberíamos ver un input, y si lo llenamos veremos que por
defecto nos redirige a la misma página, pero esta cambiará ligeramente, pues al final de la url aparecerá
?q=hola donde q es el nombre del input y el igual el valor que fue pasado.
Todos los parámetros en rails se pásan a través de un hash llamado params, dentro de la vista podemos
mostrar el contenido de este hash con:
Veremos:
Es rails los hash tienen un pequeña diferencia con los de ruby, en estos los accesos con string o con
símbolos son exactamente iguales, o sea podemos obtener el valor de q usando params[:q] o
params["q"]
Entonces si queremos mostrar sólo el valor del email enviado lo podemos hacer con <%=params[:q]%>
Ahora sacaremos <%=params%> de la vista por que no tiene sentido mostrar los párametros 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á suficiente.
Para crear una tabla de datos y guardar los usuarios necesitamos crear un modelo, en el patrón MVC
cada modelo mapea los datos a su tabla de la base de datos, o sea si creamos el modelo llamado
usuario, se creará una tabla en la base de dato llamada usuarios y el modelo nos ayudará a guardar los
datos sin tener que usar comandos SQL.
Se recomienda escribir el código en inglés, el inflector de Rails viene configurado por defecto
en inglés y por lo tanto puede haber un error en el nombramiento de las tablas al intenta
pluralizar bajo las reglas del español
Donde user es el nombre del modelo y email un campo que tendrá la tabla.
A diferencia de los controllers la convención es nombrar los módelos en singular, puesta esta clase
mapea a un elemento (por ejemplo un usuario) con sus datos en la tabla de la base de datos.
invoke active_record
create db/migrate/20151120195902_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
O sea se genera el modelo User, una migración para crear la tabla users en la base de datos y tests.
1 rake db:migrate
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 o rails c , desde consola de Rails donde podemos
procesar e insertas datos a la base de datos.
Creando un usuario
Cuando uno crea un modelo lo que se crea es una clase con ese nombre, o sea dentro de rails nosotros
ahora podemos instanciar un objeto user con:
1 user = User.new
O lo podemos instanciar directamente con un email
1 user.email = "nuevoemail@desafiolatam.com"
Estos datos no persisten en la base de datos hasta que los guardemos, eso lo podemos hacer con:
1 user.save
También podemos guardar un usuario directamente en la base de datos sin instanciarlo, eso lo podemos
hacer con el método .create
1 User.create(email: "creando_usuario_directamente@gmail.com")
1 User.all
Esto gatillará una consulta SQL y nos devolverá un array especial llamado ActiveRecord::Relation pero el
cuál a fin de cuentas es un array.
User Load (0.3ms) SELECT "users".* FROM "users"
=> #, #]>
Como los resultados son una array podemos iterarlo con un .each
Obtendremos:
El array al final se obtiene porque el método .each al terminar de iterar devuelve el arreglo original.
1 exit
Para eso tenemos que ir al controller de landings, y detectar si se enviaron parámetros en el formulario, si
se hizo los guardamos en la base de datos.
El método .blank? revisa si el parámetro q es nulo o vacío, o sea si hay un valor en el formulario que sea
distinto de nulo o vacío 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:
En estos logs debemos tener especial cuidado cuando después de la inserción aparesca rollback
transaction en lugar de commit transaction, eso es un indicador de que la operación falló.
1 root 'landings#index'
Luego podemos verificar que haya funcionando accediendo con nuestro navegador a
localhost:3000'
También podemos obeservar que al hacer rake routes aparacerá este cambio:
Desafío
Crear un formulario funcional en la plantilla que creamos en el capítulo anterior
Preguntas
Objetivos
1 cp -r nombre_proyecto1 nombre_proyecto2
Una vez dentro nos daremos cuenta si entramos a rails console ` que tenemos los mismos datos,
o sea copiaron los usuarios creados en el proyecto anterior, pero eso no se debe a que ambos utilizen las
misma base de datos, se debe a que sqlite3 es un archivo que está dentro de la carpeta db y nostoros
copiamos, por lo tanto nos trajimos todos los cambios.
Podemos probar que estos es así agregando datos en uno solo de los proyectos y luego revisando en el
otro, veremos que sólo están en uno, esto no ocurrirá posteriormente cuando trabajemos con
PostgreSQL u otro motor de base de datos.
Otro cuidado que tenemos que tener 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.
Cuando se envía un formulario por GET los campos aparecen en la URL, esto es muy útil si se quiere
mostrar y poder compartir los resutados, por ejemplo si buscamos algo en google y queremos compartir
los resultados de la búsqueda lo podemos hacer copiando la url y envíandosela a alguien, pero cuando se
quiere enviar información sensible y no dejar un registro de eso en la URL hay que ocupar el método
POST
Para enviar un formulario por post tenemos que indicar en el método que es POST.
1 <form method="POST">
2 <input name="q">
3 <input type="submit" value="Enviar">
4 </form>
Hacer un match a un ruta requiere tanto de la url como el método, no es lo mismo hacer un get a una
página que un post, para probarlo podemos hacer rake routes
Para crear una ruta nueva vamos abrir el archivo de rutas en config/routes.rb
MVC in a nutshell
Pero … ¿Por qué necesitamos una URL nueva?, si antes funcionaba con una sola.
El concepto para entender bien MVC es que una URL es una acción y una acción nueva requiere una
URL nueva, o sea para agregar procesamiento al formulario tenemos que agregar una ruta nueva, esa
URL nos llevará a un controller y un método específico y ahí manejaremos la lógica del formulario.
Si bien un if dentro del controller nos permitiría manejar múltiples acciones debemos recordar siempre el
concepto KISS (Keep it simple and stupid) entonces en lugar de manejar la opción de que si el parámetro
viene hacer tal cosas, mejor simplemente separar ambas acciones, una para mostrar el formulario y una
para procesarlo.
Existe otra ventaja en este caso específico, si tuviesemos varios landings distintos probablemente no
haya ninguna razón para manejar estos formularios de registro de forma distinta, de esta forma al tener
acciones separadas evitamos reescribir código.
La ruta nueva puede ser por POST o por GET, la diferencia es que por GET los parámetros se envían a
través de la URL, en cambio POST se pasa en los headers del request, esto lo estudiaremos con más
profundidad en el capítulo de negociación de contenido.
1 post 'pages/receive'
Para confirmar que la ruta fue agregada correctamente correremos el comando rake routes en la
consola, como resultado deberíamos obtener:
La ruta nueva no la podemos probar en el navegador ya que eso sería acceder por GET, pero podemos
probarla utilizando el formulario, para eso vamos a necesitar el prefix.
Ya habíamos comentado que podemos referirnos a cualquier página de rails ocupando el prefix + _path o
prefix + _url el primero es una ruta relativa y para que en un formulario podamos dirigir a una página
distinta tenemos que ocupar el atributo action
Form y action
El método form_tag
Otra forma de hacer exactamente lo mismo es ocupando el método form_tag de ruby, no es mucho el
código que se ahorra en los casos sencillos, pero no está de sobra saberlo.
1 <%= form_tag landings_receive_path, method: :post, class: 'form' %>
2 <%= label_tag "email", "Email" %>
3 <%= email_field_tag "email" %>
4 <%= submit_tag "Subscribirse" %>
Unknown action
The action 'receive' could not be found for LandingsController
La razón de este error es porque todavía no hemos creado el método de receive, es lo que haremos a
continuación.
Para eso vamos a abrir el controller /app/controllers/landings_controller.rb en el editor de texto, dentro del
archivo crearemos el nuevo método.
Cada método muestra por defecto una vista del mismo nombre por ejemplo index carga la vista dentro de
views/landings/index.html.erb pero hay una excepción. Si se utiliza la instrucción render o redirect_to,
esto nos puede servir para cambiar el comportamiento y en este en lugar de mostrar una vista (o un error
porque todavía no hemos creado la vista) nos ayudaría a mostrar los parámetros del formulario recibido.
1 {
2 utf8: "✓",
3 authenticity_token: "9WFUTrwJgKSCKIKA4lQPsHted6TRWmERCJTY65u+Ix94dFLxPdfY2XzFETBSEVDjU1knMCe
4 email: "gonzalo@desafiolatam.com",
5 commit: "Subscribirse",
6 controller: "landings",
7 action: "receive"
8 }
Guardando los resultados
Si estamos enviando los datos de forma correcta el siguiente paso es guardarlo, ya tenemos el modelo
creado, lo que tenemos que hacer es mover la lógica que teníamos en index a receive, quedaría así:
Se deben manejar los errores en caso de inserció esto lo estudiaremos en un capítulo futuro
Redirect_to
En lugar de mostrar los resultados del formulario después de crearlo podríamos redirigir al landing (o a
otra página al usuario y mostrarle que sus datos fueron guardados con éxito, para eso ocuparemos el
método redirect_to, este recibe dos parámetros, el primero es la URL a la que hay que redirigir al usuario
y el segundo permite enviar mensajes flash, los mensajes flashs son variables que viven durante un sólo
request o sea son para mostrar a la próxima carga de página, ideal para avisos de lo lograste o tal cosa
falló.
Variables flashs
Nos falta una mejora, siempre existe una posibilidad de que la operación de guardado pueda fallar, en
este caso es muy difícil que suceda dada la simplicidad de la operación, pero siempre debemos manejar
y reportar los casos de falla.
No basta con crear variables flashs, tenemos que mostrarlas, para eso dentro de la vista maestra, o sea
layouts/application.html.erb mostraremos las variables flash.
Es una práctica común desde el punto de vista de usabilidad mostrar estas alertas bajo la
barra de navegación. Mostrarlas arriba de la barra de navegación haría que se moviera y se
vería raro, mostrarlas muy abajo haría que el usuario no las viera.
1 get 'landings/get_leads'
Dentro del controller agregaremos el método get_leads y desde ahí obtendremos todos los leads.
1 def get_leads
2 @users = User.all
3 render json:@leads
4 end
User.all devuelve todos las filas de la tabla leads dentro de un array (realmente no es una rray pero eso lo
estudiaremos más adelante), este array lo vamos a guardar dentro de una variable de instancia, lo cual
nos permitirá más adelante 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, para eso tenemos que crear dentro de
app/views/pages el archivo get_leads.html.erb pero primero tenemos que decirle al
controller que ocupe la vista y para eso removeremos el render.
1 def get_leads
2 @users = User.all
3 end
Para mostrar los resultados iteraremos todos los leads obtenidos (los users son un array) y por cada uno
de ellos mostraremos el email.
1 <ul>
2 <% @users.each do |u| %>
3 <li> <%= u.email %> </li>
4 <% end %>
5 </ul>
Desafío
Modificar el formulario de la plantilla que hicimos previamente para incorporar un formulario que funcione
por posts, el usuario debe ser notificado cuando ingresó sus datos y se debe crear una página que
permita verificar todos los usuarios ingresados.
Preguntas
1. ¿Cuál es la diferencia entre enviar datos por get o por post?
2. Complete la oración. ¿Una acción nueva requiere de una …
3. ¿Para que sirve el atributo action de los formularios?
4. ¿Cuál es la diferencia entre utilizar el form de HTML y la etiqueta form_tag de
rails?
5. ¿Que hace redirect_to?
6. ¿Cuál es la diferencia entre redirect_to y render ?
7. ¿Qué hace render json: params?
8. ¿Qué son las variables flashs?
9. ¿Cuál es la diferencia entre utilizar <%= @users.each do |u| %> y <%
@users.each do |u| %>?
10) El gemfile
Objetivos
El gemfile es un archivo que contiene todas las dependencias de un proyecto en Rails estas
dependencias reciben el nombre de gemas, con este archivo especificamos que bibliotecas 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 correr el programa simplemente debemos ejecutar bundle .
1 source 'https://rubygems.org'
2
3 # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
4 gem 'rails', '4.2.5'
5 # Use sqlite3 as the database for Active Record
6 gem 'sqlite3'
La primera línea especifica de donde deben ser buscadas las gemas, el resto de las gemas se especifca
con las instrucción gem, luego el nombre de la gema y como parámetro opcional la versión.
Al correr bundle se generará un archivo llamado gemfile.lock, este archivo contiene todas las gemas y
dependencias (otras gemas) de un proyecto.
Tanto el gemfile como gemfile.lock deben ser añadidos al repositorios pues permiten para otros instalar
las mismas libererías de tu proyecto en rails con las versiones específcadas con una sola línea de
comando.
Se puede pedir que una gema sea mayor que una versión con:
Se puede pedir que la versión a instalar esté dentro de una versión menor de la gema, (o sea que el
cambio sea sólo en el segundo decimal)
Preguntas
Objetivos
1. Conocer las diferentes alternativas que hay para hacer deployment de una
aplicación
2. Instalar y configurar las herramientas necesarias para hacer deployment a
Heroku
3. Aprender a hacer deployment en Heroku
Se le llama deployment al proceso de subir la aplicación en modo de producción (o sea lista para atender
usuarios reales), mientras que mientras estamos trabajando en nuestros computadores se dice que la
aplicación está en modo de desarrollo.
Esto no es sólo un decir, rails puede detectar el ambiente que está y correr configuraciones dependiendo
del ambiente, por ejemplo en producción podríamos tener funcionando google analytics, mientras que en
desarrollo no.
Además de estar en proudcción una aplicación requiere de dos cosas, un hosting y un dominio, no son lo
mismo y no hay que confundirlos.
Una pequeña aclaración, el dominio no es estrictamente necesario, si tiene un ip fija (cuando pagas por
un hosting usualmente te dan una ip) puedes usar esa ip para entrar a la aplicación, sin embargo tus
clientes o usuarios no van a recordar ese número, es necesario tener un dominio
Tipos de hosting
Hay varias estrategias para subir una aplicación:
Servidor propio
Este es el típico hosting que uno arrienda y viene con Cpanel, por lo general son baratos, antes de
arrendar uno de estos revisa bien los términos y condiciones y en especial que tengan un buen servicio
de soporte y compatibilidad con Rails 4 (muchos no lo tienen)
un hosting clásico puede costar desde 10 dólares al año, generalmente cuestan del orden de 20 dólares
mensuales, dan poco ram, y además el setup que tienen no está optimizado para Rails, usualmente
vienen con Apache y MySQL, pero NginX tiene mejor rendimiento en cuanto a usuarios / costo
Rails trabaja igual de bien con MySQL que con PostgreSQL sin embargo en un hosting clásico estás
limitado a eso al setup que trae, no puedes crear tu configuración propia
VPS
En un VPS te arriendan una máquina virtual, hay de dos tipos arriendas un servidor normal (Linode, Digital
Ocean, Etc) o un con escalamiento automático como Amazon.
El pro de los VPS es que obtienes un muy buen precio y una buena máquina, además 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 buen
trabajo.
PAAS
Existen diversos sistemas PAAS el más famoso es Heroku, de Salesforce, existen otros como Engine
Yard, estos sistema te permiten levantar rápido tu aplicación, además heroku tiene planes gratuitos que
son suficientes para subir tu primer prototipo y mostrarselo a los primeros clientes.
El link al toolbelt debería aparecer después de crear la cuenta pero de todas formas puedes descargar el
toolbelt directamente desde toolbelt.heroku.com
Claves SSH
Heroku intentará agregar nuestras claves ssh automáticamente, pero para eso supondrá que tu juego de
claves se llama id_rsa si no las tenemos al momento de instalarlo debemos crearlas y luego agregarlas,
para eso debemos ir a la carpeta .ssh dentro de tu carpeta personal y crear un nuevo juego de claves
1 ls ~/.ssh
1 ssh-keygen -t rsa
1 heroku keys:add
También es posible copiar y pegar el contenido de la clave ssh pública (id_rsa.pub) a través de la interfaz
de heroku en la sección de configuración de cuenta.
Repositiorio GIT
Configurando GIT
Primero debemos estar trabajando sobre un repositorio GIT si no es así, lo crearemos con:
1 git init
El primer paso es crear un proyecto en Heroku, esto lo podemos hacer a través del panel de control pero
es mucho más sencillo realizarlo directamente con bash en la carpeta del proyecto, los únicos requisitos
son tener GIT configurado y el toolbelt de Heroku instalado.
Entonces sobre un proyecto con repositorio GIT ya inicializado crearemos el proyecto en heroku con el
comando:
1 heroku create
Ahora debemos cambiar el Gemfile de nuestro proyecto, Aquí hay que remover la línea que dice sqlite3
del gemfile (puesto que heroku no la soporta) y agregar la de postgreSQL y otra que permite obtener los
logs y arreglar algunos problemas de assets en producción.
Como se hace siempre tras hacer un cambio en el archivo gemfile, debemos correr en el terminal el
comando para actualizar nuestras gemas.
Si no agregamos las gemas, o no hacemos bundle, o no hacemos el commit después obtendremos el
siguiente error.
Para especificar la versión de ruby debemos escribir la versión dentro del gemfile como en el siguiente
caso:
1 source 'https://rubygems.org'
2 ruby "2.2.3"
3
4 # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
5 gem 'rails', '4.2.3'
1 bundle
Donde dentro de las comillas deberías especificar los cambios que se han realizado luego debemos
enviar los cambios a producción, esto es lo que comunmente se llama deployment.
Revisando errores
Si tienes un error en los assets, tenemos que encontrar el error y corregirlo, causas comunes es un
archivo con una extensión errada, ej algo.csc (en lugar de css).
No es bueno agregar estos assets al GIT, como vimos previamente esto correrá los preprocesadores
sobre los archivos respectivos, luego concatenara los CSS y JS, luego los minificará y finalmente les
añadirá un fingerprint, los assets finales quedarán dentro de la carpeta public.
Después debemos borrarlos dentro de la misma carpeta, no es una buena práctica agregarlos a GIT.
Sobre la precompilación de assets
Recuerda migrar la base de datos de producción cada vez que migres la tuya.
Desafío
Subir al aplicación que creaste en el capítulo anterior a heroku, el formulario debe quedar funcionando.
Preguntas
Para entenderlo bien vamos a revisarlo en orden de importancia, o sea de derecha a izquierda.
Los dominios de nivel superior (ejemplo los .com, .cl, .mx) son entregados a diversas autoridades a lo
largo del mundo, estas autoridades se dedican a administirar y vender los dominios.
Los dominios son el nombre, es lo que uno le compra a las diversas autoridades a los largo del mundo,
algunas autoridades venden solo el suyo, otras como godaddy venden de múltiples tipos.
El valor de un dominio depende de múltiples factores pero principalmente depende de que tan solicitado
es, hoy en día es casi imposible tomar un dominio de 4 letras, y mucho menos uno que tenga sentido,
dominios de estos tipos se transan por millones de dólares.
Otros dominios pueden costar incluso de 5 dólares con un cupón de descuentos, fatwallet es una buena
página para buscar cupones.
Una vez que tienes el dominio puedes proceder a configurarlo, y puedes agregar todos los subdominios
que desees.
Entonces que es el subdominio?, es lo que antecede al dominio, el más famoso de todos es www, pero
puedes configrar el que quieras.
Una vez comprado el dominio lo importante es configurarlo para realizar un redireccionamiento tipo
Cname, algunos sistemas de los administradores no lo permiten, GoDaddy lo permite, Nic Chile por
ejemplo no, ya veremos como resolver eso más adelante.
Para probarlo simplemente entra a la página configurada. Recuerda que la propagación de nombres es
lenta y por lo tanto puede demorar de 1 a 48 horas. (normalmente es una hora).
La configuración es sencilla:
Tips de mantención
muy útil cuando tienes errores en la versión remota pero en la local funciona bien
1 heroku logs
Dejar una consola con los logs corriendo
1 heroku logs -t
Es posible descargar la base de datos de heroku a tu computador, y utilizarla como respaldo o, mejor aún
utilizarla para trabajar con datos reales y 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.
Esto descargará un archivo que se llama latest.dump en la carpeta donde hayas lanzado la línea de
comandos.
Desafio
Comprar un dominio .com y configurar la aplicación creada en el capítulo anterior para que quede
funcionando con el dominio o con un subdominio.
Preguntas
Existen diversas implementanciones de SQL, siendo las más famosas MySQL, PostgreSQL y Oracle, hoy
en día MySQL le pertenece a Oracle y a pesar de que algunas implementaciones siguen bajo licencia GPL
también ha incorporado diversas licencias comerciales lo que empujó a diversos miembros de la
comunidad a moverse a PostgreSQL, ahora la principal razón para utilizar Postgre 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 agnóstico a la base de datos, esto quiere decir que puede ser configurado con
cualquiera de ella mientras existan los drivers, y existen drivers para todas las bases de datos conocidas.
Al día de hoy PostgreSQL es la mejor opción para trabajar con rails, primero porque viene configurado
con heroku y homologar los entornos de desarrollo con el de producción facilita el desarrollo y pruebas
del software, pero además Postgres incluye diversas funcionales como manejo nativos de array y de
hashs que abordaremos más adelante en este libro.
Esta forma es fácil de prender y apagar y nos aseguramos de que el servidor no esté corriendo de fondo
gastando recursos en nuestro computador.
Ahora necesitamos entrar a PostgreSQL para crear una base de datos. En linux
1 su - postgres
En OSX
1 psql
Para que lo anterior funcione, el servicio debe de estar corriendo, pues en caso contrario obtendremos
un:
1 psql
Una curiosidad que tiene SQL es que es un lenguaje insensible a las mayúsculas, la convención consiste
en escribir las palabras reservadas de SQL en mayúsculas, y los nombres de las tablas y valores en
minúsculas
Creando usuarios
En postgres los usuarios y los roles son lo mismo, podemos cambiar el password de un usuario con:
Podemos listar a todos los usuarios para ver si los creamos exitosamente.
1 gonzalosanchez=# \du
desafio_blog Superuser {}
super_gonzalo Superuser {}
En SQL las tablas son archivos, archivos que contienen un solo formato y se parecen en cierto sentido a
un excel, sólo que las columnas son fijas.
Cuando creamos una tabla definimos una estructura, cuando insertamos datos no podemos salirnos de
la estructura de la tabla definida, sin embargo en el futuro podemos cambiar la estructura de la tabla
siempre y cuando especifiquemos 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.
Postgre al igual que otros motores de bases de datos maneja múltiples bases de datos, y cada usuario
del sismte
Por lo mismo en lugar de borrar se dice botar una base de datos, por el drop.
No hay vuelta atrás 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.
Para poder ver los datos de las diversas tablas que hay dentro de una base de datos necesitamos
primero conectanos a una, eso lo podemos lograr con:
1 \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
1 \t
Para poder crear tablas, que son archivos con estructuras fijas, primero vamos a tener que entender un
poco más de estas estructuras, y para eso hay que entender que cada columna tiene un nombre y un tipo
de dato.
Los detalles exacto de los tipos de datos no los abordaremos en este libro pero pueden ser consultados
en la doumentación oficial de PostgreSQL. http://www.postgresql.org/docs/9.4/static/datatype.html
Partamos creando una tabla con los tipos más simple, integer y varchar
integer, que permite guardar enteros de hasta 4 bytes, o sea números entre -2147483648 to
+2147483647
CREATE TABLE table_name( column1 datatype, column2 datatype, column3 datatype, ….. columnN
datatype );
Ahora con esto nosotros guardaremos datos en una tabla que almacena personas con nombre y edad
Insertando valores
Para insertar valores dentro de la tabla podemos hacerlo con el comando INSERT, la sintaxis básica es la
siguiente:
Para leer datos de una tabla la instrucción es SELECT, con SELECT nosotros podemos especificar los
valores que queremos o utilizar * para indicar que queremos todos los campos de la tabla.
name age
Camila 26
Gonzalo 30
(2 rows)
Camila
Gonzalo
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 sólo una persona debemos acompañar el update de la instrucción where
Debemos cuidar las comillas, estas tienen que ser comillas simples cuando remplazemos valores por
strings, en lugar pueden ir sin comillas cuando los valores sean numéricos.
Podemos borrar datos ocupando la instrucción delete, pero debemos tener mucho cuidado de ocupar el
where o borraremos todos los datos.
Podemos borrar bajo la condición de igualdad, pero también podemos borrar bajo otras condiciones,
como en el siguiente ejemplo:
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.
1 ERROR: new row for relation "products" violates check constraint "products_price_check"
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.
ERROR: null value in column "name" violates not-null constraint DETAIL: Failing row contains (null, 100).
El constraint Unique
1 CREATE TABLE products3 (
2 name VARCHAR(100) UNIQUE,
3 price numeric CHECK (PRICE > 0)
4 );
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 combinación entre los constraints NOT NULL y UNIQUE, pero además es un
índice, que en este caso permite encontrar de forma rápida los resultados, y además te asegura que sea
único los resultados, esto en la mayoría de los casos hace sentido, por ejemplo todos los autos tienen
una patente la cual es única dentro de cada país, y esta clave permite encontrar todos los datos
referentes a ese auto específico, en la mayoría de los países existe un identificador 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.
Si hemos ido siguiendo la secuencia de este libro en este momento obtendremos el siguiente error.
El error dice que una columna con clave primaria no puede tener valores null y eso tiene mucho sentido
ya que este es un identificador ú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 debería ser sólo una persona.
name age id
Julian 55
1 UPDATE personas SET id=1 WHERE name='Julian';
Si todo está bien en lugar de error, obtendremos como output alter table, pero ahora nunca más
podremos ingresar usuarios sin id o con un id que esté repetido.
Obtendremos:
Es posible crear directamente una tabla con clave primaria, la sintaxis es la siguiente:
name age id
Francisca 30 1
Juan 31 2
Javier 32 3
Penelope 28 4
Para ordenar por algún criterio tenemos que agregar order by y la columan sobre la cual vamos a ordenar.
name age id
Penelope 28 4
Francisca 30 1
Juan 31 2
Javier 32 3
name age id
Penelope 28 4
Juan 31 2
Javier 32 3
Francisca 30 1
(4 rows)
Conteo
Aquí la sintaxis es ligeramente distinta, en lugar de seleccionar una tabla en específico seleccionaremos
la cuenta de elementos de esa tabla
Select distinct
Para los siguientes ejemplos necesitamos un par de datos más, para eso vamos a ingresar a otra mujer
llamada Penelope en la base de datos de distinta edad.
Nuestro set de datos quedaría:
name age id
Francisca 30 1
Juan 31 2
Javier 32 3
Penelope 28 4
Penelope 30 5
Podemos seleccionar de la base de datos todos los nombres distintos, o sea si hay un nombre repetido
no aparecerá en la respuesta.
name
Javier
Juan
Penelope
Francisca
Un ejemplo donde esto podría ser muy útil es si queremos extraer de nuestra base de datos cuantas
personas hay de cada país o ciudad (o ambas)
Distinct y count
Podemos combinar distinct y count para contar la cantidad de elementos distintos.
Con los datos de nuestro ejemplo deberíamos obtener 4, y si hacemos el count sin el distinct deberíamos
obtener 5
Preguntas
Guía de ejercicios
Peliculas
Completar los queries y poner la consulta SQL repectiva de cada pregunta subirlos a la plataforma de
empieza.
CRUDS
Sorting
Conteo
Productos
Completar los queries y poner la consulta SQL repectiva de cada pregunta subirlos a la plataforma de
empieza.
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 películas y categorías pero que pasa si queremos
renombrar una categoría, entonces tendríamos que asegurarnos de cambiar todas las referencias a la
categoría en la tabla películas, en una base de datos pequeñas esto podría no ser un problema pero en
bases de datos grandes es más complejo.
Separando una tabla en dos partes, la primera contiene los datos donde insertaremos la película, la
segunda es una tabla donde insertaremos todas las categorías, y las relacionaremos a través de un
número.
De esta forma sólo tenemos que cambiar el valor dentro de la categoría para cambiar todos los valores
referidos.
1 create table movies (
2 id integer primary key,
3 title varchar(64),
4 category_id integer
5 );
6
7 create table categories (
8 id integer primary key,
9 name varchar(64)
10 );
11
12 INSERT INTO categories VALUES (1, 'Acción');
13 INSERT INTO categories VALUES (2, 'SCI FI');
14 INSERT INTO categories VALUES (3, 'Animación');
15
16 INSERT INTO movies VALUES (1, 'Terminator', 1);
17 INSERT INTO movies VALUES (2, 'Terminator 2', 1);
18 INSERT INTO movies VALUES (3, 'Volver al futuro', 2);
19 INSERT INTO movies VALUES (4, 'Terminator 3', 1);
20 INSERT INTO movies VALUES (5, 'Volver al futuro 2', 2);
21 INSERT INTO movies VALUES (6, 'Tiburón', 1);
22 INSERT INTO movies VALUES (7, 'Akira', 3);
23 INSERT INTO movies VALUES (8, 'Ghost in the Shell', 3);
24 INSERT INTO movies VALUES (9, 'Duro de matar', 1);
1 Terminator 1 1 Acción
2 Terminator 2 1 1 Acción
4 Terminator 3 1 1 Acción
6 Tiburón 1 1 Acción
7 Akira 3 1 Acción
1 Terminator 1 2 SCI FI
2 Terminator 2 1 2 SCI FI
4 Terminator 3 1 2 SCI FI
6 Tiburón 1 2 SCI FI
7 Akira 3 2 SCI FI
1 Terminator 1 3 Animación
2 Terminator 2 1 3 Animación
4 Terminator 3 1 3 Animación
6 Tiburón 1 3 Animación
7 Akira 3 3 Animación
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 instrucción where.
1 Terminator 1 1 Acción
2 Terminator 2 1 1 Acción
4 Terminator 3 1 1 Acción
6 Tiburón 1 1 Acción
7 Akira 3 3 Animación
Cuando hay campos con los mismos nombres en ambas tablas no podemos ser ambiguos, por ejemplo
si realizamos el query:
ERROR: column reference "id" is ambiguous LINE 1: SELECT * FROM movies, categories WHERE id =
category_id;
Es por lo mismo que en la mayoría 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 instrucción JOIN
Hay diversos tipos de join y principalmente difieren en como tratar la unión de estos conjuntos cuando los
valores no están asociados.
Para entender bien la diferencia vamos a ingresar una película sin categoría y una categría sin película.
Luego si probamos:
O si probamos:
1 Terminator 1 1 Acción
2 Terminator 2 1 1 Acción
4 Terminator 3 1 1 Acción
6 Tiburón 1 1 Acción
7 Akira 3 3 Animación
1 Terminator 1 1 Acción
2 Terminator 2 1 1 Acción
4 Terminator 3 1 1 Acción
6 Tiburón 1 1 Acción
7 Akira 3 3 Animación
Tipos de relaciones
Recientemente separamos la tabla películas en dos tablas para evitar tener datos repetidos como el
nombre de la película dentro de cada una.
Al tener dos tablas se generó una relación entre ellas, podemos decir que una película tiene una
categoría, pero una categoría puede tener múltiples películas, 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 elemetnos
de la otra tabla.
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.
nombre
edad
direccion
telefono
tarjeta de credito
rut (identificador de la persona)
compañia
direccion compañia
telefono compañia
cargo
En una tabla como esta podemos separar los datos del usuario con los de la compañía, y sólo llenarlos si
la persona tiene compañía, entonces quedaría la tabla usuarios y la tabla de compañías, pero para unirlas
tenemos que hacer una de dos, guardar un id de la compañía en la tabla usuarios o guardar el usuario en
la tabla de compañía, determinar cual es la mejor forma tiene que ver más con el diseño de la aplicación
que con el de la base de datos, pero la pregunta de rigor que nos haríamos es que se guardará primero,
si creamos el usuario y luego la compañía es más sencillo guardar el usuario en la compañía, si creamos
primero la compañía y luego al usuario probablemente sea más sencillo guardar el dato de la compañía
en el usuario.
Hay diversas formas de diagramar las relaciones, usualmente se representa con una flecha cuando tiene
un elemento y con doble flecha cuando tiene más de un elemento.
Relaciones de 1 a n
Estas son las relaciones más frecuentes, normalmente sucede cuando hay una tabla de datos y una tabla
de categoría de esos datos, por ejemplo: persona y pais, o, ciudad o país, pelicula y categoría. En otros
casos sucede cuando también hay un dueño 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 autoría 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 sección puede ser accedida por varios usuarios.
Estas relaciones son un poco más complejas porque no se pueden representar directamente en un
modelo de datos relacional, porque donde pondríamos los índices?, si los agregamos por el lado de
usuarios un proyecto_id como haríamos para meter varios proyectos?, por otro lado si lo agregamos del
lado del proyecto con usuario_id como haríamos para meter varios usuarios?
De esta forma pueden haber cuantas personas quieran trabajando en cuantos proyectos quieran.
La clave foránea
La clave foránea (en ingés 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 refieran a
el.
En nuestro ejemplo anterior no podríamos borrar la categoría acción porque hay películas que utilizan esa
categoría.
Ahora si intentamos ingresar una película con una categoría que no existe, obtendremos:
ERROR: insert or update on table "movies2" violates foreign key constraint "movie
s2\_category\_id\_fkey"
DETAIL: Key (category_id)=(10) is not present in table "categories".
Preguntas
Ejercicios
Shopping
movieDB
5. Ingresar las FK para relacionar las películas existentes con las categorías respectivas
6. Obtener todas las películas de la categoría Acción y contarlas
7. Ordenar la tabla de categorías según la cantidad de películas que hay con esas categorías
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 foraneas tag_id y movie_id
3. Ingresar un grupo de tags
4. Asignar 3 tags a cada películas (puedes ocupar más de un insert)
5. Obtener todas los nombres de las películas con el tag dinosaurios
6. Utilizando joins, devuelve todas las películas con todos sus tags
7. Contar la cantidad de tags que tiene cada película -hint tienes que hacer dos joins en el mismo query
8. Devolver los tags ordenados por mayor uso -hint hacer el join sólo con la tabla intermedia
Exportando datos
Objetivos
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
artículos, ahora los comentarios son un campo de artículo o una tabla aparte?, el título del artículo 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, sólo nos genera las consultas SQL de forma automática.
Modelando un blog
Lo primero que tenemos que hacer es distinguir a los actores y las acciones principales.
Entonces: - una visita entra y ve artículos - una visita entra al artículo 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
articulos nuevos) - un editor puede borrar artículos y comentarios - al borrar un articulo se deben borrar
todos sus comentarios
La pregunta de rigor al modelar es ¿qué infomración necesitamos utilizar? y para poder utilizarla ¿quçe
necesitamos guardar?.
¿Vamos a guardar las visitas en la base de datos o no?, quizas no sea útil ya que estas no registran
nada, si queremos medir el acceso de visitas podemos hacerlo utilizando alguna solución como
google analytics y no tenemos la necesidad de meter estos datos en nuestra base de datos.
Los artículos necesitamos guardarlos, porque vamos a mostrar esa información, cada artículo puede
tener un título 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 después.
Hay comentarios, y estos tenemos que guardarlos para poder mostrarlos, y le pertenecen a un
usuario, pero además uno comenta dentro de un artículo, y dentro de un artículo pueden haber varios
comentarios, así que los comentarios le pertenecen a un artículo.
Las convenciones son muy importantes, especialmente cuando trabajamos con Rails, una de estas
convenciones es la del nombre de las claves foráneas, el nombre debe ser el nombre de la tabla referida
en singular con un sufijo id.
O sea si la tabla comentario tiene usuarios, la FK debería llamarse usuario_id y en lo posible todos los
nombres en inglés para evitar problemas.
Ejercicio
Crear la blase de datos blogX
Crear un usuario llamado "Julian" del tipo editor
Crear 5 artículos y asociarlos al usuario creado
Crear 3 usuarios más y agregar comentarios a los artículos creados
Mostrar todos los articulos con la cantidad de comentarios de cada uno
Mostrar todos los usuarios con la cantidad de comentarios creados de cada uno
Mostrar los articulos junto con la información del editor.
Modelando Tinder
Tinder es una famosa red social de citas, que tiene usuarios y tu ves personas marcando si te gustas o
no, si ambas se marcan como gustados entonces es un match, ahora hay múltiples formas de modelar
esto, pero analizemos alguna.
La pregunta entonces es, que actores hay, y que información 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 aplicación 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 información, pero si debemos saber entre que usuarios
se hizo match.
Ahora en el funcionamiento de la aplicación, se encuentran personas que están cerca, por lo tanto
tenemos que saber donde están esas personas, para eso agregaremos latitud y longitud, y además no
podemos mostrarle usuarios que ya haya descartado a una persona, por lo que tenemos que guardar la
información de si la interacción fue de like o unlike, para eso renombraremos la tabla like a interacciones
y guardaremos cada interacción entre un usuario uno y otro.
Ejercicios de diagrama
Modificar 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 interacción negativa
Obtener todos los usuarios con los que hayas tenido una interacción positiva
Obtener todos los usuarios con los que no hayas tenido una interacción.
Obtener todos los nombres de los matches de un usuario.
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 sesión 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 más comprados
Sección IV: Backend
15) Rails con PSQL
Objetivos
1 gem install pg
1 development:
2 adapter: postgresql
3 encoding: unicode
4 database: nombre_base_de_datos
5 pool: 5
6 username: usuario
7 password: password
Los archivo yaml o .yml requieren de una identación perfecta, asegura de agregar espacios de
sobre ni que falten, así como no agregar comillas donde no vayan.
Con el archivo database configurado el siguiente paso es abrir el archivo gemfile, sacar la gema de sqlite3
del entorno de desarrollo y agregar la gema ‘pg’ al entorno de desarrollo, (y de producción si así se
desea)
1 rake db:setup
2 rake db:create
Existen posibilidades de que el usuario puesto en la configuración no tenga permisos para crear bases de
datos, en ese caso debemos o cambiar el usuario de la configuración, o darle acceso a ese usuario a
crear bases de datos, o finalmente crear la base de datos a mano.
De todas formas necesitaremos abrir el archivo database.yml para configurar nuestra base de datos, sin
embargo no tendremos que abrir el gemfile para remover la gema sqlite3 y agregar la gema pg.
El error de socket
Existe un error muy común al cargar el servidor de rails y abrir la página que dice
Preguntas
Objetivos
El concepto de modelo en rails puede ser complicado de entender porque es simultaneamente dos
cosas.
En primera opción es un ORM (object relational mapper) lo cuál 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.
Además los modelos manejan la capa lógica del negocio, o sea se encargan de cuidar la integridad de los
datos a un nivel más abstracto y más fácil de programar que el de SQL
En este capítulo abordaremos los modelos como un ORM, y en el capítulo 16 abordaremos el como
cuidar y validar datos con los modelos.
Obtendremos:
invoke active_record
create db/migrate/20151130040223_create_tasks.rb
create app/models/task.rb
invoke test_unit
create test/models/task_test.rb
create test/fixtures/tasks.yml
Al crear modelos se generan migraciones que nos permiten crear las tablas de forma automática en la
base de datos, en el caso anterior se generó la migración
db/migrate/20151130040223_create_tasks.rb , podemos correr esos cambios con:
1 rake db:migrate
Luegos gracias a esto obtendremos métodos de clase que nos permiten traer a memoria todos los tasks
que hay en la base de datos, o, encontrar uno en específico, ya sea el primero, el último o por algún
criterio a nuestra voluntad.
Estos los podemos probar directamente en rails console o dentro de nuestro proyecto rails .
No es sorpresa que en ninguna de las pruebas anteriores obtendremos resultados distinto de vacío,
puesto que todavía no tenemos datos, par ahacerlo podemos generar instancias de los tasks para
guardar tareas nuevas.
1 t = Task.new(user: "Gonzalo")
2 t.save
1 Task.create(user: "Gonzalo")
Para actualizar un dato de la base de datos tenemos que traerlo a memoria primero, luego podemos
modificarlo y guardarlo.
1 t = Task.first
2 t.name = "R2D2"
3 t.save
Hay métodos que traen a memoria un objeto, en ese caso ese objeto será de ese tipo, como por ejemplo
t.task será Task, pero los métodos que traen múltiples 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 términos
de que son iterables.
Se debe tener cuidado de no confundir los métodos de instancia con los de clase, por ejemplo
no tiene sentido obtener `Task.user` ¿de qué usuario estaríamos hablando? sin embargo si
tiene sentido obtener un usuario de la tabla y preguntar su nombre.
La clave para evitar este error es entender quelos métodos de clase sirven para operar por sobre la tabla,
y en cambio los métodos de instancia sirven para operar sobre una de las filas.
Creando modelos
En rails cada vez que creamos un modelo se crea una migración que nos permite modificar la base de
datos y que existe esa persistencia a nivel de base de datos cuando ingresamos, borramos o
modificamos datos.
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
Para cambiar la estrucutra de la base de datos, particularmente sirven para crear, actualizar y borrar
tablas, campos e índices.
Convención de nombres
La convención 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 español puesto que las reglas no son
las mismas y podemos causarnos problemas.
Podemos crear un modelo vacío, (sólo id, created_at, updated_at los modelos son en minúscula, con:
Para crear el modelo con uno o más campos, pero debemos ocupar un nombre distinto
1 invoke active_record
2 create db/migrate/20151128090136_create_comments.rb
3 create app/models/comment.rb
4 invoke test_unit
5 create test/models/comment_test.rb
6 create test/fixtures/comments.yml
O sea, un archivo de migración, un archivo con el modelo y tests (los cuales cubriremos pronto en un
próximo capítulo)
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
información.
Ahora, dentro de los tipos de datos comunes que ocuparemos, los más freceuntes son:
integer
float (decimales)
boolean (true o false)
string (hasta 255 chars)
text (más de 255 chars)
Migraciones
Una migración 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
aplicación, la base de datos es el esqueleto de una aplicación web y las migraciones nos permiten
generar un control de cambios bien fino y evitar errores por diversas versiones.
Imaginemos el siguiente caso, dos personas, Pedro y Raúl están trabajando en una aplicación web,
Pedro crea un campo nuevo para una tabla y crea el código para manejar ese campo, luego sube al
repositorio los cambios, pero la base de datos está en su computador, por lo tanto sólo sube los cambios
respectivos a lógica del manejo del campo y no notifica que agregó un campo en la base de datos, luego
Raúl descarga los cambios de Pedro y la aplicación deja de funcionar.
Las migraciones resuelven el tipo de problemas que tienen Pedro y Raúl, pues estas son parte del código
y por lo mismo se agregan a los repositorios, gracias a las migraciones podemos compartir de forma
sencilla las aplicaciones y con sólo rake db:migrate estar listos para empezar a trabajar.
El archivo schema.rb
El secreto para entender las migraciones es el archivo db/schema.rb, el cual a esta altura luce así:
1 ActiveRecord::Schema.define(version: 20150819042819) do
2 create_table "tasks", force: :cascade do |t|
3 t.string "task"
4 t.datetime "created_at", null: false
5 t.datetime "updated_at", null: false
6 t.string "user"
7 end
8 end
El schema contiene toda la información respecto al estado actual de la base de datos, la suma de todas
las migraciones construye el schema, y la versión del schema, o sea el número que aparece corresponde
al nombre del archivo de la última migración.
Rails sabe que hay migraciones no corridas con solo comparar el último archivo de las migraciones con el
de la versión del schema.
Creando migraciones.
Al crear modelos se crean migraciones, pero nosotros también podemos crear una migración nueva
utilizando el generador de rails rails g migration nombre_migracion pero esto generará una
migración vacía.
Tanto para el añadir columnas como para removerlos existen autogeneradores, Si recuerdan bien ya los
hemos ocupado previamente para agregar el campo usuario a task.
El pasado no lo debemos modificar, pero podemos, por lo tanto debemos hacerlo con cuidado.
Supongamos que Raúl realizó una migración pero esta tuvo algún error, ya sea por el tipo de dato, o por
un error tipográfico y supognamos que se dio cuenta después de haber corrido el comando
rake db:migrate si Raul todavía no ha compartido sus cambios a través de un push todavía está a
tiempo de enmendar su error.
Enmendando un error.
Como no podemos modificar el schema lo que tenemos que hacer es modificar 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 códigos las migraciones deben ser las mismas, por eso
sólo vamos a enmendar los errores de esta forma cuando no hayamos compartido nuestros cambios.
Si los cambios ya fueron enviados entonces lo que tenemos que haces es simplemente avanzar hacia
adelante, o sea crear una nueva migración que remueva la columna mal hecha y que la cree de nuevo, de
esta forma para aplicar los cambios sólo basta rake db:migrate.
Cuando se está trabajando con sqlite3 en la versión 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 Raúl ahora está trabajando en un proyecto con PostreSQL, va a implementar una
funcionalidad nueva por lo que hace un branch del código, luego crea una migración y la corre
modificando la base de datos, después 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 situación, volver al branch y hacer un rake db:rollback (o tantos como
sea necesario) y volver al branch orginal o …
Botón de autodestrucción
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 reseta:
1. rake db:drop
2. rake db:create
3. rake db:migrate
4. rake db:seed
Si quieres saber más sobre migraciones revisa la guía oficial de migraciones en Rails
http://guides.rubyonrails.org/active_record_migrations.html
Es importante siempre respaldar la base de datos porque una vez que la botemos no la
podremos recuperar.
Ahora que ya entendemos lo básico de migraciones podemos volver al modelo y sus campos.
Destruir un modelo
Una vez que hayamos creado un modelo no podemos volver a crear campos para agregar datos, desde
el punto de vista de SQL no tendría sentido crear dos veces las misma tabla, habría que destruir una para
luego crear la otra, por lo mismo podemos destruir el modelo pero si ya tiene código esto no tendría
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 migracion automática o sin el que nos obliga a nosotros a
escribir los comandos para crear las tablas, añadir los campos o hacer lo que queramos hacer.
Con helper
Por ejemplo:
Sin helper
1 rake db:migrate
1 rake db:rollback
El número de la versión lo podemos sacar de los nombres de los archivos dentro de la carpeta de
migraciones.
Todas las migraciones se encuentran dentro de la carpeta db/migrate , las migraciones están
ordenadas por un número que es una fecha invertida por lo tanto las últimas siempre iran al final.
Al correr el comando rake db:migrate se corren todas las migraciones y a partir de ellas se genera
db/schema.rb , no se corran todas desde cero, el archivo schema contiene un número, ese número
corresponde al de la última migración corrida, a partir de ahí rake db:migrate corre todo el resto de las
migraciones y actualiza el archivo schema.
Probar un modelo
Una vez creado el modelo debemos probarlo como lo hicimos en capítulos previos.
1 rails c
1 Modelo.new
Es importante ocupar mayúscula 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.
Los índices generan dependencias, por ejemplo ya no podremos borrar un usuario sin borrar o
reasignar la compañía que tiene antes
Getters y setters
Para la siguiente sección vamos a crear un modelo user con nombre y un campo para saber si es
administrador o no.
1 u = User.create(name:"Gonzalo", admin:true)
Los modelos automáticamente crean métodos getter y setters para cada uno de los campos de nuestro
objeto.
Por ejemplo podemos obtener el nombre del usuario con u.name y el valor de si es administrador con
u.admin en el caso de los valores booleanos se crea un método getter adicional con un signo de
interrogación al final, entonces es lo mismo escribir u.admin que u.admin?
El archivo seed
El archivo seed sirve para agregar datos de prueba a la base de datos de desarrollo (e incluso podría
servir para poblar una base de datos de producción, como por ejemplo para agregar los administradores
y configuraciones inciales)
Dentro del archivo seed vienen comentado dos ejemplos que se explican de forma bastante clara
El primero sirve para crear varias ciudades, el segundo para crear un Mayor para la primera ciudad
(dependiendo de la versión de rails estos ejemplos pueden ser distintos).
1 c = Category.create("Disney")
2 Movie.create(name: "El rey león 2", category: c)
Atributos virtuales
Los atributos virutales 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 cálculo que no queremos guardar en la base de datos, para
crear atributos virtuales sólo tenemos que crear variables de instancias en el modelo con sus respectivos
getter y setters.
Preguntas
Ejercicio
Crea el modelo icecream con el campo sabor (flavour) 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, deberían 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"
Modifica 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.
17) Reglas del negocio
Objetivos
Dentro de los modelos podemos validar presencia de campos, que estos sean únicos, que sean mayores
que otros, que la combinación de campos sea única, o que un producto no esté expirado o que una
licitación esté hecha dentro de cierto rango de fechas, este tipo de reglas dependen específicamente de
cada negocio.
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 método validate
acompañado de los métodos que queramos ejecutar, ejemplo
1 validate :metodo_custom
2
3 def metodo_Custom
4 if expiration_date.present? && expiration_date < Date.today
5 errors.add(:expiration_date, "can't be in the past")
6 end
7 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 modificar una
columna y darle valor inicial.
Ejemplos:
Callbacks
La segunda forma es utilizando callbacks, los modelos permiten llamar automáticamente métodos en
ciertos momentos, por ejemplo antes de guardarse, de esa forma podemos detectar si no hay valores
ingresados y ponerlos nosotros.
Estos métodos se llamaran en el orden establecido, o sea primero metodo1 y luego metodo2, pero para
que esto funcione debemos definir los métodos, ejemplo:
1 def metodo1
2 self.user = User.first
3 end
Debemos tener mucho cuidado de nunca devolver false dentro de estos métodos pues en es
caso rails asumirá que alguna validación falló y hará rollback del commit, o sea no se
guardarán los datos.
Ejercicios
Copiar todos los códigos a un archivo de texto
Crear un proyecto nuevo de rails, con una configuración nueva de la base de datos
Crear el modelo de posts (sin agregar los usuarios)
Validar que el título del post este presente
Crear 5 artículos desde la consola
Crear una artículo sin título y observar el error obtenido
Cuando se cree un post sin usuario se debe asignar al usuario por defecto.
Cuando se borre un usuario que sea dueño de posts se deben asignar todos esos post al usuario
"usuario por defecto"
Preguntas
Objetivos
En SQL podemos relacionar dos tablas haciendo un select a múltiples tablas o haciendo un join, cuando
lo hacemos estamos uniendo dos o más tablas en una resultante.
En rails podemos hacer lo mismo al establecer relaciones, sin embargo debemos especificar el tipo de
relación.
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 relación de uno a muchos entre los posts y los comentarios podemos
hacer lo siguiente:
1 @post = Post.first
2 @post.comments
Establecer la relación en el modelo nos genera automáticamente métodos que nos permiten obtener los
objetos relacionados de forma sencilla, en este capítulo 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 pertence al
primero.
Si tenemos un usuario y una compañía semanticamente hace más sentido decir que la compañía le
pertenece al usuario, y que el usuario tiene una compañia, debemos tratar de ocupar las relaciones
siempre en el sentido semántico, si no se hace muy difícil programar si lo que se lee no tiene sentido en
españo.
Entonces crearemos dos modelos usuario y compañia
1 rake db:migrate
1 has_one :company
En el modelo company.rb nos daremos cuenta que ya se encuentra la relación, esto se agregó
automáticamente ya que rails lo dedujo a partir del user:references
1 belongs_to :user
Al igual que cuando agregamos los campos validadores en los modelos, al agregar referencias
debemos reiniciar la consola, despues de hacer cambios en el modelo, lo podemos hacer
ocupando el comando reload!
Debemos tener cuidado de por cual lado va la clave foránea 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.
1 u = User.new(name:"Rick Hunter")
2 c = Company.new(name:"SDF-1 Macross", user:u)
¿Qué fue lo que obtuvimos al hacer las relaciones? ganamos el obtener métodos getter automáticos para
obtener objetos a partir del otro, ejemplo:
1 u.company
2 => #<Company id: nil, name: "SDF-1 Macross", user_id: nil, created_at: nil, updated_at: nil>
3 c.user
4 #<User id: nil, name: "Rick Hunter", created_at: nil, updated_at: nil>
Podemos borrar campos en cascada, esto quiere decir que al borrar un usuario se borrará su compañía
automáticamente, esto lo podemos hacer agregando dependent: :destroy en el modelo dentro de la
relación.
reiniciar la consola
Para probarlo esta vez si necesitamos guardar los datos, (si no están no tiene sentido borrarlos)
1 u = User.create(name:"Rick Hunter")
2 c = Company.create(name:"SDF-1 Macross", user:u)
3 u.destroy
Donde del SQL claramente se distinguen dos borrados, primero el de la compañía asociada y luego el de
el usuario que se intenta destruir.
En OSX
En Ubuntu
y finalmente bundle.
1 rake diagram:all
Se necesita la base de datos para generar el documento, por lo mismo es necesario correr las
migraciones antes del comando
User
Models diagram
Date: Nov 28 2015 - 02:15 id :integer
Migration version: 20151128080750 name :string
Generated by RailRoady 1.3.1 phone :string
http://railroady.prestonlee.com 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, películas y categorías, una categoría tiene muchas películas, una
película le pertenece a una categorías
luego
1 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 abstracción por sobre los datos, por lo mismo al igual que en las
relaciones 1 a 1 ahora debemos agregar las relaciones.
para agregar las relaciones debes agregar belongs_to :category al modelo de películas y
has_many :movies al modelo de categoría.
en el modelo category.rb
1 has_many :movies
y luego abriremos el modelo de movies.rb para revisar si se creó la relación, debería aparecer:
1 belongs_to :category
Observar que el has_many viene acompañado con el nombre de la tabla en plural, y es por que son varias
películas y no una, esta es una de las grandes razones porque debemos programar en inglés en lugar de
en español, porque las reglas de inflección (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 categoría y una película.
1 m.category
Si observamos el diagrama veremos que es similar al de 1 a 1 pero en este caso vemos varias flechas
para representar que la relación es de 1 a n.
Lo nuevo en este tipo de relación es que categoría no tiene una película, tiene muchas, por lo mismo el
getter para obtener todas las películas es en plural.
1 c.movies
2 #=> #<ActiveRecord::Associations::CollectionProxy [#<Movie id: 1, name: "Hercules", category_id:
El método build
1 m = c.movies.build(name:"Wall-E")
De esta forma automáticamente el objeto nuevo tiene como clave foránea la id del padre, debemos tener
cuidado porque aún cuando el objeto no esté guardado en la base de datos si mostramos ahora todas las
películas veremos esta nueva, para guardarlo en la base datos es el mismo proceso.
1 m.save
Múltiples relaciones de 1 a n
Son muy pocas las aplicaciones que tengan unicamente una o dos tablas, una aplicación pequeña puede
tener entre 5 y 10 y una grande puede tener cientos, muchas veces existen relaciones entre varias de
estas simultáneamente.
En el caso típico de un blog tenemos usuario, comentarios y posts, ya estudiamos un posible modelo
para esto en el capítulo 13, y era:
Generamos los tres modelos:
Corremos las migraciones con rake db:migrate agregamos las relaciones en los respectivos
modelos.
1 has_many :comments
2 has_many :posts
En el modelo de posts:
1 has_many :comments
2 belongs_to :user
En el modelo de comments:
1 belongs_to :user
2 belongs_to :comment
Podemos rescatar los post de un usuario de la misma forma que hicimos previamente, pero para realizar
las pruebas vamos a tener que agregar algunos datos, lo podemos hacer en el archivo seed.rb o
directamente en la consola.
1 u1 = User.create(name:"Usuario 1")
2 u2 = User.create(name:"Usuario 2")
3 u3 = User.create(name:"Usuario 3")
4
5 p1 = Post.create(content:"Articulo 1 del usuario 1", user: u1)
6 p2 = Post.create(content:"Articulo 2 del usuario 1", user: u1)
7 p3 = Post.create(content:"Articulo 3 del usuario 1", user: u1)
8
9 Comment.create(content:"Comentario del usuario 1 en el articulo 1", user: u1, post
10 Comment.create(content:"Comentario del usuario 1 en el articulo 1", user: u1, post
11
12 Comment.create(content:"Comentario del usuario 2 en el articulo 1", user: u2, post
13 Comment.create(content:"Comentario del usuario 2 en el articulo 1", user: u2, post
14
15 Comment.create(content:"Comentario del usuario 3 en el articulo 1", user: u3, post
16 Comment.create(content:"Comentario del usuario 3 en el articulo 1", user: u3, post
17
18 Comment.create(content:"Comentario del usuario 2 en el articulo 1", user: u2, post
19 Comment.create(content:"Comentario del usuario 3 en el articulo 2", user: u3, post
1 user = User.first
2 user.posts
Tambien podemos obtener todos los comentarios con user.comments pero que pasa si lo que
necesitamos es un poco más complejo, por ejemplo como podríamos obtener todos los comentarios que
ha recibido en todos sus posts un usuario.
Intuitivamente podríamos decir user.posts.comments pero sería un error, puesto que user.posts es un
active record association, es como un array de posts y este no tiene un método .comments
Podemos seleccionar primero todos los posts de ese usuario y luego buscar dentro de estos los
comentarios.
1 comments = []
2 user.posts.each {|p| comments << p.comments }
El problema es que ahora comments es un array y no podemos realizar filtros sucesivos o utilizar otros
métodos del activerecord
Como solución podemos utilizar dos joins, o sea unimos la tabla de comments con la de post, y la de
post con la de usuarios.
pero hay una forma mucho más sencilla, que es utilizando hasmanythrough
Para llegar a una tercera tabla a través de una segunda podemos utilizar has_many :through en el
modelo, la sintaxis es la siguiente
Ahora en el caso de nuestro modelo de usuarios ya tenemos una relación llamada comentarios, por lo
que no podemos crear otra igual, pero en este caso podemos crear una relación con nombre, para eso
especificamos el nombre de la relación y el destino, ya que no lo puede calcular sólo.
Relaciones n a n
Existen dos formas de implementar relaciones de n a n en rails, la primera es ocupando el has_many
:through, o sea dado que tenemos 3 modelos, donde uno de los
Preguntas
Objetivos
Los modelos son el esquelo de la aplicación y por lo tanto es muy importante que desarrollemos tests
automatizados para probar nuestra lógica del negocio.
En este capítulo 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 lógica 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 código es sólido y lo que es peor obviaremos aquellas que son más
importantes y representan un riesgo para nuestra aplicación.
Dentro de un proyecto rails todos los test se encuentran en la carpeta test de nuestra aplicación, dentro
de ella encontraremos subcarpetas para distintos tipos de tests, en este capítulo utilizaremos sólo dos de
ellas, models y fixtures los cuales sirven para hacer unit testing de los modelos.
Los test corren en un entorno especial llamado testing y no comparten los datos con la base de datos de
desarrollo o producción.
Los fixtures
Los fixtures son los archivos con los datos que se cargaran en la base de datos en cada prueba. Cuando
creamos un modelo se crea automáticamente un fixture para ese modelo, los fixtures están en formato
yaml por lo que su identación es clave.
1 one:
2 content: MyText
3 post_id:
4 user_id:
5
6 two:
7 content: MyText
8 post_id:
9 user_id:
Un secreto para no tener problemas con la identación es cuidar las tabulaciones, son críticas
en YML, utilizar dos espacios, en lugar de tabs, esto requiere configurar el editor.
Por defecto los fixtures 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 después con una migración no
aparecerán aquí, pero los podemos agregar manualmente.
También hay que destacar que los fixtures no incluyen el campo id, pero podemos agregarlo, esto es
especialmente útil a la hora de probar relaciones.
Hay dos formas de escribir los tests dentro de este archivo, la primera es creando métodos que empiezen
con test_
1 def test_the_truth
2 assert true
3 end
El método assert
Los asserts que vimos en las líneas anteriores son afirmaciones, con un assert le estamos diciendo a ruby
que una cosa tiene que ser de una forma y si no cumple, que nos avise.
Entonces con estos asserts podemos hacer pruebas como determinar si un objeto con ciertos dados
puede ser guardado con éxito en la base de datos.
Cuando ejecutemos los tests obtendremos un reporte por cada assert que no haya cumplido.
1 assert post.save?
Assertion Purpose
assert_equal( expected, actual, [msg] ) Asegura que el primer parámetro sea igual al segundo.
Cada test contiene uno de los assets vistos en la tabla o incluo más de uno, la lógica del testing unitario
es probar una sola cosa a la vez lo que suele traducirse a hacer sólo 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 método
Corriendo tests
Todos estos tests se correran 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 definidos, obtendremos:
1 # Running:
2
3 Finished in 0.007509s, 0.0000 runs/s, 0.0000 assertions/s.
¿de donde sacamos el valor de user?, la respuesta es de los fixtures, para poder utilizar un fixture en un
test lo hacemos de la siguiente forma:
El método setup permite crear un método comun para todos los otros que se carga antes que cualquier
otra cosa, y sirve casi exclusivamente para cargar los fixtures, si todos los metodos cargan fixtures
distintos no tiene sentido, pero en muchos casos un fixture se comparte en todos o casi todos los
métodos, en ese caso es muy útil para evitar repetir código.
En la línea 5 se ve que escribimos @user en lugar de user, debemos ocupar variables de instancias, si
fueran locales no podriamos compartirlas entre los distintos métodos.
Preguntas
1. ¿Por qué los tests los debemos escribir con la lógica de fallar?
2. ¿Qué son los fixtures?
3. ¿En qué consisten los tests unitarios?
4. ¿En qué entorno corren los tests?
5. ¿Cómo utilizamos un fixture dentro de un test?
6. ¿Cómo podemos correr los tests?
7. ¿Cuál es la diferencia entre escribir `def test_insert` y test “insert” do?
8. ¿Qué significa assert?
9. ¿Cuántos assert pueden haber en un test?
10. ¿Para qué sirve el método valid?
11. ¿Cuál es la diferencia entre assert assert_equal?
12. ¿Cómo se corren los tests?
13. ¿Cómo se cargan los fixtures en un test?
14. ¿Para qué sirve el método setup en los tests?
15. ¿Por qué el método setup se ocupan variables de isntancias en lugar de
locales?
20) Creando un Blog
Construyendo el modelo
En este capítulo y en los próximos construiremos un blog, por ahora utilizaremos como base el modelo
que hemos generado en capítulos anteriores del blog para realizar los tests del modelo.
Corremos rake db:migrate para crear el archivo schema y para probar si tenemos algún conflicto con la
configuración de la base de datos
1 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 rails g model user name email role
2 rails g model post title content user:references
3 rails g model comment content user:references post:references
Para evitar errores de cualquier tipo primero tenemos que partir de fixtures válidos 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 one:
2 name: MyString
3 email: MyString
4 role: MyString
5
6 two:
7 name: MyString
8 email: MyString
9 role: MyString
1 require 'test_helper'
2
3 class UserTest < ActiveSupport::TestCase
4 test "user should have an email" do
5 user = users(:one)
6 user.email = nil #decimos que no tiene email
7 assert_not user.valid?, "el usuario no puede no tener email"
8 end
9
10 end
1) Failure:
UserTest#test_user_should_have_an_email [/Users/gonzalosanchez/Proyectos/clases/bo
otcampv4/modelo/blog_with_testing/test/models/user_test.rb:10]:
Expected true to be nil or false
Además a los métodos assert les podemos pasar un parámetro más con el mensaje.
UserTest#test_user_should_have_an_email [/Users/gonzalosanchez/Proyectos/clases/bo
otcampv4/modelo/blog_with_testing/test/models/user_test.rb:10]:
user must have an email
Ahora, nosotros hicimos un test que dice que un usuario no debería ser valido si el email es nulo,
entonces al fallar nos está diciendo que hay usuarios con email nulo, por lo tanto nuestro código no lo
está validando, y eso es obvio porque no lo hemos implementado.
¿Cómo lo arreglamos?
Agregando una validación de presencia en el model de usuario al campo email, entonces en el modelo de
usuario:
Es normal que los test fallen, que fallen significa que una funcionalidad no está implementada o está mal
implementada, y eso es genial porque nos permite encontrar problemas en nuestro código y además nos
permite ordenar nuestro trabajo.
A esta filosofía de testear primero y codear después se llama Test Driven Develpment o TDD
¿Por qué en esta ocasión no ocupamos un fixture?, nuevamente, el secreto para sobrevivir al testing es
que todos los fixtures deben ser válidos porque estos se cargan directamente a la base de datos, y si uno
de ellos parte rompiendo un regla ya tendremos probemas.
Entonces como probamos que esté repetido?, creamos uno nuevo (no lo guardamos todavía) y
verificamos si con los datos que tiene es válido.
Para probar una relación necesitamos dos fixtures, uno para cada elemento de la relación, 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 podríamos preguntarnos si dentro de todos los posts del usuario se
encuentra el fixture, para eso probaremos con el método assert_includes
1 one:
2 title: MyString
3 content: MyString
4 user_id: 1
5
6 two:
7 title: MyString
8 content: MyString
9 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.
Al correr los test obtendres un error en lugar de un failure, los failures suceden cuando el assert no
cumple la espectativa, los errores por diversos motivos, pero en este caso es porque el usuario no tiene el
método posts
UserTest#test_user_has_posts:
NoMethodError: undefined method `posts' for #
test/models/user_test.rb:19:in `block in '
¿Cómo lo arreglamos?, agregando el método post a usuarios, por ahora lo haremos manualmente sin
agregar la relación para demostrar el failure, y devolveremos un arreglo vacío ya que assert_includes
espera como primer parámetro una colección de datos y un array vacío cumple con eso.
model user.rb
Expected [] to include #.
Claro, nosotros esperamos que el usuario tenga dentro del arreglo un post, y un arreglo vacío no tiene
nada adentro, ahora borraremos el método posts y montaremos la relación para arreglarlo.
Running:
...
Test de pertenencia
Ahora queremos hacer el test inverso, o sea asegurarnos que un post le pertenece a un usuario, si existe
el método 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 método, para eso dentro de los posts escribiremos el test.
Validando que un post no se pueda actualizar después de 5 minutos. Primero haremos el test, para eso lo
primero que necesitamos es revisar que el fixture de post incluye la fecha en que fue guardado, como nos
interesa que no se pueda guardar después de 5 minutos crearemos 2, uno hace 6 minutos que no
podemos actualizar y uno de hace 4 minutos que si podremos.
1 require 'test_helper'
2
3 class PostTest < ActiveSupport::TestCase
4 # test "the truth" do
5 # assert true
6 # end
7 test "cant update after 5 minutes" do
8 @post = posts(:one)
9 assert_not @post.valid?
10 end
11
12 test "can update before 5 minutes" do
13 @post = posts(:two)
14 assert @post.valid?
15 end
16
17 end
Al correr los tests veremos que sólo uno de ellos fala, esto se debe a que no existe ninguna validación,
por lo que al intentar guardar el post después de 5 minutos funciona y no debería funcionar.
Ahora tenemos que crear la validación, como no existe una para tiempos tendremos que crear una
custom como aprendimos previamente en el libro.
Desafios
En un modelo de datos donde hay usuario y cada usuario puede tener hasta 6 fotos, se pide.
En un modelo de datos donde hay articulos, item pedido, orden de compra y usuarios, donde un una
orden de compra se relaciones con muchos artículos y viceversa y una orden de compra le pertenece a
un usuario. se pide:
Preguntas
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 levamantos corriendo el comando rails s , es por eso que necesita estar andando para que
podamos entrar a la página localhost:3000.
Cuando el servidor de ruby on rails detecta una conexión, las cuáles desde ahora en adelante llamaremos
request lo que hace es verificar el archivo de rutas, este le indica que controller debe resolver el request y
con que método, dentro de este a veces se hacen llamados al modelo (y a veces no) y luego se mostra la
vista.
La ventaja de esta separación en tres capas es que hace fácil la revisión de un código 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 diseñador es fácil saber cual archivos es, se
revisa la URL, luego se revisa a que controller y que método redirige y luego busca el archivo dentro
de views con el mismo nombre dentro del método.
2. Si hay una falla dentro de una página 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 lógica de negocios está en el modelo, si una regla del negocio sólo tendremos que actualizar
este archivo, en caso de que agreguemos un campo o saquemos uno tendremos que revisar la vista
pertintente pero sólo eso.
Arquitectura REST
Rails es un framework MVC, pero hay otro concepto de desarrollo embebido en la lógica de la
construcción de proyectos, y es el de REST.
La idea de REST es definir recursos independientes para construir una aplicación, cada uno de estos
recursos tiene 7 métodos primarios.
La forma más fácil de empezar a trabajar con REST en Rails es con el generador de scaffold.
Scaffold
En rails existe una forma de crear todos los métodos rest para el recurso que queramos, es más el
scaffold crea:
1. El modelo (y cada vez que se crea un modelo viene acompañado de la migración para crear la table
en la base de datos)
2. El controller con los métodos REST
3. Las rutas para cada uno de los métodos REST.
4. Las vistas necesaria para cada uno de los métodos rest
5. Tests y fixtures básicos
6. Un archivo SCSS para ingresar sass (una variante de CSS)
7. Un archivo coffeescript (una variante de Javascript)
8. Un archivo de helper para delegar la lógica del controller
Para probarlo creemos un proyecto nuevo, donde haremos una una lista de tareas.
El último archivo creado scaffolds.scss es una base para mostrar los errores de los formularios y otros
detalles menores, si no es de tu agrado puedes borrarlo o modificarlo.
¿Cómo lo probamos?
Primero tenemos que saber cuales son las rutas, eso lo podemos lograr con rake routes
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
Esto se debe a que cada vez a que rails detecta que hay una migración que aún no ha sido corrida, pero
resolverlo simplemente debemos correr el comando rake db:migrate en el terminal.
1 rake db:migrate
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 página es lo compararemos con el resultado de rake routes, de esta forma
sabremos que task/new utiliza el controller tasks con el método new, entonces para saber que acción
se realiza revisaremos el controller y método respectivo.
Si observamos el método veremos que no hay mucha lógica, solo se crea un objeto task vacío que se
ocupará para guardar el tasks
1 <h1>New Task</h1>
2
3 <%= render 'form' %>
4
5 <%= link_to 'Back', tasks_path %>
Render es una instrucción que no habíamos visto en los controllers, pero no en las vistas aunque tiene el
mismo propósito, cargar otro vista, cuando de una vista se carga otra se le denomina vista parcial, para
señalizar que un archivo es una vista parcial se utiliza como prefijo un _, como es el ejemplo del archivo
app/views/tasks/_form.html.erb.
Lo primero que vemos es el helper form_for, es similar al form_tag sólo que este es capaz de determinar
las rutas para guardar o actualizar a partir de un objeto, en cambio en form_tag hay que especificarlas
como lo hicimos en el capítulo previo.
En caso de que al querer guardar un objeto haya un error lo mostraremos, de eso se encargan las líneas 2
a la 12. y entre las líneas 14 a 17 se crea el field task y de la 18 a la 20 el botón de envío.
la ruta del formulario se calcula con el objeto, si el objeto task es nuevo apuntará al metodo create si es
antiguo a update, revisemos el método create.
1 # POST /tasks
2 # POST /tasks.json
3 def create
4 @task = Task.new(task_params)
5
6 respond_to do |format|
7 if @task.save
8 format.html { redirect_to @task, notice: 'Task was successfully created.'
9 format.json { render :show, status: :created, location: @task }
10 else
11 format.html { render :new }
12 format.json { render json: @task.errors, status: :unprocessable_entity }
13 end
14 end
15 end
El método create parte con algo muy interesante Task.new(task_params) esta es la clave para
entender los form_for todos los datos del formulario se envían en un hash, esto lo podemos ver en los
logs de rails server
Los parameters son un hash, este hash ya lo hemos ocupado antes cuando dentro de un método del
controller utiizábmaos 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 modificados.
Utilizando la misma idea del capítulo anterior podemos mostrar el hash en lugar de
1 def create
2 render json: params
3 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 def create
2 render json: params[:task]
3 end
Strong parameters
Veremos en las últimas líneas del task controller el siguiente código.
1 # Never trust parameters from the scary internet, only allow the white list through.
2 def task_params
3 params.require(:task).permit(:task)
4 end
Ahí se define un método task_params, donde dice que del key task solo permiteremos un key dentro, en
este caso se le llama task, pero si hubiese otro, por ejemplo el id de un usuario podríamos obtenerlo
agregándolo a la lista.
1 def task_params
2 params.require(:task).permit(:task, :user_id)
3 end
Todo lo que no esté en esa lista será declarado un parámetro 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
migración que agregue el campo a la base de datos. Esto se hace así:
invoke active_record
create db/migrate/20150819042641_add_user_to_task.rb
Si el archivo es igual al mostrado entonces procedemos a correr la migración, ahí es cuando realmente se
modifica la base de datos.
1 rake db:migrate
Las migraciones son el medio por el cual modificamos la base de datos, jamás deberíamos modificar la
base de datos sin una migración, como queremos agregar usuarios responsables a la tarea haremos justo
eso, crear una migración que agregue un usuario a la tarea, donde el usuario es un string.
Discutiremos más profundamente el tema de las migraciones en un próximo capítulo, si te interesa tener
más información puedes revisar la documantación oficial de migraciones
Probando los strong params
Ahora tenemos un nuevo campo user, vamos a probar los strong params agregando el campo user al
formulario pero sin agregarlo a los strong params. Para eso abriremos el archivo
app/views/tasks/_form.html.erb y dentro agregaremos el nuevo field.
1 rails c
1 Task.last
Task Load (0.7ms) SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."id" DES
C LIMIT 1
=> #
2.2.2 :002 >
La razón del por qué user es nil, la podemos encontrar en los logs de rails server (en el tab secuestrado)
Started POST "/tasks" for 127.0.0.1 at 2015-08-18 23:43:40 -0500
Processing by TasksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"iGFUq2LShLo5NZV+Cjd5dmtnnz4
8MbSderUtyRDdAeCxgi0gN4xyL4kVxolMVb0Hy0sIZz3YMDAxVf4kVYQYew==", "task"=>{"task"=>"
Tarea 1", "user"=>"Gonzalo"}, "commit"=>"Create Task"}
Unpermitted parameter: user
(0.2ms) begin transaction
SQL (1.6ms) INSERT INTO "tasks" ("task", "created_at", "updated_at") VALUES
(?, ?, ?) [["task", "Tarea 1"], ["created_at", "2015-08-19 04:43:40.411793"], ["
updated_at", "2015-08-19 04:43:40.411793"]]
(0.8ms) commit transaction
Redirected to http://localhost:3000/tasks/1
Completed 302 Found in 30ms (ActiveRecord: 2.6ms)
Podemos ver una línea que dice Unpermitted parameter: user, y la razón es sencilla, user no está dentro
de los strong parameters, dentro del controller de tasks tendremos que agregar:
1 params.require(:task).permit(:task, :user)
Preguntas
El archivo routes.rb
El archivo routes.rb que se encuentra dentro de la carpeta config contiene todas las URL que serán
accesibles por los usuarios del sistema, si la URL no está especificada aquí entonces simplemente no
será accesible.
El archivo routes se encarga de mapear la ruta con un controller y un método dentro de ese controller, de
esta forma cada URL corresponde a una acción que se encuentra definida dentro de un método de
instancia del controller especificado.
Es posible también que el nombre de la ruta no corresponda con el nombre del método, o omitir el
nombre del controller, eso lo veremos más adelante.
Otra cosa que puede hacer el archivo routes es pasar parámetros, los parámetros en la url son unas
especies de variables, de esa forma el programador puedes especificar la página
/controller_x/pag/20 y eso llamar al método pag asigando el valor 20 a una variable.
Rake routes
rake es un programa de administración 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 más detalles de Rake routes
después de crear nuestra primera ruta sin parámetros.
El caso más común para crear rutas es el de sin parámetros, estas son útiles cuando queremos páginas
estáticas o incluso cuando queremos obtener todos los elementos de un recurso, por ejemplo todos los
post, otro caso útil por ejemplo es el perfil del usuario conectado actualmente, por otra parte obtener el
perfil de un usuario específico requeriría el id de ese usuario.
Las rutas sin parámetros pueden ser con controller (la forma más habitual) y sin el controller, no tiene
ventajas omitir el controller pero hay personas que lo prefieren por estetica de la URL
Ruta con controller
ejemplo: localhost:3000/controller\_x/about\_us
Para crear una ruta especificando el controller debemos agregar al archivo routes una nueva dirección de
la siguiente forma: get 'controller/accion' , como por ejemplos
1 get 'pages/about_us'
para probar que esto funcione en el terminal dentro del mismo proyecto escribiremos rake routes
1 rake routes
Prefix: El prefijo
El prefix muestra una variable que se creará para referirnos a esa página, es un prefijo porque hay dos
sufijos 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 dirección a esa página.
Verb, el verbo
Los request dentro de HTTP se hacen en conjunto con un verbo, en la mayoría de los casos este verbo es
GET, pero también 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 automáticamente estamos haciendo un GET, pero cuando enviamos un formulario lo más
probable es que estemos haciendo un POST.
Ahora, para que una ruta sea válida tienen que suceder dos cosas, la primera es que la url tiene que estar
habilitada, la segunda es que el verbo tiene que correponder a la url. En el caso anterior si intentamos
hacer un POST a localhost:3000/pages/about_us 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:
URI pattern
el URI pattern es la dirección URL en conjunto con el formato, ejemplo .html, .json, .xml, .js. Los
controllers en RAILS por defecto responden a múltiples formatos pero es posible limitarlos a uno o sólo
algunos.
URI Controller#Action
finalmente la última columna indica quien es el controller y el método responsable cuando se carga una
página web, esto es muy útil para debuggear errores pues nos indica en que parte del código esté
probablemente el problema.
Ahora que ya entendemos más sobre rake routes podemos proceder a ver aplicaicones interesantes del
archivo routes.
primero aclarar que una ruta sin controller no quiere decir que no exista un controller, es sólo que no
aparece en la URL.
Claramente para que esta página funcione tiene que existir el controller pages con un método llamado
about_us y la respectiva vista.
Cambiando el prefijo
Es posible cambiar el prefix de una url utilizando el símbolo :as cuando definimos la ruta, ejemplo:
No es conveniente cambiar el nombre del prefix arbitrariamente, pero en algunos casos rails no será
capaz de distinguir el prefix de forma automática y en esos casos lo necesitaremos.
Para crear una ruta con parámetros tenemos que hacer dos cosas, la primera es especificar que la ruta
tiene un parámetro y nombrarlo, esto lo hacemos así: get 'user/:id' la segunda parte consiste en
especificar el controller como lo hicimos previamente, ya que la ruta no tiene la misma estructura que
antes del tipo controller/pages ahora hay que especificar el controller, uniendo quedaría así:
1 get 'user/:id' => 'users#profile'
al realizar rake routes nos daremos cuenta que en esta ocasión el prefix desapareció, como no sabía cuál
era el controller y el método, menos aún tiene la capacidad de autodeterminar un prefijo, entonces
recurrimos a especificarlo con el símbolo :as
Dentro de las vistas y controllers de rails ahora debmeos pasar el parámetro id, esto es sencillo, en lugar
de utilar profile_path utilizaremos profile_path(id: 5)
Para utilizar este parámetro dentro del controller o dentro de una vista podemos utilizar el hash params de
la siguiente forma
1 params[:id]
Luego este id lo podemos utilizar para buscar registros en nuestra base de datos o para cualquier función
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
específico, pero la ruta user/:id ya la estamos ocupando para mostrar el perfil, podríamos crear otra
que diga matches/:id pero luego matches también apuntaría al controller de user y eso ya empieza
a causar confusión, entonces una forma de solucionarlo de forma que quede limpio es anidando la ruta
para que resulte en user/:id/matches
Los parámetros dentro de las rutas tienen nombres, por ejemplo en el caso anterior estabamos
nombrando a la variable :id, pero ¿qué sucede si queremos utilizar una segundo parámetro, por ejemplo
para ver el detalle del match de un usuario en específico. Eso lo podemos implementar de la siguiente
forma:
Las rutas anidadas y formularios anidados son muy útiles a la hora de construir aplicaciones,
ahondaremos en más detalle en un próximo capítulo.
Query strings
Los querys strings son los parámetros libres que recive una dirección, estos parámetros no se declaran
en el archivo routes.rb, si no que los pasa el usuario cuando realiza un request.
Todo lo que está a continuación del caracter ? en la url son query strings, seguramente los has visto
cuando haces búsquedas en google.
Entonces al pasar una ruta al navegador del tipo localhost:3000/users?sort=ok podemos rescatar tanto el
valor ok simplemente utilizando params[:sort] desde el controller o desde la vista, en este caso
nos devolvería ok
¿Para qué sirven los query strings? principalmente cuando una página puede realizar otras acciones, que
no son críticas en los datos, como por ejemplo paginación, ordenamineto, etc o búsquedas como lo hace
google.
Resources
Hasta el momento hemos trabajado todas las rutas individualmente, pero hay forma de crear rutas REST
de forma automática para un recurso, para eso basta con especificar dentro del archivo routes:
1 resources :nombre_recurso
Prefix Verb URI Pattern Controller#Action
En este caso cuando el prefijo no sale mencionado es porque es el mimo que el anterior.
Es posible además limitar las rutas rest generada utilizando los símbolo :only o :except, en el primer caso
las rutas quedan limitadas a las específicadas, y en el segundo son todas menos las especificadas.
Ejemplo:
member vs collection
Además 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 específico del recurso, por ejemplo a las
rutas rest de user podríamos agregar el método profile de la siguiente forma:
Fuera de las rutas de user que vimos previamente, obtendremos una nueva la de profile.
Los bloques member hacen más sencillo el agregar rutas nuevas sin tener que especificar con :as y
establecer que controller y que método, pues es el método respectivo del recurso.
Collection
Los bloques de collections son muy similares a los de members, pero con una diferencia, el collection no
tiene un parámetro :id involucrado, pues se utiliza principalmente para generar una acción que involucra a
todos los recursos, o a uno independiente del id.
Obtendríamos:
Prefix Verb URI Pattern Controller#Action
Para entender bien la idea detrás de las rutas anidadas debemos reapasar el propósito de la arquitectura
REST, esta sirve para normalizar la manipulación de recursos, o sea utilizar las misma estructura de URL
ya sea para verlos, crearlos, modificarlos 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 asi:
1 resources :posts do
2 resources :comments
3 end
Prefix Verb URI Pattern Controller#Action
Los bloques collection y member tamibén aplican a rutas anidadas, si hay que tener el cuidado de
escoger el recurso completo, por ejemplo si quisieramos agregar un método para votar un post
deberíamos escribir:
1 resources :posts do
2 member do
3 get 'vote'
4 end
5 resources :comments
6 end
Finalmente si queremos que ambos tengan un método para votos, simplemente combinamos ambos
métodos.
1 resources :posts do
2 member do
3 get 'vote'
4 end
5 resources :comments do
6 member do
7 get 'vote'
8 end
9 end
10 end
En el caso de los usuarios lo podemos crear como scaffold o no, pero sólo vamos a utilizar el index y el
show, ya que nos enfocaremos en el recurso anidado, el resto se vio en capítulos anteriores.
Antes de siqueira generar el método index para los tweets vamos a comenzar creando los tests.
Pero las rutas anidadas tienen una subruta adentro, o sea el index de los tweets depende de un usuario
específico /users/2/tweets entonces debemos testear asi:
Para evitar errores también debemos agregar id: 1 dentro del fixture de users.
Ahora en ambos casos (aunque sólo el segundo es el correcto) obtendremos el siguiente error:
1) Error:
TweetsControllerTest#test_should_get_index:
ActionController::UrlGenerationError: No route matches {:action=>"index", :control
ler=>"tweets"}
test/controllers/tweets_controller_test.rb:8:in `block in '
Agregamos la ruta:
1 resources :users do
2 resources :tweets
3 end
Corremos los tests nuevamente, obviamente fallará, puesto que todavía no tenemos el método en el
controller. Observemos el error:
1) Error:
TweetsControllerTest#test_should_get_index:
AbstractController::ActionNotFound: The action 'index' could not be found for Twee
tsController
test/controllers/tweets_controller_test.rb:8:in `block in '
Agreguemos el método index al tweet controller, y de paso agreguemos la vista para evitar errores que ya
hemos estudiado.
1 def index
2 end
No habríamos tenido que pasar por esto si hubiésemos 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) Error:
TweetsControllerTest#test_should_get_index:
ActionController::UrlGenerationError: No route matches {:action=>"index", :control
ler=>"tweets"}
test/controllers/tweets_controller_test.rb:8:in `block in '
Esto se debe a que la ruta que nosotros creamos es anidada, por lo tanto debemos testear contra una
ruta anidada.
Para que funcione tenemos que haber establecido las relaciones belongsto y hasmany en el modelo.
1 def index
2 @user = User.find params[:user_id]
3 @tweets = @user.tweets
4 end
y luego dentro de la vista mostramos los tweets con:
Es necesario obtener el objeto user?, no, otra opción de hacer lo mismo sería.
Pero es limpio, elegante y después nos servirá para hacer los redireccionamientos, además debemos
recordar que los controllers deben tener la menor cantidad de lógica posible, y esto incluye evitar hacer
consultas a la base de datos directamente.
la ruta ya está agregada, agreagmos todas las rutas REST, ahora en el controller agregamos el método
show.
1 def show
2 @tweet = Tweet.find(params[:id])
3 end
1 def new
2 @user = User.find params[:user_id]
3 @tweet = @user.tweets.build
4 end
La forma que creamos el tweet en el paso anterior puede parecer rara, de hecho es muy común, y es lo
mismo que @tweet = Tweet.new (bueno, casi lo mismo, en ciertas ocasiones, como cuando ocupemos
relaciones polimórficas, veremos la útilidad de ocupar el build)
Recordemos que form_for determina automáticamente las rutas para el objeto, pero en este caso las
rutas de tweet están anidadas dentro de user, para lograr que form_for construya las rutas
automáticamente con recursos anidados tenémos que pasarles los recursos en un arreglo. El orden de
los parámetros del arreglos es el mismo que el de anidamiento, de afuera hacia dentro, o sea si es del
tipo users/2/tweets el orden sería [@user, @tweet]
1 def create
2 @tweet = Tweet.new(tweet_params)
3 @tweet.save
4 render nothing: true
5 end
No existe un lugar específico a donde debamos redireccionar, depende de los requisitos de la plataforma,
pero un lugar muy común, es al show del objeto padre, en este caso user.
1 def create
2 @user = User.find params[:user_id]
3 @tweet = Tweet.new(tweet_params)
4 @tweet.user = @user
5 @tweet.save
6 redirect_to @user
7 end
1 @tweet = @user.tweets.build(tweet_params)
Se deja de tarea crear los métodos de edit y update que son iguales al de new y create. y el de delete que
no presenta mayor dificultad que el show.
22) Relaciones n a n
Tenemos una relación de n a n cuando un elemento de una tabla está relacionados con n elementos de la
otra y viceversa.
Una pelicúla x puede tener el tag acción, y el tag suspenso, y luego el tag suspenso puede a su vez ser
de la película x y de la película y.
En bases de datos relacionlaes no es posible modelar directamente una relación de muchos a muchos,
pero si es posible hacerlo ocupando una tabla intermedia.
Entonces siempre que queramos implementar una relación de muchos a muchos en bases de datos
relacionales necesitaremos 3 tablas.
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.
Implementando relaciones
has_and_belongs_to_many
Vamos a empezar con un proyecto nuevo, en el vamos a crear dos modelos, el de películas (movies) y el
de los generos.
luego creamos la tabla intermedia, para eso podemos crear una migración y luego crear la tabla
manualmente dentro o, podemos utilizar una ayuda de rails para crear la migración que cree la tabla
automáticamente.
para eso:
En muchas ocaciones la tabla intermedia no tiene un id como clave primario, puesto que no es necesaria,
además esta tabla nunca la accederemos directamente, siempre buscaremos por movie, o por genero.
Con las tablas hechas y los modelos creados ahora procedemos a establecer las relaciones.
Podemos probar las relaciones desde la consola de rails Movie.new.genres nos debería devolver
un colección vacía, y Genre.new.movies también.
Hagamos otra prueba para explicar como agregar generos a las películas.
Observar que se generó una transacción donde se guardan tres cosas simultáneamente, la película, el
genero, y la asociación.
Borrando la asociación:
¿Cómo podemos borrar la asociación?
Para borrar debemos tener el objeto que queremos borrar, por ejemplo
1 genre = Genre.first
2 movie = Movie.first
3 movie.genres.delete(genre)
Se debe tener el cuidado de no hacer movie.genres[0].delete, pues en ese caso se borraría el tag pero no
la asociación, esto en SQLite es posible pero en PostgreSQL generaría un error.
Testeando la relación
Cómo todo sucende dentro de una transacción podemos probarlo con un simple assert, también
podríamos contar los resultados.
A continuación veremos como hacer la relación con has_many through usando el mismo ejemplo de
películas y genero
Y ahora crearemos un modelo que los una a los otros dos, con:
Revisamos las relaciones de movie_genre, (deberían haberse agregado solas gracias al generador de
ruby)
Desde usuario:
1 Movie.new.movie_genres # ó
2 Movie.new.genres
Desde car:
1 Genre.new.movie_genres # ó
2 Genre.new.movies
Al hacerlo obtendremos colecciones vacías, ya que una película nueva no tiene géneros 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 movie = Movie.new(name:"Avengers")
2 genre = Genre.new(name:"Action")
3 movie.genres << genre
4 movie.save
Se puede observar que la utilización es la misma y el resultado también, independiente del método
utilizado, entonces ¿Cuándo conviene utilizar uno o el otro?
EL principal motivo para utilizar has_many through es cuando se requiere agregar negocio al modelo
intermedio, tanto en el caso de las películas como en el de los vehículos no había una necesidad real,
pero imaginemos por un momento que quisiéramos manejar el porcentaje de películas pertenecientes a
un genero o necesitar validaciones para esa tabla, entonces en ese caso ya convendría trabajar con
has_many through.
Primero agregaremos películas y géneros a nuestra base de datos, para eso modificaremos nuestro
archivo seed
1 genres = []
2
3 genres << Genre.create(name: 'Action')
4 genres << Genre.create(name: 'Comedy')
5 genres << Genre.create(name: 'ScyFy')
6 genres << Genre.create(name: 'Horror')
7 genres << Genre.create(name: 'Drama')
8 genres << Genre.create(name: 'Adventure')
9 genres << Genre.create(name: 'Thriller')
10 genres << Genre.create(name: 'Documental')
11 genres << Genre.create(name: 'Musical')
12 genres << Genre.create(name: 'Animated')
13
14 movies = []
15
16 (1..50).each do |m|
17 movies << Movie.create(title: "Pelicula #{m}")
18 end
Luego crearemos el método index y show de películas, para eso primero crearemos el controller con el
método index y show desde el generador con:
Luego dentro del método show del controller de movies, obtendremos la película y los generos,
necesitamos ambas para crear nuestro formulario de accesos
y ahora creamos un formulario sencillo, que apunte a la misma página, para probarlo.
1 <h2>Generos</h2>
2 <%= form_tag @movie, method: :get do %>
3 <table>
4 <thead>
5 <tr>
6 <th>Genero</th>
7 <th>Selecionar</th>
8 </tr>
9 </thead>
10 <tbody>
11 <% @genres.each do |genre| %>
12 <% if @movie_genres.include? genre %>
13 <tr>
14 <td><%= genre.name %></td>
15 <td><%= check_box_tag 'genres_ids[]', genre.id, 'Yes' %></
16 </tr>
17 <% else %>
18 <tr>
19 <td><%= genre.name %></td>
20 <td><%= check_box_tag 'genres_ids[]', genre.id %></td>
21 </tr>
22 <% end %>
23 <% end %>
24 </tbody>
25 </table>
26 <%= submit_tag 'Guardar' %>
27 <% end %>
El checkbox por defecto recibe el nombre de un atributo, y eso nos permite pasar un campo como true o
false. Aviso: cuando el checkbox no está marcado no se envía, que definitivamente no es lo mismo a que
se envíe con el valor cero.
Pero en este caso nosotros queremos pasar un grupo de géneros asociados a esta película, para eso
ocupamos el arreglo.
Cuando un elemento del arreglo esta marcado se agregará a la lista con su id respectivo.
Ahí vemos que efectivamente los ids están en un arreglo, ahora simplemente hay que buscar esos
géneros y asignarlos a la película, pero para eso agregaremos una ruta nueva, set_genre
Antes tenemos que preguntarnos en que controller tiene que ir este método. ¿En uno nuevo?, ¿En el de
géneros?, ¿En el de las películas?, en este caso lo que queremos hacer es asignar géneros a una película
por lo que tenemos que trabajar sobre el controller de películas, movies. Y no solo necesitamos una ruta
que nos lleve al método, sino que también envíe el id de la película actual para poder trabajar en ella, para
eso hacemos uso de una ruta de tipo member en movies
1 resources :movies do
2 member do
3 post 'set_genre'
4 end
5 end
Tenemos que pasar la película (@movie) como opción a la ruta del formulario porque se tiene que enviar el
id de esta.
1 def set_genre
2 if params.key?(:genres_ids) && !params[:genres_ids].empty?
3 @genres = Genre.find(params[:genres_ids])
4 @movie.genres = @genres
5 else
6 @movie.genres.clear
7 end
8
9 redirect_to @movie
10 end
Lo primero que hacemos comprobar que el parámetro que vamos a usar, en este caso :genres_ids ,
existe y no esta vacío, después buscamos los géneros y las guardamos en un arreglo, y por ultimo le
asignamos los géneros al usuario, en este paso se eliminan los géneros que ya se desmarcaron al usuario
y se asignan las que se marcaron, en el caso de ya estar asignadas no hace nada. Si el parámetro no
existe o esta vacío, borramos todas los géneros asignados a la película.
Lo ultimo que haremos sera crear una validación en el modelo moviegenres para que la combinación
entre movieid y genre_id sea única y así evitar entradas duplicadas
Podemos complicarlo aún más, y crear un nuevo formulario dentro de index, pero aquí para dar acceso a
todos los usuarios simultáneamente.
1 def index
2 @users = User.all
3 @sections = Section.all
4 end
y luego haremos nuestro primer intento de vista, para eso dentro de la vista views/users/index.html
crearemos una tabla donde en la cabecera estén los usuarios y en el eje de las columnas la sección, la
intersección de ambas será el acceso.
1 <table>
2 <caption>Accesos</caption>
3 <thead>
4 <tr>
5 <th> Sección </th>
6 <% @users.each do |u| %>
7 <th><%= u.name %></th>
8 <% end %>
9 </tr>
10 </thead>
11 <tbody>
12 <% @sections.each do |s| %>
13 <tr>
14 <td> <%= s.name %> </td>
15 <% @users.each do |u| %>
16 <td><%= u.name %></td>
17 <% end %>
18 </tr>
19 <% end %>
20 </tbody>
21 </table>
Ahora el siguiente paso, cambiemos el nombre repetido en cada fila por un checkbox, ese checkbox
deberá guardar información del usuario y de la sección.
1 <%= form_tag users_path, method: :get %>
2 <table>
3 <caption>Accesos</caption>
4 <thead>
5 <tr>
6 <th> Sección </th>
7 <% @users.each do |u| %>
8 <th><%= u.name %></th>
9 <% end %>
10 </tr>
11 </thead>
12 <tbody>
13 <% @sections.each do |s| %>
14 <tr>
15 <td> <%= s.name %> </td>
16 <% @users.each do |u| %>
17 <td><%= check_box_tag "user_sections_ids[#{u.id}][#{s.id}]" %></td
18 <% end %>
19 </tr>
20 <% end %>
21 </tbody>
22 </table>
23
24 <%= submit_tag "enviar" %>
Se vería así:
Aquí todo es bastante parecido excepto 1 cosa, el checkbox, analicemos esto:
En el caso anterior teníamos un sólo usuario, y estabamos enviando las secciones asociadas a ese
usuario dentro de un arreglo, ahora tenemos un arreglo de usuarios, dentro de cada uno de ellos hay un
arreglo con las secciones.
1 Parameters: {"utf8"=>"✓",
2 "user_sections_ids"=>{"5"=>{"1"=>"1", "9"=>"1"}, "7"=>{"7"=>"1"}},
3 "commit"=>"enviar"}
O sea recibimos un hash con los usuarios que recibieron accesos, en este ejemplo aquellos con id 5 y 7 y
cada uno de estos apunta un hash con las secciones a las cuales recibieron acceso, el 5 recibió acceso a
la 1 y a la 8, el 7 sólo a la 1.
Para procesar esto ocuparemos un método distinto, como es para todos los usuarios usaremos un
bloque collection.
1 resources :users do
2 member do
3 post 'give_access'
4 end
5 collection do
6 post 'give_accesses'
7 end
8 end
y el nuevo prefix obtenido con rake routes sería: give_accesses_users, por lo que el formulario de
index debería quedar:
1 def give_accesses
2 end
Dentro del controller lo importante es primero rescatar los usuarios que reciben los permisos.
1 def give_accesses
2 users_and_sections = params[:user_sections_id]
3 users_and_sections.each do |u|
4 # user_hash contiene el user_id y el hash con las secciones
5 user = User.find(u[0])
6 sections = Section.find(u[1].keys)
7 user.sections << sections
8 user.save
9 end
10 redirect_to users_path
11 end
24) Haciendo un cloudtag
En este tutorial 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.
Las relaciones dentro del modelo tagpost se crearon automáticamente, ya que utilizamos el generador de
rails con references.
Dentro del controller de tags agruparemos los tags por término y los contaremos.
1 class TagsController < ApplicationController
2 def index
3 tags = Tag.group(:tag).count
4 @tags = tags.collect {|x,y| {text: x, weight: y}}
5 end
6 end
Vista de tags#index
1 <div class="keywords">
2 </div>
3
4 <script>
5 tags = <%= (@tags.to_json.html_safe) %>
6 console.log(tags)
7 $('.keywords').jQCloud(tags, {
8 width: 500,
9 height: 350
10 });
11 </script>
25) Devise
La autenticación es el proceso de validación de un usuario en un sistema, es perfectamente posible
programar uno desde cero, pero como en la mayoría 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 autenticación más famoso se llama Devise, y es el que aprenderemos a
ocupar en este capítulo, razones para ocuparlo:
1 gem 'devise'
1 bundle
2 rails generate devise:install
Y ahora debemos cumplir con estos 5 puntos: El 1er 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 página 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
cuál página se use, por lo mismo estos mensajes se ponen en la página 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 contraseñas y muchas otras, si omitimos
este paso igual se van a mostrar estas vistas pero nosotros no las podremos generar.
invoke Devise::Generators::SharedViewsGenerator
create app/views/devise/shared
create app/views/devise/shared/_links.html.erb
invoke form_for
create app/views/devise/confirmations
create app/views/devise/confirmations/new.html.erb
create app/views/devise/passwords
create app/views/devise/passwords/edit.html.erb
create app/views/devise/passwords/new.html.erb
create app/views/devise/registrations
create app/views/devise/registrations/edit.html.erb
create app/views/devise/registrations/new.html.erb
create app/views/devise/sessions
create app/views/devise/sessions/new.html.erb
create app/views/devise/unlocks
create app/views/devise/unlocks/new.html.erb
invoke erb
create app/views/devise/mailer
create app/views/devise/mailer/confirmation_instructions.html.erb
create app/views/devise/mailer/reset_password_instructions.html.erb
create app/views/devise/mailer/unlock_instructions.html.erb
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 cuál nombre utilizaste.
invoke active_record
create db/migrate/20151015141123_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users
En resumen: se generó una migración, el modelo de usuario, test para el modelos de usuarios, los fixtures
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.
1 rake db:migrate
y con eso ya terminamos la configuración inicial, ahora para revisar el login y el formulario de registro
debemos entrar a
http://localhost:3000/users/sign_in http://localhost:3000/users/sign_up
recordar que si cambiamos el modelo de user la ruta también debe reflejar el nombre del modelo utilizado
en plurar.
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, ¿cómo
podemos ver todas la acciones?, con rake routes , a continuación ingreso las tres rutas que nos
permitiran que un usuario se registre, ingrese y luego cierre la sesión.
Prefix Verb Url Controller#method
Para salir debemos ocupar la ruta respectiva, pero además debemos especificar el verbo delete
o en la forma de HTML
login or logout
Es muy común en un sitio web o aplicación web que no se muestre simultaneamente el link a ingresar y
registrar y el link a salir simultáneamente, normalmente se muestra ingresar y registrar (si no has
ingresado) y salir (si ya estas ingresado)
El objeto current_user
Cuando el usuario se registra o ingresa se inicia una sesión, las sesiones en rails son un hash que permite
identificar al usuario y guardar junto con el información 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, ¿cómo lo hace?
Gracias a esa definición, nosotros podemos mostrar información del usuario logeado, por ejemplo si
quisieramos mostrar el email sería:
1 rails g devise:views
Con las vistas generadas ahora podemos modificarlas utilizando HTML. Las vistas generadas se
encuentran en: views/devise/ ahí encontraremos varias carpetas, el login está dentro de
views/devise/sessions/new y 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.
Este fomulario de devise es compatible con múltiples recursos simultaneamente, o sea podemos tener
múltiples modelos de usuarios (obviamente con distintos nombres) y todos con devise.
Además la mayoría de las vistas de devise incluyen un render a la vista parcial de links
1 <div class="field">
2 <%= f.label :name %><br />
3 <%= f.text_field :name, autofocus: true %>
4 </div>
Al recargar la página 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:
El callback nos permite llamar al método configure_permitted_parameters si estamos dentro del controller
de devise, luego el método agrega el campo name a la lista de los parámetros sanitizados. Podriamos
agregar un segundo separandolo con una coma.
Bloqueando el acceso
Es muy común en un sitio web que un usuario no puede acceder a una página 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.
1 before_action :authenticate_user!
Ahora supongamos que tenemos un controller llamado pages, cuyo único objetivo es mostrar dos
páginas, una es home, la otra es secreto y queremos que la página secreto sea sólo para usuarios
logeados.
1 class PagesController < ApplicationController
2 def home
3 end
4
5 def secreto
6 end
7 end
Podríamos hacer:
Pero de esta forma limitaríamos todas las páginas y no solo home, para evitar esto podemos agregar
como parámetro al before_action los hashs :only o except de esta forma:
De esta forma sólo el método secreto de pages dependerá de que el usuario se haya logeado y si el
usuario intenta entrar a http://localhost:3000/pages/secreto sin estar logeado será
redirigido a la login.
1 get 'pages/home'
2 get 'pages/secreto'
Cuando se crea un sistema de login con Devise todo funciona perfecto salido de la caja excepto el
recuperar contraseñas, la razón es muy sencilla, esto se hace via email y para que rails pueda enviar un
email necesita tener un sender (enviador) configurado, este modulo en rails 4 recibe el nombre de Action
Mailer.
Configurando action_mailer para enviar correos con gmail para hacerlo basta abrir el archivo de
configuración config/application.rb (también es posible ocupar un initializer) y agregar las siguientes líneas
dentro del module y de class Application.
Donde dice ENV[‘email’] y password podemos cambiarlas por nuestras claves de email y al reiniciar la
aplicación ya estaría funcionando pero hay un problema grande con hacer eso, estaríamos dejando las
claves del correo electrónico dentro de nuestro código.
Protegiendo las claves con dot-env Dot-env es una gema que nos permite agregar variables de entorno
de forma sencilla a nuestra aplicación, para eso vamos agregar la siguiente gema al gemfile
gem 'dotenv-rails'
Luego tenemos que crear un archivo .env (si, el punto es parte del nombre) dentro de la raíz de nuestro
proyecto, en el vamos a agregar las variables de entorno.
1 email=tuemail@gmail.com
2 email_password=tuppassword
y ya con eso nuestra aplicación permite recuperar las contraseñas 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 sólo
aplica si están ocupando GIT)
/.env
Configurando Heroku para que acepte las variables de entorno Si ocupas Heroku te estarás 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:
y ahora si que si, tus claves están seguras y tu aplicación está funcionando con la opción de recuperar
contraseñas.
26) Devise avanzado
Ya hemos cubierto los tópicos básicos de devise, pero todavía hay varias funcionalidades interesante que
veremos en este capítulo.
Para lograr esto tenemos que agregar un campo rol al usuario para poder distinguirlo y 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 algún momento se
ingrese un rol no definido.
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 usuari en vez de usuario tienen el problema de que no es claro que hace
cada número, te obliga a revisar la documentación 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.
Para cambiar al primer usuario de la base de datos y darle acceso de admin haríamos
1 User.last.admin!
El problema que tenemos ahora es que los usuarios que ya existen en la base de datos quedan con el
campo role como nil y los nuevos usuarios también, a menos que le demos la opción de elegir el role que
tendrán, lo que es una muy mala idea ya que el administrador del sitio debería ser quien asigne los roles
en la aplicación.
Lo que debiera pasar es que cualquier usuario nuevo se cree con un role estándar, en este caso el de
user, para eso definiremos un valor por defecto al campo role y lo haremos a nivel de base de datos y de
modelo.
Una vez corrida la migración todos los usuarios en la base de datos que tenían role = nil ahora
tendrán el role por defecto, en este caso 2.
Ahora setearemos el defaul a nivel de modelo, para eso en el modelo de usuario user.rb añadimos lo
siguiente:
1 ...
2 before_save :default_role
3
4 def default_role
5 self.role ||= 2
6 end
7 ...
Ahora que ya tenemos roles pasaremos a verificar los accesos, para eso vamos al applicationController y
añadimos lo siguiente al final del archivo antes del ultimo end
1 private
2 def check_admin!
3 authenticate_user!
4 unless current_user.admin?
5 redirect_to root_path, alert: "No tienes acceso"
6 end
7 end
Luego en cada uno de los controladores donde queremos que el usuario logueado sea administrador
agregamos lo siguiente.
Esto es suficiente para sitios chicos donde hay que revisar 3 o 4 páginas contra uno o dos accesos, pero
si escalamos de esta forma nos vamos a ver creando muchos métodos en cada controller para revisar los
accesos, para evitar esto en le próximo capítulo estudiaremos la gema CanCanCan.
1 include Devise::TestHelpers
Luego creamo fixtures para los distintos tipos de usuario en el archivo test/fixtures/user.yml
1 admin:
2 id: 1
3 email: "admin@desafiolatam.com"
4 role: 0
5
6 editor:
7 id: 2
8 email: "editor@desafiolatam.com"
9 role: 1
10
11 user:
12 id: 3
13 email: "user@desafiolatam.com"
14 role: 2
Principalmente lo que vamos a testear en este punto es si un usuario tiene acceso a una página estando
logeado o no, o si tiene acceso a el método 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.
y luego probamos nuestro primer test, debería pasar, ahora si obtenemos un error del tipo:
ActionView::Template::Error:
undefined method `authenticate' for nil:NilClass
Ahora creemos un test para revisar si un usuario logeado puede entrar a la página home
1 test "logged user can get home" do
2 user = users(:user)
3 sign_in(user)
4 get :home
5 assert_response :success
6 end
Este test será más interesante, que pasa si un usuario no loggeado intenta entrar a una página que no
puede.
Y como probamos que un usuario con un rol específico no tenga acceso, eso lo hacemos con:
Como mencionamos previamente no es suficiente probar que no tenga acceso, también 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 algún momento llegamos a romper
algo por integrar una nueva funcionalidad podemos detectarlo sin tener que probar manualmente todas
las páginas una a una.
Códigos completos:
Tests
1 require 'test_helper'
2
3 class PagesControllerTest < ActionController::TestCase
4 include Devise::TestHelpers
5
6 test "unlogged user can get home" do
7 get :home
8 assert_response :success
9 end
10
11 test "logged user can get home" do
12 user = users(:user)
13 sign_in(user)
14 get :home
15 assert_response :success
16 end
17
18 test "logged user can't get secreto" do
19 get :secreto
20 assert_response :redirect
21 end
22
23 test "user without privileges can't get secreto" do
24 user = users(:user)
25 sign_in(user)
26 get :secreto
27 assert_response :redirect
28 end
29
30 test "admin can get secreto" do
31 admin = users(:admin)
32 sign_in(admin)
33 get :secreto
34 assert_response :success
35 end
36
37 end
Pages Controller
1 class PagesController < ApplicationController
2 before_action :check_user, only: :secreto
3
4 def home
5 end
6
7 def secreto
8 end
9
10 private
11 def check_user
12 authenticate_user!
13 unless current_user.admin?
14 redirect_to root_path, alert: "No tienes acceso"
15 end
16 end
17
18 end
Fixture de usuario
1 admin:
2 id: 1
3 email: "admin@desafiolatam.com"
4 role: 0
5
6 editor:
7 id: 2
8 email: "editor@desafiolatam.com"
9 role: 1
10
11 user:
12 id: 3
13 email: "user@desafiolatam.com"
14 role: 2
Para lograr esto iremos al archivo de rutas y cambiaremos el devise_for :users por
Una vez corrido el generador veremos que se crean diversos archivos bajo la carpeta user dentro de
controllers.
Los métodos expresados ahí dentro son sencillos, ya que user hereda de RegistrationsController todos
los métodos respectivos se reducen a hacer un llamado a super para llamar al método padre.
Agregaremos al final:
1 protected
2 def after_sign_in_path_for(resource)
3 destino_path
4 end
donde dice ENV[‘email’] y password podemos cambiarlas por nuestras claves de email y al reiniciar la
aplicación ya estaría funcionando pero hay un problema grande con hacer eso, estaríamos dejando las
claves del correo electrónico dentro de nuestro código.
gem 'dotenv-rails'
luego tenemos que crear un archivo .env (si, el punto es parte del nombre) dentro de la raíz de nuestro
proyecto, en el vamos a agregar las variables de entorno.
1 email=tuemail@gmail.com
2 email_password=tuppassword
y ya con eso nuestra aplicación permite recuperar las contraseñas 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 sólo
aplica si están ocupando GIT)
/.env
y ahora si que si, tus claves están seguras y tu aplicación está funcionando con la opción de recuperar
contraseñas.
Quiz
¿Para qué sirve el objeto current_user?
¿Donde se ingresan los strong parameters de un objeto deviseado?
¿Cómo podemos redireccionar a un usuario después de ingresar por devise?
¿Cómo podemos redireccionar a un usuario después de registrarse por devise?
27) Autorización con CanCanCan
CanCanCan es una gema para el manejo de accesos de usuarios en un sitio, y juega muy bien en
conjunto con Devise.
Hay que tener cuidado a la hora de trabajar con este gema de no confundirla con su versión
anterior llamada CanCan
Instalando CanCanCan
Para instalar la gema debemos abrir el gemfile y agregar la gema cancancan.
luego corremos bundle y generamos el árbol de habilidades de cancancan con el generador de rails.
1 rails g cancan:ability
1 create app/models/ability.rb
El árbol de habilidades
El árbol de habilidades es el archivo donde se define que puede hacer y que no puede hacer cada
usuario, este archivo se encuentra dentro de app/model/ability.rb y parte con un par de ejemplos
comentados. CanCanCan espera que exista un método current_user en el controlador, por eso
primero agregamos un sistema de autenticación como devise en primer lugar.
1 def initialize(user) # user es sacado del metodo current_user automaticamante por cancancan
2 # Define abilities for the passed in user here. For example:
3 user ||= User.new # guest user (not logged in)
4 if user.admin?
5 can :manage, :all
6 else
7 can :read, :all
8 end
9 end
La lógica que contiene es suficiente para partir, se entiende que si no hay un usuario logeado igual hay
que pasar por el proceso de autenticación por lo mismo crear el objeto user en la línea 4, luego si es
admin le da permiso para acceder a todo, pero si no sólo tiene permisos de lectura.
El método can recibe dos parámetros, el primero es la acción (método) a la cual estamos estableciendo el
permiso, y el segundo el recurso (Clase) en donde se puede ejecutar esa acción.
Por ejemplo quisiéramos dar acceso completo al recurso películas a los editores, haríamos
1 if user.editor?
2 can :manage, Movie
3 end
por ejemplo quisiéramos dar acceso completo a todos los recursos a los editores, haríamos
1 if user.editor?
2 can :manage, :all
3 end
Si quisiéramos por ejemplo que un usuario solo tuviera acceso a ver el índice podríamos hacer:
1 if user.user?
2 can :index, Movie
3 end
1 if user.user?
2 can [:index, :show], [Movie, Review]
3 end
El método :manage es un alias que se refiere a todas las acciones y el método :all es un alias
para referirse a todos los recursos de la aplicación.
Hay otros cuatro alias que usaremos constantemente para definir y chequear permisos y son:
También es posible definir aliases propios siguiendo los principios de la guía oficial
https://github.com/CanCanCommunity/cancancan/wiki/Action-Aliases, pero al menos que tengas
muchos métodos no REST repetidos en cada controller no tiene sentido.
Revisión de habilidades
Una vez que ya tenemos el árbol de habilidades, podemos empezar a ocupar el método can? para
determinar cuando mostrar (o cuando no) un link o cierta información específica a un usuario
dependiendo de su rol.
o es una línea
Ocultar el contenido no siempre es suficiente, por ejemplo en el caso de un link no sólo nos interesa que
la persona no pueda ver el link, también nos interesa que no logre entrar cambiando la url.
Bloqueo
La forma de bloquear un recurso a un acceso no deseado es a través del controller, se especifica la
acción loadandauthorize_resource.
1 class MoviesController < ApplicationController
2 load_and_authorize_resource
3
4 def show
5 # @movie is already loaded and authorized
6 end
7 end
load_and_authorize_resource cambia algunas reglas del juego, por ejemplo ya no tenemos que cargar los
recursos en los métodos clásicos para los métodos REST usando el callback before_action .
ejemplo:
1 def index
2 # @movies is already loaded and authorized
3 end
El testing de una habilidad siempre tiene la siguiente forma: Seleccionamos un usuario y vemos si tiene
acceso a un recurso.
1 user = User.first
2 ability = Ability.new(user)
3 ability.can?(:create, Movie)
4 ability.can?(:edit, Movie)
5 ability.can?(:destroy, Movie)
En el árbol de habilidades se puede especificar el acceso en base a un campo del recurso, por ejemplo:
Luego el recurso (en el controller) también hay que autorizarlo de una forma ligeramente distinta.
1 load_and_authorize_resource
Construir tests para este tipo de habilidades es igual que para otros tests de recursos anidados.
Para no tener que implementar las reglas del negocio para cada uno de los elementos y caer en el error
de repetir código existe una técnica llamada polimorfismo 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 común, por ejemplo si queremos implementar likes de usuarios
sobre movies y sobre reviews la interfaz la haríamos sobre likes. Agregando la interfaz:
1 belongs_to :user
2 belongs_to :likable, polymorphic: true
Los modelos que interactúan con la interfaz deben saber que lo están haciendo, para eso agregaremos al
modelo de movie y al de review la relación con
1 has_many :likes
2 has_many :movie_likes, through: :likes, source: :likable, source_type: ‘Movie’
3 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 validación en el modelo like:
Es necesario hacer la comprobación 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.
29) 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 mimso propósito llamada paperclip, la configuración es distinta pero ambas
hacen el trabajo.
Instalando carrierwave
El branch master de la gema todavía es experimental, por lo que recomendamos ocupar la
documentación del tag 0.10
luego bundle.
El siguiente paso es generar un uploader, un uploader define 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 distitna crearemos uno por estrategia.
Generando el uploader
storage file indica que el archivo se guardará dentro de la aplicación, dentro de la carpeta public, en el
próximo capítulo 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.
por ejemplo si tuvieramos un campo llamado photo dentro del modelo, entonces:
1 post = Post.first
2 File.open("nombre_archivo) do |f|
3 post.photo = f
4 end
5 post.save
por ejemplo
luego dentro del formulario para agregar un campo que nos permite utilizar archivos debemos uitlizar
1 f.file_file :photo
30) Amazon S3
Amazon S3 es un sistema de almacenamiento de archivos, es bastante fácil 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 aplicación, sin embargo fuera
de que la configuración dentro de un archivo es ligeramente distinta el resto de nuestra aplicación se
mantiene intacto, sin importar cual de estas dos gemas utilicemos.
1 if Rails.env.test?
2 CarrierWave.configure do |config|
3 config.storage = :file
4 config.enable_processing = false
5 end
6 else
7 CarrierWave.configure do |config|
8 config.storage = :aws
9 config.aws_credentials = {
10 :access_key_id => ENV['aws_access_key_id'], # required
11 :secret_access_key => ENV['aws_secret_access_key'], # required
12 :region => ENV['aws_region'], # optional, defaults to 'us-east
13 # :host => 's3.amazonaws.com'
14 #:host => 's3.example.com', # optional, defaults to nil
15 :endpoint => 'http://s3.amazonaws.com' # optional, defaults to nil
16 }
17 config.aws_bucket = ENV['aws_dir'] # required
18 config.aws_acl = 'public-read'
19
20 config.aws_attributes = {
21 expires: 1.week.from_now.httpdate,
22 cache_control: 'max-age=604800'
23 }
24
25 end
26 end
Separar los entornos no es necesario pero es útil, también es posible que en el entorno de desarrollo no
utilizar amazon para no generar gastos innecesarios, pero de todoas formas deberíamos hacer una
prueba con amazon localmente antes de subir los cambios.
Preguntas
Uno de los errores más frecuentes de los desarrolladores novatos de rails es el problema de las N+1
queries.
El problema sucede cuando intentando realizar una sola consulta a la base de datos pero esto gatilla
internamente N+1 consultas, y aunque cuando uno tiene una base de datos con pocos datos el efecto es
casi invisible, a medida de que crece el número de datos y el número de clientes este problema puede
llegar a impactar de forma muy dura en el rendimiento de tu aplicación a tal punto de botarla.
Modelo para el experimento Específicamente el problema se hace visible en la iteración de los resultados.
1 User.all.each do |u|
2 u.pins.each do |p|
3 puts p.name
4 end
5 end
Para este caso de pruebas se tienen 2 usuarios, Diego y Gonzalo y uno tiene 3 pins y el otro ninguno, y
aquí podemos observar que que se hicieron dos queries a la base de datos en lugar de solo una.
Para solucionarlo en el mismo momento que hacemos el query debemos incluir a los hijos del modelo, en
nuestro caso el query inicial debió haber sido.
1 User.all.includes(:pin)
De esta forma se hace una sola consulta a la base de datos, ahora repitamos el experimento anterior y
comparemos resultados.
Muchas veces me encuentro con aplicaciones de Rails que tienen javascript por todas las vistas y en
cualquier parte, mezclando estructura y comportamiento en el mismo archivo y cuando pregunto el por
qué lo hicieron así o por qué pusieron ese javascript ahí, la respuesta suele ser que si dejaban el código
en un archivo externo muchas veces el script no funcionaba o su comportamiento era errático, otras
veces era porque no sabían donde debían ponerlo, etc …
Esto es una muy mala practica ya que se hace difícil debuguear la aplicación, esteremos repitiendo
código si tenemos un comportamiento que se repite en todas la paginas, la carga de javascript detiene la
carga del HTML que resulta en paginas lentas que se sientes pesadas.
Los comportamientos globales deberían ser requeridos en el manifiesto para que de esa manera se
carguen en toda la aplicación.
Los comportamientos específicos NO deberían estar en el manifiesto y se deberían 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" específicos 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:
Con esto nuestra etiqueta <body> tendrá dos clases, una sera el nombre del controlador y la otra el
nombre de la acción que llama a la vista. Por ejemplo, en una aplicación donde tenemos posts al acceder
al detalle de un post nuestro <body> quedaría así:
Una vez hecho lo anterior podremos tener un comportamiento especifico a una vista haciendo lo
siguiente en nuestro archivo js:
1 $(document).ready(function() {
2 function someBehavior() {
3
4 ... comportamiento ...
5
6 };
7
8 if ( $('.posts.show').length > 0 ) {
9 someBehavior();
10 }
11 })
Turbolinks
Muchos de nosotros nos hemos dado cuenta, y no de la mejor manera, que cuando Turbolinks está
habilitado en nuestro proyecto (estado por defecto cuando creamos un proyecto), la función
$(document).ready() tiene un comportamiento errático y nuestros js no se vuelven a ejecutar al
cambiar de una pagina a otra y como consecuencia nuestro sitio no funciona como lo esperado. Esto
sucede debido a como turbolinks maneja la carga de las paginas de nuestra aplicación. La función de
turbolinks es hacer que nuestra aplicación se sienta mas rápida y fluida. En vez de dejar que el browser
cargue y recompile los JavaScripts y CSS cada vez que nos cambiamos de pagina, turbolinks mantiene la
pagina 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 pagina, por lo que no podremos confiar en
DOMContentLoaded o jQuery.ready() para ejecutar nuestro código. En su lugar turbolinks
dispara eventos en el document que podremos usar para ejecutar nuestro código:
Evento Descripción
page:before-
La pagina esta por cambiar.
change
page:fetch Una nueva página está a punto de ser traída desde el servidor.
page:receive Una página ha sido recibida desde el servidor, pero aún no analizada.
page:before-
Los nodos están a punto de ser cambiados.
unload
page:partial-
Nuevos elementos han sido cargados en el DOM a través de la sustitución parcial
load
page:after-
Un elemento se ha eliminado del DOM.
remove
Ejemplos de uso
1 $(document).on('page:fetch', function() {
2 $(".loading-indicator").show();
3 });
4 $(document).on('page:change', function() {
5 $(".loading-indicator").hide();
6 });
Como regla general, todos nuestros scripts asociados a un $(document).ready() deberían ser
modificados para usar $(document).on('page:load') ó
$(document).on('page:change') según sea necesario. Otra opción 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 sección 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 opción
data-no-turbolink .
Ejemplo:
Otra cosa importante para que nuestros javascript funcionen correctamente es el orden en el que los
requerimos en el manifiesto, primero siempre jQuery, después las librerías externas, luego nuestros
scripts y al final turbolinks para que sea el ultimo en instalar el manejador del evento click y así no
interferir otros scripts.
1 //
2 //= require jquery
3 //= require jquery_ujs
4
5 //= librerías externas
6 //= nuestros scripts
7
8 //= require turbolinks
9 //
1 //
2 //= require jquery
3 //= require jquery.turbolinks
4 //= require jquery_ujs
5
6 //= librerías externas
7 //= nuestros scripts
8
9 //= require turbolinks
10 //
Vamos a necesitar agregar las siguientes gemas para hacer los gráficos
1 gem "chartkick"
2 gem 'groupdate'
Para este capítulo vamos a construir un proyecto para administrar un fundacion donde tenemos donantes
y donaciones.
Agregando el plugin
Cargando el fullcalendar
En el html tenemos que agregar un div, y dentro de este div agregaremos el calendario
1 <div id="calendar">
1 <script>
2 $(document).ready(function() {
3 $('#calendar').fullCalendar({
4 theme: true,
5 events: "#{}"
6 })
7 });
8 </script>
Intro
Action Mailer nos permite enviar correos desde nuestra aplicación utilizando clases y vistas mailer .
Estos funcionan muy parecido a los controladores en donde un método en la clase mailer tiene una vista
asociada.
1 create app/mailers/user_mailer.rb
2 create app/mailers/application_mailer.rb
3 invoke erb
4 create app/views/user_mailer
5 create app/views/layouts/mailer.text.erb
6 create app/views/layouts/mailer.html.erb
7 invoke test_unit
8 create test/mailers/user_mailer_test.rb
9 create 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.
El método default, que acepta un hash como parámetro: 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 método mail: es donde armamos el mensaje de correo. aquí
estamos pasando a quien :to y el asunto :subject . template_path y template_name
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 definidas en este método estarán
disponibles para ser usadas en la vista.
1. Crear la vista
Y también 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 Welcome to example.com, <%= @user.name %>
2 ===============================================
3
4 You have successfully signed up to example.com,
5 your username is: <%= @user.login %>.
6
7 To login to the site, just follow this link: <%= @url %>.
8
9 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 a nuestro mailer:
test/mailers/previews/user_mailer_preview.rb En el llamaremos a nuestro método
welcome_email y le pasaremos un usuario cualquiera como parámetro
Es importante recordar que la información sensible, como por ejemplo nuestro nombre de
usuario y contraseña de gmail, nunca deben ser usados de manera explicita en nuestros
archivos de configuración, ya que existen pequeños programas, llamados [web crawlers]
(https://en.wikipedia.org/wiki/Web_crawler) o web spiders, que se dedican a buscar este tipo
de información sensible. Por lo tanto para usar estos datos los pasaremos de manera implícita
a nuestros archivos de configuración usando variables de entorno con la gema `dotenv-rails`
1 GMAIL_USERNAME=ninombredeusurario
2 GMAIL_PASSWORD=miclave
1 El nombre de la variable puede ser cualquier cosa y puede ir en mayusculas o minúsculas. Es impo
Ahora para poder usar estas variables en algún archivo las llamamos de la siguiente forma:
1 ENV['GMAIL_USERNAME']
1. Configuración de ActionMailer
Para configurar ActionMailer la mejor opción es usar los archivos de ambiente (como enviroment.rb,
production.rb, development.rb, etc…). Pueden revisar las opciones de configuración aqui.
1 config.action_mailer.raise_delivery_errors = true
2 config.action_mailer.delivery_method = :smtp
3 config.action_mailer.smtp_settings = {
4 address: 'smtp.gmail.com',
5 port: 587,
6 user_name: ENV['GMAIL_USERNAME'],
7 password: ENV['GMAIL_PASSWORD'],
8 authentication: :login,
9 enable_starttls_auto: true
10 }
Esto para que al hacer pruebas en nuestra aplicación no se envíen los correos.
Para usar nuestro mailer y enviar el mensaje cuando se crea un nuevo usuario, lo llamaremos desde el
UserController en el método create que es donde se crea y guarda el usuario. Es importante
que sepan que para que el correo efectivamente se envíe tenemos que terminar de llamarlo con el
método :deliver_now o :deliver_later , la diferencia entre ambos la veremos mas adelante.
En el caso de estar creando un nuevo registro, como en este ejemplo, es importante que el envío del
correo se realize una vez que el registro se ha guardado, no antes ya que no queremos enviar el correo y
después 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.
2. Modificar el controller Ahora que tenemos el controller creado lo modificaremos y lo dejaremos así:
3. Decirle a devise que use el controller creado anteriormente En el archivo de rutas ( routes.rb )
le diremos a devise que use este nuevo controller
1 ...
2 devise_for :users, controllers: { registrations: 'users/registrations' }
3 ...
En esta segunda opción no es necesario crear nada adicional ya que solo modificaremos el modelo de
usuario
1. Modificar el Modelo de usuario Creamos un método privado dentro del modelo de usuario
1. deliver_now: (sincrona) envía el correo inmediatamente en el proceso en el que fue llamado, si han
realizado pruebas se habrán dado cuenta que al crear un nuevo usuario este proceso demora un
poco mas, y ¿por qué demora más?, demora más porque ahora cuando se crea un nuevo usuario se
llama al mailer para que envíe 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 creación del usuario puede terminar.
2. deliver_later: (asíncrona) a diferencia de su hermano, este método no envía 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 creación del usuario no demora más ya que no tiene que esperar a
que se envíe el correo para poder terminar su proceso. Si han hecho pruebas con deliver_later se
habrán dado cuenta de que aun así el tiempo de creación del usuario no ha mejorado con respecto a
deliver_now, ¿por qué? La respuesta es ActiveJob
ActiveJob y deliver_later
A partir de la version 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, envío de correos, etc
…
Los backends que manejan estas colas de trabajo son gemas que podemos integrar en nuestra
aplicación, 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 oficial aqui
El punto que nos interesa a nosotros en esta guía 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 asíncrona en una cola de trabajo utilizando el método deliver_later
Entonces, ¿por qué, si están integrados, al usar deliver_later se comporta como deliver_now? Por
defecto, cuando no tenemos ningún 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 configurarlo.
1 gem 'sucker_punch'
1 config.active_job.queue_adapter = :sucker_punch
3. NO HAY PASO 3 En serio, esto es todo lo que hay que hacer para poder enviar los correos de forma
asíncrona usando ActiveJob y deliver_later
Si hacen una prueba ahora se darán cuenta que el proceso de crear un usuario ya no demora como
antes y el correo se envía igual!!!
36) Testings automatizado con Guard
En rails es posible automatizar completamente los test ocupando guard.
Uno se preguntaría para que automatizar más, puesto que con la simple instrucción rake se corren
todos los test definidos, pero la gema Guard permite que se corran automatícamente los tests respectivo
cada vez que modificas un archivo de un controller, fixture, modelo o test, y te avisa si producto de la
introducción de alguna mejora rompiste alguna funcionalidad ya existente en el sistema.
Esta guía se ha probado con: * Ruby version 2.1.2 o mayor * Rails version 4.1.5 o mayor * MiniTest version
5.4.0 o mayor
1 group :development do
2 gem 'guard'
3 gem 'guard-minitest'
4 gem 'minitest-reporters'
5
6 # notificaciones, solo usuarios de Mac OS X 10.8 o mayor
7 gem 'terminal-notifier'
8 gem 'terminal-notifier-guard'
9 end
si van a usar las notificaciones tienen que instalar terminal-notifier con brew (brew install terminal-notifier)
guard : añade soporte para la herramienta Guard que maneja eventos y modificaciones 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 mostrara un mensaje en el centro de notificaciones cuando se ejecuten los test.
Añadimos estas gemas al grupo de desarrollo porque se ejecutarán 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
configuración, para esto ejecutamos lo siguiente en la terminal:
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
modificado. En nuestro caso va a monitorear la carpeta app y todo su contenido y llamara los test
correspondientes usando minitest.
1 # A sample Guardfile
2 # More info at https://github.com/guard/guard#readme
3
4 guard :minitest do
5 # with Minitest::Unit
6 watch(%r{^test/(.*)\/?test_(.*)\.rb$})
7 watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
8 watch(%r{^test/test_helper\.rb$}) { 'test' }
9
10 # with Minitest::Spec
11 # watch(%r{^spec/(.*)_spec\.rb$})
12 # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
13 # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
14
15 # Rails 4
16 # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
17 # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
18 # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test
19 # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_te
20 # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
21 # watch(%r{^test/.+_test\.rb$})
22 # watch(%r{^test/test_helper\.rb$}) { 'test' }
23
24 # Rails < 4
25 # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
26 # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
27 # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
28 end
Como nosotros estamos usando Rails 4, descomentaremos las líneas 16 a la 22 y borraremos el resto de
las líneas comentadas dejando el archivo así:
1 # A sample Guardfile
2 # More info at https://github.com/guard/guard#readme
3
4 guard :minitest do
5 # with Minitest::Unit
6 watch(%r{^test/(.*)\/?test_(.*)\.rb$})
7 watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
8 watch(%r{^test/test_helper\.rb$}) { 'test' }
9
10 # Rails 4
11 watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb"
12 watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
13 watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/
14 watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[
15 watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}
16 watch(%r{^test/.+_test\.rb$})
17 watch(%r{^test/test_helper\.rb$}) { 'test' }
18 end
1 guard :minitest do
2 # ...
3 end
Aquí le decimos a Guard que el siguiente bloque se tiene que ejecutar con Minitest. Guard se puede
configurar con multiples plugins que pueden realizar muchas otras cosas.
1 watch(%r{^test/(.*)\/?test_(.*)\.rb$})
Esta línea monitorea todos los archivos .rb en las subcarpetas de test/ y ejecuta el archivo que
se ha modificado.
Esta línea monitorea todos los archivos .rb que se encuentran en el directorio lib y ejecutará el
test correspondiente si es que existe alguno.
1 watch(%r{^test/test_helper\.rb$}) { 'test' }
Esta línea monitorea el archivo test_helper.rb y si hay un cambio ejecuta todos los test.
1 watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
Esta línea monitorea todos los archivos en el directorio app y ejecuta el test correspondiente. En una
aplicación típica de rails estos serian los models, controllers, helpers y mailers.
1 watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
Esta línea monitorea los controladores y ejecuta el test de integración correspondiente cuando hay
modificaciones.
Esta línea monitorea la carpetas de las vitas de los mailers y ejecuta el test correspondiente cuando hay
modificaciones.
Esta línea monitorea la carpeta lib y ejecuta el test correspondiente cuando hay modificaciones si es
que lo existe un test asociado.
1 watch(%r{^test/.+_tests\.rb$})
2 watch(%r{^test/test_helper\.rb$}) { "test" }
Estas líneas 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 configuración básica de nuestro Guardfile y como ven es muy completa. Sin embargo podemos
agregar unos monitores extras, que en lo personal encuentro de bastante utilidad.
1 require 'active_support/inflector'
Aquí añadimos la clase inflector de rails para poder ‘singularizar’ los nombres de los fixtures. Esto nos
permite convertir ‘Posts’ en ‘Post’ para cualquier modelo o controlador.
Esta línea correra el el test de controlador si cambia alguna de las vistas asociadas.
Esta línea ejecutará los tests de modelo si los fixtures cambian. Aquí es donde usamos la clase inflector
de rails
Configurar Minitest-Reporters
Antes de continuar con Guard vamos a configurar 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
Sin minitest-reporters
Con minitest-reporters
1 Started with run options --seed 28340
2
3 ProductsControllerTest
4 test_should_get_new PASS (0.19s)
5
6 ProductTest
7 test_should_not_create_product_withoud_description PASS (0.00s)
8
9 UserTest
10 test_user_owns_products PASS (0.02s)
11
12 Finished in 0.34634s
13 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
Esto iniciara Guard, analizara el Guardfile y los plugins instalados y carrera nuestros test de manera
automática. 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 13:07:02 - INFO - Guard::Minitest 2.4.4 is running, with Minitest::Unit 5.8.2!
2 13:07:02 - INFO - Running: all tests
3 Started with run options --seed 46327
4
5 ProductsControllerTest
6 test_should_show_product PASS (0.18s)
7 test_should_get_edit PASS (0.03s)
8
9 UserTest
10 test_should_not_create_user_withoud_username PASS (0.01s)
11 test_should_not_create_user_without_name PASS (0.00s)
12
13 ProductTest
14 test_should_not_create_product_withoud_description PASS (0.00s)
15 test_price_should_be_a_float PASS (0.00s)
16
17 Finished in 0.31300s
18 6 tests, 6 assertions, 0 failures, 0 errors, 0 skips
19
20 13:07:05 - INFO - Guard is now watching at '/Users/Username/Path/To/Project'
21 [1] guard(main)>
Con guard cualquier cambio que hagamos en los archivos monitoreados gatillará los test relevantes y se
ejecutarán automáticamente, y si estas en OSX e instalaste terminal-notifier recibirás una notificación.
Si quieres forzar la ejecución 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.
Introducción
Ruby on Rails (RoR) es un framework de desarrollo que le entrega a los desarrolladores una fácil y rápida
herramienta para crear aplicaciones web, y Nginx es un servidor web ligero de alto rendimiento. Estos
dos programas puedes ser configurados fácilmente para que trabajen en conjunto en un VPS (Virtual
Private Server) con Phusion Passenger.
Pushion Passenger es un servidor web y de aplicaciones, diseñado 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, rápido y escalable.
Se espera que el lector de esta guía sepa como utilizar la terminal y tenga conocimiento de al menos
comandos básicos de este. Así como también se espera sepan usar postgres.
Convenciones.
1. Los términos 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 sección se usara: local para la maquina local o servidor para la maquina virtual.
4. Se usara para los ejemplos la IP: 111.11.111.11, esta tienen que ser remplazada por la ip de su
maquina virtual.
5. Para mostrar las instrucciones a ejecutar en el terminal se antepondrá el signo $ , que es la
representación de su línea de comandos que esta lista para recibir una instrucción (no hay que
tipearlo), y tendrán el siguiente estilo:
1 $ gem install postgres
Para mostrar las respuestas, errores o advertencias que nos arroja el terminal al ejecutar una
instrucción no se antepone el signo $ y tendrán el siguiente estilo:
Para acceder a nuestro VPS usaremos el Terminal como interface de conexión mediante SSH.
En nuestra terminal:
1 $ ssh root@111.11.111.11
La primera vez que nos tratemos de conectar se nos mostrara un mensaje como este:
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
1 $ root@ip-111-11-111-11:~$
Esto no indica que estamos logueados en la maquina con ip 111.11.111.11 en el usuario root, todo lo
que escribamos ahora se ejecutará en el servidor.
Para desconectarnos del servidor y "volver" a nuestra maquina local escribimos lo siguiente:
servidor
1 $ exit
servidor
En algunos casos puede ser que al entrar a su maquina les muestre esta advertencia:
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:
2. Reconfiguramos lo locales:
1. Abrimos el archivo environment con algún editor (vim, nano, emacs, etc):
1 LC_ALL=en_US.UTF-8
2 LANG=en_US.UTF-8
Para configurar el timezone del servidor a nuestra zona horaria local haremos lo siguiente:
1 $ date
1 $ more /etc/timezone
1 US/Arizona
Una vez elegida nuestra zona horaria, podemos volver a comprobar ejecutando los comandos del paso
anterior.
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.
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.
1. Creamos un nuevo usuario, este se llamara deploy. Se nos pedirá crear una contraseña, 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á información adicional.
1 $ adduser deploy
(El nombre deploy es un buen nombre para el usuario de deployment, pero no es necesario que sea
este)
2. Añadimos 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 $ exit
1 $ ssh deploy@111.11.111.11
*Esta vez se nos pedirá la contraseña que pusimos al crear el usuario.
Para no tener que poner la clave cada vez que queremos conectarnos al servidor y también así evitar
problemas en un futuro al usar Capistrano para hacer deploy, haremos lo siguiente:
1 $ ssh-copy-id deploy@111.11.111.11
En esta etapa, que demora un poco, se nos pedirá la clave del usuario deploy para poder copiar la llave
ssh en el llavero del usuario deploy
Solo si el paso anterior falla porque no encuentra el comando ssh-copy-id, lo instalaremos de la siguiente
forma:
En Mac.
En Linux.
1 $ ssh deploy@111.11.111.11
Ahora que ya podemos entrar a nuestro servidor con un usuario diferente a root, vamos a hacer el primer
paso para configurar 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 estén al día
2. Instalación de RVM
En este paso nos mostrara un warning y si leemos bien veremos las siguiente líneas:
1 ...
2 gpg: Can't check signature: public key not found
3 Warning, RVM 1.26.0 introduces signed releases and automated check of signatures when GPG sof
4 Assuming you trust Michal Papis import the mpapis public key (downloading the signatures).
5
6 GPG signature verification failed for '/home/deploy/.rvm/archives/rvm-1.26.11.tgz' - 'https:/
7 try downloading the signatures:
8
9 gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
10
11 or if it fails:
12
13 command curl -sSL https://rvm.io/mpapis.asc | gpg --import -
14
15 the key can be compared with:
16
17 https://rvm.io/mpapis.asc
18 https://keybase.io/mpapis
Ahi mismo nos dice que tenemos que descargar la firma para autenticar el paquete antes de instalar, y
eso lo hacemos copiando la que dice:
Y la pegamos en el terminal
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
3. Ahora le diremos a RVM que instale todas las dependencias que necesita.
1 $ rvm requirements
2. Ahora nos aseguraremos de que tenemos todos los componentes requeridos por RoR
Si todo sale bien ahora podremos instalar RoR y otras gemas, pero antes de eso le diremos a nuestra
maquina que no descargue la documentación de las gemas al instalarlas, ya que ellas demoran el
proceso y usan espacio innecesariamente.
Por un problema con la ultima version de rubygems y ruby (2.2.3), en el proceso de instalación de Rails
tendremos un problema con la gema nokogiri , para solucionarlo haremos lo siguiente:
Una ves 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
3. Luego añadimos el repositorio de passenger al source list de nuestra maquina para poder
usarlo
4. Después de agregarla tenemos que cambiar el grupo y permisos para poder hacer un update a
los paquetes disponibles para incluirlo a ellos
Ahora que tenemos el servidor preparado podemos instalar Nginx con Passenger:
En el archivo de configuración de Nginx (nginx.conf) se tiene que especificar donde esta passenger. Para
eso se tienen que descomentar (quitar el signo # que se antepone) las líneas que empiezan con:
1 # passenger_root ....
2 # passenger_ruby ....
1. Primero abrimos el archivo de configuración de nginx con algún editor. (Nginx se encuentra en
la carpeta /etc del servidor)
1 passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
2 passenger_ruby /home/deploy/.rvm/wrappers/ruby-2.2.3/ruby;
Ojo que en la segunda línea la version de ruby, que en este caso es ruby-2.2.3, tiene que decir la version
que tienen instalada
Hecho esto guardamos los cambios y cerramos el archivo, y tenemos que reiniciar el servidor nginx para
que aplique los cambios.
En caso de tener un problema al reiniciar el server puedes revisar los logs así:
Instalación
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:
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 aplicación con
alguna variante o simplemente uso deploy.
Crear el usuario:
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).
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.
1 \password nombreUsuario
Se nos pedira ingresar la password y confirmarlo.
En el entorno de postgres crearemos la base de datos asociada al usuario que creamos en el paso
anterior.
Estos datos, nombre de usuario, contraseña y base de datos de postgres, son los que usaremos para
configurar nuestro archivo database.yml en rails (en la guía de capistrano), por lo que es muy importante
no olvidarlos.
Cuando se usa Nginx, los server blocks (similar a los virtual hosts en Apache) se usan para encapsular
los detalles de configuración y servir mas de un dominio en un único servidor.
Ahora veremos como configurar 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 configurado para servir los documentos que están en el siguiente directorio:
1 /var/www/html
Nosotros no usaremos el default ya que es mas fácil trabajar desde nuestro directorio del usuario.
Para eso nos podemos crear una nueva carpeta llamada /www o /apps (En esta guía vamos a
trabajar con la carpeta /www ). En este directorio es donde cada uno de nuestros proyectos tendrá
su propia carpeta, ej:
1 ~/www/example/
2 ~/www/test/
Crear el directorio:
1 $ mkdir -p ~/www/example
**La opción -p le dice a mkdir que cree todos los directorios padres necesarios si estos no existen
Vamos a crear una pagina de ejemplo, para tener algo que mostrar al crear el server block. Ya que aun
no subimos un proyecto de rails.
1 $ vim ~/www/example/index.html
1 <html>
2 <head>
3 <title>Bienvenidos a Example.com</title>
4 </head>
5 <body>
6 <h1>El server block esta funcionando!</h1>
7 </body>
8 </html>
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 server {
2 listen 80 default_server;
3 listen [::]:80 default_server ipv6only=on;
4
5 server_name example.com www.example.com;
6
7 passenger_enabled on;
8 rails_env production;
9
10 root /home/deploy/www/example;
11
12 # redirect server error pages to the static page /50x.html
13 error_page 500 502 503 504 /50x.html;
14 location = /50x.html {
15 root html;
16 }
17 }
Las líneas 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 parámetro 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 especificación de
default_server!!!
En la línea 5 seteamos a que requests responderá este server block, en este caso example.com, y
ademas podemos añadir alias, en este caso www.example.com, separados por un espacio.
En la línea 7 activamos passenger para este server block.
En la línea 8 seteamos en ambiente de rails que vamos a ejecutar, en este caso el ambiente de
producción.
En la línea 10 con la directiva root apuntamos al directorio de nuestro proyecto, el path tiene que
ser absoluto. OJO Cuando estamos trabajando con una aplicación de rails la ruta queda
así: root /home/deploy/www/example/current/public;
De la línea 12 a la 16 le estamos diciendo que las paginas de error de servidor apunten a una
pagina estática.
Ahora nuestro archivo se encuentra habilitado, pero también 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 parámetro default_server solo puede estar en un server block. Para
arreglar esto simplemente eliminamos el enlace simbólico a este:
1 $ sudo rm /etc/nginx/sites-enabled/default
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 guía seria http://111.11.111.11.
Deberíamos ver el mensaje que pusimos en nuestro archivo index.
Para que nuestra aplicación RoR funcione bien y en caso de usar capistrano para hacer el deployment,
tenemos que instalar los siguientes paquetes:
Con esto ya hemos terminado de configurar y dejar lista nuestra VPS para el deployment de una
aplicación RoR
Extras
Algunos comandos importantes
1 $ ssh deply@ip_del_servidor
2
3 $ sudo apt-get install paquete
4 $ sudo apt-get update
5 $ sudo apt-get upgrade
6
7 $ sudo -u postgres psql
8
9 $ sudo service nginx stop
10 $ sudo service nginx start
11 $ sudo service nginx reload
12 $ sudo service nginx restart
13
14 $ sudo ln -s ruta/original/archivo ruta/destino
15
16 $ sudo tail -n 50 /var/log/nginx/error.log
38) Deployment con Capistrano.
Introducción
Cuando yo estaba aprendiendo Rails, como un novato, no tenía ni idea de cómo llegar de esta cosa que
estaba trabajando en mi máquina de desarrollo a un verdadero servidor web que otras personas pudieran
ver. Todas estas cosas Unix CLI (Command Line Interface o terminal) parecían como magia negra para mí
(probablemente, en parte porque mi terminal es de color negro) y me sentí como que necesitaba un
doctorado en Cirugía Robótica para poder hacerlo. Rails ha hecho tan fácil 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 están tratando de quitar una gran parte de la complejidad a la
hora de hacer deploy una aplicaciones web, y están 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. Sentía 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 cómo
mi servidor web funcionaba.
En un principio hice todo manualmente (la copia de archivos, la migración de bases de datos, la
instalación de las gemas, reiniciar servicios, etc). Pero rápidamente 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 sabía por qué, y tenia que
pasar horas averiguando dónde fue que perdí un signo o puse uno de mas. La moraleja de la historia es:
encontrar la manera de hacerlo una vez, y después, guardarlo en un script que se pueda repetir sin
problemas (aparentemente los computadores (ordenadores) son muy buenos haciendo lo mismo una y
otra vez; ¿quién sabe?). Y es por eso que uso Capistrano y Git!
Configuraremos nuestra aplicación de Rails para hacer deploy en el servidor remoto utilizando Capistrano
y Git para que este proceso de implementación sea automatizado, rápido y libre de dolores de cabeza.
Creo que los dos aspectos clave de cualquier proceso de implementación son la velocidad y
consistencia. Velocidad significa que podemos repetir y corregir errores rápido y mantener nuestro código
de producción en sintonía con nuestro código de desarrollo. Consistencia significa que sabemos que va a
hacer lo mismo cada vez, así que no tendremos miedo de hacerlo y estar al día. El uso de un sistema de
control de versiones como Git, junto con las recetas de implementación automatizadas de Capistrano
satisface estos criterios con facilidad.
En esta guía usaremos un servidor remoto que tenga Ubuntu, Passenger y Nginx instalados y
configurados, así como también acceso SSH a este. Si no tienes un servidor remoto con los
requerimientos antes mencionados, te recomiendo que sigas la guía "Rails y Nginx con Passenger en
Ubuntu: Preparando nuestro entorno de producción", que disponible para servidores Amazon,
DigitalOcean o Linode.
Se espera que el lector de esta guía sepa como utilizar la terminal y tenga conocimiento de al menos
comandos básicos de este. Así como también se espera sepan usar postgres.
Convenciones.
Para mostrar las respuestas, errores o advertencias que nos arroja el terminal al ejecutar una
instrucción no se antepone el signo $ y tendrán el siguiente estilo:
Paso 0 – La Aplicación
local
Para empezar, vamos a necesitar algo para implementar Capistrano y hacer deploy. Para eso vamos a
crear una aplicación sencilla aquí (el proceso de implementación debe ser más o menos el mismo,
independientemente de lo que esté haciendo su aplicación). Mi objetivo, aquí, es explicar un método muy
simple para la automatización de sus deployments para darle un lugar donde empezar. Quizás esta no es
la manera más rápida o la manera más elegante, pero va a hacer su proceso coherente, y sin duda será
mucho más rápido que hacerlo manualmente. Mi pensamiento es que si funciona, al menos ustedes
pueden darse el tiempo para aprender las técnicas más avanzadas.
Empecemos:
1. Crear una aplicación de tareas (usando Postgres porque eso es lo que he instalado en mi
servidor):
Vamos a crear una pequeña aplicación de tareas con rails y vamos a hacer un scaffold y vamos a
revisar que funcione!
Listo tenemos nuestra aplicación creada, no hace mucho, pero nos servirá para lo que necesitamos.
Antes de instalar capistrano, es muy importante que nuestra aplicación 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
añadir los archivos con información sensible a este. Por ejemplo el config/database.yml ,
config/secrets.yml y el .env en caso de estar usando la gema dotenv-rails .
1 $ git init
2 $ git add --all
3 $ 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 configurar nuestra app para linkearla con el. En los
ejemplos usare github!
Una vez linkeado, pushearemos la app para que este disponible en nuestro repo.
1 $ git push -u origin master
La instalación de Capistrano es tan fácil como añadir la gema al Gemfile de nuestra aplicación y ejecutar
bundle install. Nosotros no necesitamos Capistrano en el servidor de producción, por lo que la añadimos
bajo el grupo de "desarrollo" del Gemfile. Como referencia, estoy usando la ultima version ‘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 gemfile y añadimos lo siguiente:
1 group :development do
2 gem 'capistrano'
3 gem 'capistrano-bundler'
4 gem 'capistrano-rails'
5 gem 'capistrano-rvm'
6 gem 'capistrano-passenger'
7 gem 'capistrano-ssh-doctor'
8 end
gem 'capistrano' : es la que nos permitirá instalar capistrano y ejecutar sus tareas.
gem 'capistrano-bundler' : añade la tarea bundler:install a Capistrano, y se
ejecuta automáticamente en el servidor remoto como parte de las tareas que se realizan cuando
hacemos un deploy con capistrano.
gem 'capistrano-rails' : añade 2 tareas especificas a Capistrano, deploy:migrate y
deploy:compile_assets , y se ejecutan automáticamente 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 version de Ruby correcta y le
dice a capistrano que use rvm ... do ... para correr rake, bundle, gem y ruby.
gem 'capistrano-passenger' : Añade la tarea passenger:restart , y reiniciará el
servidor passenger después de hacer un deploy con capistrano.
gem 'capistrano-ssh-doctor' : Añade la tarea ssh:doctor , para verificar si las
conexiones mediante ssh están correctas y si no ayudarnos a resolverlas.
1 $ bundle install
1 $ cap install
1 !"" Capfile
2 !"" config
3 # !"" deploy
4 # # !"" production.rb
5 # # $"" staging.rb
6 # $"" deploy.rb
7 $"" lib
8 $"" capistrano
9 $"" tasks
En nuestro caso solo queremos tener un solo stage, el de producción, para eso lo instalamos así:
~~~bash $ cap install STAGES=production ~~~
1 !"" Capfile
2 !"" config
3 # !"" deploy
4 # # $"" production.rb
5 # $"" deploy.rb
6 $"" lib
7 $"" capistrano
8 $"" tasks
¿Para que tener distintos stages? Sirven por si queremos tener configuraciones especificas para
diferentes deployments de nuestro proyecto.
Listo, nuestro proyecto esta "capify". En la siguiente sección prepararemos nuestro proyecto!
Paso 2 – Preparación de nuestro proyecto
En esta sección vamos a revisar cada archivo que la instalación de capistrano creo, vamos a entender
que hacen y los vamos a editar con nuestras preferencias.
1. Capfile
Como vemos, tenemos varias líneas comentadas por lo que vamos a editar el archivo e incluir los
plugins que necesitamos en nuestro proyecto, estos están relacionados con las gemas que
agregamos al gemfile y limpiar lo que no necesitamos.
Ahora tenemos todas las tareas extras, de las gemas de capistrano que agregamos, disponibles para
usar al hacer deploy.
2. deploy.rb
Ahora les voy a explicar que es lo que estamos haciendo en este archivo:
Existen más variables que se pueden configurar, pero estas son las que, en la mayoría de los casos,
vamos a necesitar cambiar. Para conocer que otras variables y profundizar en el tema les recomiendo
que lean http://capistranorb.com/documentation/getting-started/configuration/
3. production.rb
Para las configuraciones que son especificas de cada stage, editamos cada uno de los archivos que
tengamos en config/deploy/ . En este caso solo tenemos el stage production, que se encuentra
en config/deploy/production.rb . Al igual que antes el archivo creado viene con contenido
por defecto y lo vamos a remplazar por lo siguiente:
Aqui va la explicación:
:stage : le damos el nombre a nuestro stage, en este caso producción, que usaremos al hacer
deploy. :rails_env : le decimos a rail que corra en el ambiente que necesitamos, en este caso
producción.
server... : En esta línea le decimos a Capistrano como tiene que acceder a nuestro vps. Le
damos el ip de la maquina 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 maquina. La
opción 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 añadir unas tareas personalizadas a
capistrano.
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 día antes de hacer deploy.
1. setup.rake
2. assets.rake
1 namespace :clean do
2 desc 'Runs rake assets:clobber on server to remove compiled assets'
3 task :assets do
4 on roles(:app) do
5 within "#{current_path}" do
6 with rails_env: :production do
7 execute :rake, 'assets:clobber'
8 execute :touch, release_path.join('tmp/restart.txt')
9 end
10 end
11 end
12 end
13 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 estén causando
problemas.
3. deploy.rake
Esta tarea se encargara de revisar si nuestro repo esta al día con los cambios locales antes de hacer
el deploy. Si no es así, se cancelara el deploy y nos dará una advertencia.
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
automáticamente 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 conexión. En caso contrario crearemos una nueva llave.
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
Finalmente se nos mostrara la ‘huella’ o id de nuestra llave ssh. Sera algo parecido a esto:
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
Listo, ahora si, como hacemos el deploy. Hacer un deploy es tan fácil como escribir esto en la consola en
el root de nuestro proyecto:
1 $ cap -T
Esto nos devolverá un listado de todas las tareas que podemos usar y su descripción; de toda esa lista
las tareas que más usaremos y las que nos interesan son:
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 jerarquía de directorios estrictamente definido en cada servidor remoto para
organizar el código fuente y otros datos relacionados con el deployment. La ruta raíz de esta estructura
es la definida en la variable de configuración: :deploy_to que modificamos en el archivo
config/deploy.rb
Si revisamos la ruta raíz e inspeccionamos los directorios veremos algo como esto:
1 !"" current -> /var/www/my_app_name/releases/20150120114500/
2 !"" releases
3 # !"" 20150080072500
4 # !"" 20150090083000
5 # !"" 20150100093500
6 # !"" 20150110104000
7 # $"" 20150120114500
8 !"" repo
9 # $"" <VCS related data>
10 !"" revisions.log
11 $"" shared
12 $"" <linked_files and linked_dirs>
/releases : cada vez que se hace un deploy un nuevo directorio se creara aquí, y contiene todo el
código de ese deploy.
/current : es un enlace simbólico 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.
1 $"" shared
2 !"" .env
3 !"" config
4 !"" public
5 !"" log
6 !"" tmp
7 !"" bin
8 !"" bundle
9 $"" vendor
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 añade los links
simbólicos que apuntan a /shared , corre Bundler, las migraciones y compila los assets.
publishing : crea el links simbólico 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 apuntara a la ultimo directorio de /releases que estaba
funcionando, de esta manera el sitio siempre estará disponible.
1. Si es el primer deploy de nuestra app, ejecutaremos las siguientes tareas, una a una, en el
orden en que aparecen a continuación:
ssh:doctor : comprueba que todas las conexiones mediante ssh estén correctas.
setup:database|secrets| : suben los archivos database y secrets
respectivamente. si falta alguno de ellos el deploy nos dará error.
setup:env : sube el archivo .env , este solo se ejecuta si estamos utilizando la gema
dotenv-rails en nuestro proyecto para definir variables de configuración privadas.
deploy : finalmente ejecutamos deploy.
Si siguieron todos los pasos de esta guía y los de la guía de "Rails y Nginx con Passenger en
Ubuntu…" no deberían tener ningún error. En el caso de que el deploy nos de un error, revisaremos
bien los mensajes que nos da la terminal y buscaremos en google alguna respuesta.
Si ahora abrimos nuestro navegador favorito (espero que no sesea Internet Explorer) y escribimos la
dirección IP de nuestro servidor en la barra de direcciones, podremos ver nuestra aplicación; si no la
vemos, no se preocupen. La implementación es difícil y toma un tiempo para asimilar. Si las cosas no
funcionan, lo mejor es comenzar con los registros y googlear cualquier error que encontremos allí.