Crea un clon de Instagram desde cero
https://github.com/railsgirls-cali/instagram-2023
Un recordatorio rápido sobre Rails.
Rails es un framework de desarrollo de aplicaciónes web escrito en el lenguaje de programación Ruby. Está diseñado para hacer que la programación de aplicaciónes web sea más fácil, haciendo supuestos sobre lo que cada desarrollador necesita para comenzar. Te permite escribir menos código realizando más que muchos otros lenguajes y frameworks. Además, expertos desarrolladores en Rails reportan que hace que el desarrollo de aplicaciones web sea más divertido.
Rails te permite escribir un buen código evitando que te repitas y favoreciendo la convención antes que la configuración.
La convención antes que la configuración es un concepto simple que se utiliza principalmente en la programación. Significa que el entorno en el que trabaja (sistemas, bibliotecas, lenguaje …) asume muchas situaciones lógicas por defecto, por lo que si te adaptas a ellas en lugar de crear tus propias reglas cada vez, la programación se convierte en una tarea más fácil y productiva.
El objetivo es disminuir el número de decisiones que debes tomar como programador y eliminar la complejidad de tener que configurar todas y cada una de las áreas de desarrollo de aplicaciones. El resultado inmediato es que puedes crear muchas más cosas en menos tiempo.
Los entornos de programación altamente exitosos como Ruby on Rails se basan en este concepto. Si sigue las convenciones establecidas, puede desarrollar una aplicación Rails en mucho menos tiempo y con muchas menos líneas de código de las que necesitaría desarrollar una aplicación web en otros lenguajes.
En le transcurso de este proyecto vamos a hablar de conceptos de arquitectura y programación como vistas o controladores que usa Rails debido a que sigue un patrón de arquitectura que se llama MVC.
El MVC o Modelo-Vista-Controlador es un patrón de arquitectura de software que, utilizando 3 componentes (Vistas, Models y Controladores) separa la lógica de la aplicación de la lógica de la vista en una aplicación.
MODELO Se encarga de los datos consultando la base de datos. Actualizaciones, consultas, búsquedas, etc. todo eso va aquí, en el modelo.
CONTROLADOR Recibe las órdenes del usuario y se encarga de solicitar los datos al modelo y de comunicárselos a la vista.
VISTAS Son la representación visual de los datos, todo lo que tenga que ver con la interfaz gráfica va aquí. Ni el modelo ni el controlador se preocupan de cómo se verán los datos, esa responsabilidad es únicamente de la vista.
La mejor forma de usar esta guía es seguir cada paso tal y como se muestra. No hemos ahorrado ninguna línea de código en las explicaciones, así que puedes seguirla literalmente paso a paso.
Antes de iniciar a programar veremos 2 puntos importantes.
La interfaz de línea de comandos, terminal o consola es un método que permite a los usuarios dar instrucciones a algún programa informático por medio de una línea de texto simple.
Lo primero que debemos saber es que el terminal se utiliza para dar órdenes al sistema mediante unas palabras llamadas comandos de Linux. Estos comandos pueden servir para muchas cosas, como por ejemplo: copiar archivos, comprimir carpetas, reproducir música, descargar archivos, etc.
Para que nosotros sepamos que el sistema está preparado para recibir órdenes, en el terminal aparecerá una línea de texto llamada prompt
$
Entonces cada vez que vas a encontrar referencia a la consola
en la guía, solo copia el comando y ejecútalo en la consola.
Encontrarás referencias como la siguiente para ubicar el archivo que tienes que cambiar
app/views/pages/home.html.erb
Con instrucciones y el codigo que tienes que agregar o cambiar
[...]
codigo
[...]
Para evitar instalar programas en tu computador, hemos seleccionado esta plataforma que ofrece Amazon AWS y se ejecuta en una explorador web, te provee un entorno completo de trabajo:
Dile a tu coach que te recuerde cómo usar Cloud9…
Ahora bien, dentro de la terminal de Cloud9 debes digitar el siguiente comando:
consola
cd
y luego vamos a crear una nueva carpeta, recuerda que a final de cuentas, una terminal es una interfz de usuario, pero en lugar de usar el ratón para interactuar con los archivos y carpetas, usamos el teclado, muy retro no…?
consola
mkdir aplicaciones
aquí estamos creando la carpeta aplicaciones
y es la carpeta que vamos a usar dentro de nuestro computador virtual para almacenar todo el código de nuestra aplicación.
Esta será una de las pocas palabras que usaremos en español… a Ruby on Rails le gusta más que usemos el inglés :)
Muy bien, ahora vamos a proceder a instalar la versión 2.7.7 del lenguaje de programación Ruby, es la versión más reciente con la que es compatible Cloud9, sin embargo, no es la versión más reciente del lenguaje…
Para hacerlo, digitamos el siguiente comando:
consola
rvm install 2.7.7
rvm
es un programa que ya viene pre-instalado en Cloud9 con el cual podemos instalar diferentes versiones de Ruby, al final del proceso vamos a ver algo como esto:
ruby-2.7.7 - #extracting ruby-2.7.7 to /home/ec2-user/.rvm/src/ruby-2.7.7.....
ruby-2.7.7 - #configuring.............................................................
ruby-2.7.7 - #post-configuration..
ruby-2.7.7 - #compiling............................................................................................
ruby-2.7.7 - #installing................................
ruby-2.7.7 - #making binaries executable...
Installed rubygems 3.1.6 is newer than 3.0.9 provided with installed ruby, skipping installation, use --force to force installation.
ruby-2.7.7 - #gemset created /home/ec2-user/.rvm/gems/ruby-2.7.7@global
ruby-2.7.7 - #importing gemset /home/ec2-user/.rvm/gemsets/global.gems................................................................
ruby-2.7.7 - #generating global wrappers.......
ruby-2.7.7 - #gemset created /home/ec2-user/.rvm/gems/ruby-2.7.7
ruby-2.7.7 - #importing gemsetfile /home/ec2-user/.rvm/gemsets/default.gems evaluated to empty gem list
ruby-2.7.7 - #generating default wrappers.......
ruby-2.7.7 - #adjusting #shebangs for (gem irb erb ri rdoc testrb rake).
Install of ruby-2.7.7 - #complete
Ruby was built without documentation, to build it run: rvm docs generate-ri
Para terminar este paso, debemos ahora decirle a rvm
que la version que vamos a usar por defecto será siempre la versión de RUby 2.7.7. digitando este comando:
consola
rvm default 2.7.7
Luego procederemos a instalar la ultima versión de Ruby on Rails, recuerda Ruby es el lenguaje, y Rails es un Framework que usa a Ruby para poder construir aplicaciones web asombrosas
Para hacerlo digita el siguiente comando, la opción –no-document evitará instalar la documentación, sólo por esta vez lo haremos así, para hacer la instalación más ligera:
consola
gem install rails --no-document
al final deberas ver algo como:
Successfully installed rails-7.0.4.2
37 gems installed
la versión de Rails que estaremos usando es Rails 7.0.4
Ahor vamos a instalar un par de herramientas que son necesarias como complemento de Rails en el entorno de Cloud9
consola
npm install -g yarn
consola
curl -fsSL https://esbuild.github.io/dl/v0.17.5 | sh
y luego
sudo mv esbuild /usr/bin/
consola
npm install -g sass
Por último dile a tu coach que modifique los siguientes parámetros del editor:
En la consola, y dentro de la carpeta aplicaciones
, escribímos
consola
rails new instagram -j esbuild --css bootstrap
Despues de este comando presiona enter y se va a crear una serie de archivos y dependencias organizados en carpetas que conforman la estructura de una aplicación Rails, la carpeta principal tendrá el mismo nombre de la aplcación en este caso sera: instagram
Una vez terminado puedes ver la ultima linea que dice (la cantidad de sgundos puede variar):
✨ Done in 1.56s.
Después de crear la aplicación, entra a su directorio para continuar trabajando directamente en ella:
En la consola
cd instagram
En este punto, vamos a declarar la carpeta como favorito en el editor de Cloud9, pidele ayuda a tu coach para hacer esta parte, la idea es que el ábrol de directorios sólo veas la carpeta instagram
Ahora que creamos este nuevo proyecto con los archivos necesario para iniciar un proyecto de Rails echale una mirada al archivo Gemfile.
Este archivo es la base de Rails y donde encontraras las librerias que hacen funcionar nuestra aplicación. Ademas estaremos agregando nueva librerias en el transcurso de este proyecto para agregar nueva funcionalidades a nuestro proyecto.
Una vez que tenemos la primera parte lista podemos ejecutar un nuevo comando en la consola:
rails server
Eso va a arrancar nuestro servidor de aplicación.
Deberías ver algo parecido a:
=> Booting Puma
=> Rails 7.0.4.2 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 2.7.7-p221) ("Birdie's Version")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 29530
* Listening on http://127.0.0.1:8080
* Listening on http://[::1]:8080
Use Ctrl-C to stop
Que significa que nuestra aplicacion ya esta corriendo en el navegador.
En el navegador, ve a la URL: http://localhost:8080 Esta es la página de inicio por defecto para las aplicaciones Rails.
Pero para verla usando Cloud9 debes usar el proxy de Cloud9, dado que estás usando un computador virtual en la nube, no vas a poder acceder directamente a un explorador, y es por esa razón que debemos usar el proxy que Cloud9 nos provee.
Para previsualizar nuestra aplicación debemos ir a nuestra pataforma en Cloud9, dar clic en Preview de la parte superior, y luego clic en preview running application, allí una nueva pestaña se te abrirá y para maor comodida te recomiendo que debes copiar la URL completa para llevarla a una pestaña de tu explorador web (en el que estás trabajando), la URL se debe ver similar a esta https://716cb3ec2b8244ffada0813ea864deb0.vfs.cloud9.us-east-1.amazonaws.com/
NOTA si estás usando safari, es importante que habilites el uso de cookies, o puedes igualmente iniciar sesión en otro explorador, la guía fue probada en Google Chrome.
Una vez hayas abierto esta pestaña, veras un error en color rojo, hey! es normal, no te asustes ;) es sólo un sistema de seguridad de Rails que usa para evitar que tu aplicación sea usada en dominios no autorizados.
para corregir este error deberas copiar la siguiente línea:
config.hosts << /[a-z0-9]+\.vfs\.cloud9\..*\.amazonaws\.com/
en el archivo config/environments/development.rb
justo después de la línea Rails.application.configure do
algo así:
Rails.application.configure do
config.hosts << /[a-z0-9]+\.vfs\.cloud9\..*\.amazonaws\.com/
# Settings specified here will take precedence over those in config/application.rb.
una vez añadida esta línea, deberas guardar el archivo, y reiniciar el servidor que esta corriendo en la temrinal:
CONTROL + C (para parar el servidor)
consola
rails server # (para volver a iniciar el servidor)
La página “Rails!” es la primera prueba para una nueva aplicación Rails. Ésta asegura que tienes el software configurado correctamente para servir una página.
Para crear nuestra primera pagina, necesitas crear como mínimo una ruta, un controlador y una vista.
La ruta define donde se va a encontrar nuestra pagina y quien va a tener responsabilidad de mostrar el contenido.
Esta responsabilidad es la del controlador El propósito de un controlador es recibir las peticiones (requests) de la aplicación.
A menudo, hay más de una ruta para cada controlador, y diferentes rutas pueden ser servidas por diferentes acciones (actions). El propósito de cada acción es obtener información para pasarla después a la vista.
El propósito de una vista es mostrar la información en un formato legible para los humanos. Una distinción importante que hacer es que es el controlador, y no la vista, donde la información es recolectada. La vista sólo debería mostrar la información. Por defecto, las plantillas de las vistas están escritas en un lenguaje llamado ERB (del inglés, Embedded Ruby), que se procesa automáticamente para cada petición servida por Rails.
Vamos a crear una pagina de inicio
y iniciamos creando un nuevo controlador.
NOTA El primer tab de tu consola ya tiene el servidor de Rails corriendo que lanzaste con el comando:
rails server
Para crear un nuevo controlador, debes crear una nueva pestaña en la consola y necesitas ejecutar el generador de controladores y decirle que quieres un controlador llamado por ejemplo pages
con una acción llamada home
. Para ello, ejecuta lo siguiente:
recuerda abrir un nuevo tab.
En la consola aparecerá un nuevo prompt $
donde puedes ejecutar:
rails generate controller pages home
Rails creará una serie de archivos y añadirá una ruta hacia la página por ti.
create app/controllers/pages_controller.rb
route get 'pages/home'
invoke erb
create app/views/pages
create app/views/pages/home.html.erb
invoke test_unit
create test/controllers/pages_controller_test.rb
invoke helper
create app/helpers/pages_helper.rb
invoke test_unit
Estas convenciones que estabamos hablando un poco más arriba son las que permiten definir lo que se va a crear cada vez que ejecutemos un comando de Rails. Podríamos tambien crear los archivos manualmente y escribir el codigo, pero Rails se encarga de crear el esqueleto o la estrucutra base para ahorarnos tiempo e implementar buenas prácticas basadas en la experiencia colectiva de los desarolladores que contribuyen en proyectos de codigo abiertos como Ruby on Rails.
Los archivos más importantes que acabamos de crear con el generador son por supuesto el controlador, que se encuentra en app/controllers/pages_controller.rb y la vista (HTML), que se encuentra en app/views/pages/home.html.erb.
Miremos un poco acerca de la convencion que sigue estos archivos.
La mayoría del trabajo en esta guia se llevará a cabo en la carpeta app/, y vemos que el primer archivo, el controlador, se creo en una carpeta que se llama controllers
, se llama pages_controller.rb
y tiene una accion llamada home
.
class PagesController < ApplicationController
def home
end
end
Le coresponde entonces un vista ubicada en el archivo views
con el nombre home.html.erb
que va a contener la representacion en codigo HTML de lo que queremos mostrar en el navegador.
Y todo no podría funcionar sin nuestra ruta que tambíen fue añadida con el mismo comando La puedes ver abriendo el archivo config/routes.rb
Rails.application.routes.draw do
get 'pages/home'
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
# root "articles#index"
end
NOTA El algunos archivos puedes ver un texto que impieza con el numeral #
. Este texto hace parte de los comentarios (usado como oraciones para documentar el código, y normalmente no afectan en nada al código) que se estan autogenerando que dan algunas indicaciones a a el usuarios de como seguir la convenciones de Rails.
Ahora actualizaremos el archivo de inicio. Abre el archivo app/views/pages/home.html.erb en tu editor de texto y edítalo para que contenga sólo está línea de código:
<h1>¡Bienvenidos a Instagram!</h1>
En tu navegador, ve a la URL : /pages/home y ve la nueva página que se acaba de crear.
¿Cómo conectar la raíz de tu sitio a un controlador y acción específica?
Debemos configurar la ruta de acceso a la aplicación editando el archivo config/routes.rb, para esto debemos cambiar la siguiente línea en el mismo:
config/routes.rb
Reemplazar la línea
get 'pages/home'
…por:
root 'pages#home'
Este pequeño cambio indica a Rails que en la pagina de inicio de nuestra aplicación ubicada enla direccion web / queremos mostrar la pagina home
.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
end
def about
end
end
Como puedes ver acabamos de agregar debajo de la accion Home
una nueva accion about
.
Así tenemos la estructura basica de nuestra aplicación donde tenemos una pagina de inicio y una nueva pagina about
para contar un poco sobre nosotros.
app/views/pages/about.html.erb
<h1>¿Quién soy?</h1>
<p>¡Una Rails Girl creando mi propia app de Instagram!</p>
config/routes.rb
Debajo de
root 'pages#home'
Añade:
get 'about' => 'pages#about'
Ahora en el navegador puedes visitar nuestras dos paginas:
Los desarrolladores de aplicaciones web pueden trabajar programando la lógica de la aplicación y programando la interfaz gráfica del usuario final, esta última parte es mucho más estética, y para facilitar la diagramación y el diseño de componentes gráficos usamos herramientas como Bootstrap, dile a tu coach que te acompañe en una revisión rápida de esta tecnología aquí
views/layouts/application.html.erb
Dentro de la etiqueta <body>
, reemplaza
<%= yield %>
por…
<div class="container">
<br>
<%= yield %>
</div>
NOTA ten cuidado con la indentación del código que copias y pegas, la legibilidad del código es muy importante para que no te confundas.
Por convención un parcial es un archivo de plantilla cuyo nombre empieza por un guion bajo y cuyo contenido se reutiliza en varias plantillas diferentes. Aquí el encabezado es un ejemplo de contenido que se repite en varias páginas.
Crea el archivo app/views/layouts/_header.html.erb
app/views/layouts/_header.html.erb
<nav class="navbar navbar-expand-lg ">
<div class="container">
<%= link_to "Instagram", root_path, class: "navbar-brand" %>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<%= link_to "Home", root_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Quiénes somos", about_path, class: "nav-link" %>
</li>
</ul>
</div>
</div>
</nav>
NOTA Embedded Ruby? – ¿Ruby embebido?
En Ruby se usa mucho ERB como sistema de plantillas para crear archivos HTML con código Ruby embebido.
En HTML, un enlace es así:
<a href="#"> aquí </a>
Estas son etiquetas de Ruby:
< % = % >
En Ruby on Rails un enlace se verá así:
<%= link_to "aquí", "#" %>
app/views/layouts/application.html.erb
Después de la etiqueta <body>
inserta:
<%= render 'layouts/header' %>
Reemplaza todo el contenido del archivo
views/pages/home.html.erb
<div class="jumbotron text-center">
<h2>¡Bienvenidos a Instagram!</h2>
<p>
<%= link_to "Iniciar sesión", "#", class: "btn btn-default btn-lg" %>
<%= link_to "Regístrate", "#", class: "btn btn-primary btn-lg" %>
</p>
</div>
Ahora agregamos un poco de estilos CSS (usando SASS).
app/assets/stylesheets/application.bootstrap.scss
Reemplaza:
@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';
por los siguientes estilos:
@import url(http://fonts.googleapis.com/css?family=Lato:400,700);
@import url(http://fonts.googleapis.com/css?family=Oleo+Script);
$font-family-sans-serif: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$primary: #d3252c;
$jumbotron-bg: white;
@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';
.navbar {
background-color: #fff;
font-weight: bold;
border-bottom: 1px solid #dbdbdb;
}
.navbar-brand {
font-family: 'Oleo Script', cursive;
font-size: 25px;
color: #262626;
}
.navbar-nav {
font-weight: bold;
}
“Las gemas en Ruby son las bibliotecas o paquetes (código Ruby empaquetado de una manera predeterminada) de software que se instalan en el sistema para aumentar las funcionalidades del interprete”.
La mayoria de las aplicaciones web necesitan que los usuarios puedan crear una cuenta, modificar su perfil, iniciar y cerrar sesión. En el contexto de rails hay una gema que se integra naturalmente con él y gestiona estos componentes; esta gema se llama Devise.
/Gemfile
Debajo de gem "bootsnap", require: false
añade la gema de Devise.
[...]
gem 'devise'
[...]
Ahora vamos a la pestaña en la que está corriendo el servidor rails server
, detenemos el servidor y procedemos a instalar la biblioteca añadida.
CONTROL + C
y corre bundle install
para instalar una nueva gemaEjecuta bundle install
para instalarla
CONTROL + C
consola
bundle install
NOTA pero esta vez no vamos a usar rails server
ahora vamos a usar bin/dev
la razón de usar este comando es porque nos facilitará el proceso de creación de todos los archivos necesarios para nuestra aplicación, antes sólo estabamos corriendo el servidor que permite ejecutar el código Ruby que hemos hecho hasta ahora, pero con el nuevo comando bin/dev
no sólo ejecutaremos código Ruby, sino también le permitiremos a nuestra ejecución interpretar de algún modo JS (javascript) y (SCSS/CSS), que por el momento diremos que son tecnologías que nos permiten volver nuestra aplicación web (la que ves desde el explorador) más bonita e interactiva. Así que al correr el comando bin/dev
estás corriendo implicitamente el servidor, y otros dos procesos más en una misma consola, además vas a notar cambios importantes en la interfaz gráfica, cambios que tu está generando pero que ahora los podrás ver de forma automática.
De igual forma, en Cloud9 el puerto que se está usando para ejecutar es el 8080, normalmente usa el puerto 3000, para configurar esto en el comando bin/dev
debemos editar el archivo Procfile.dev
que está en la raíz del proyecto, cambia su contenido completo por el siguiente contenido:
web: unset PORT && bin/rails server -p 8080
js: yarn build --watch
css: yarn build:css --watch
consola
bin/dev
al ejecutarlo, veras algo como lo siguiente:
22:06:30 css.1 | $ sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules --watch
22:06:30 js.1 | $ esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets --watch
22:06:31 js.1 | [watch] build finished, watching for changes...
22:06:31 web.1 | => Booting Puma
22:06:31 web.1 | => Rails 7.0.4.2 application starting in development
22:06:31 web.1 | => Run `bin/rails server --help` for more startup options
22:06:32 web.1 | Puma starting in single mode...
22:06:32 web.1 | * Puma version: 5.6.5 (ruby 2.7.7-p221) ("Birdie's Version")
22:06:32 web.1 | * Min threads: 5
22:06:32 web.1 | * Max threads: 5
22:06:32 web.1 | * Environment: development
22:06:32 web.1 | * PID: 10225
22:06:32 web.1 | * Listening on http://127.0.0.1:8080
22:06:32 web.1 | * Listening on http://[::1]:8080
22:06:32 web.1 | Use Ctrl-C to stop
22:06:37 css.1 | Compiled app/assets/stylesheets/application.bootstrap.scss to app/assets/builds/application.css.
22:06:37 css.1 | Sass is watching for changes. Press Ctrl-C to stop.
22:06:37 css.1 |
NOTA El primer tab de tu consola todavía tiene el servidor de Rails corriendo. Sigue usando el otro tab / pestaña.
Ejecuta el generador de Devise:
consola
rails generate devise:install
Acabamos de crear los archivos necesario para la configuracion de Devise. Ademas Devise nos deja una serie de recomendaciones sobre buenas practicas y configuraciones adicionales de la gema. Ahora podemos crear las vistas.
Generar las vistas para la personalización del manejo de sesiones de usuario. Estas vistas coresponden a las que necesitamos para iniciar y cerrar session, manejar registras de usuarios, cambio de contraseña…etc
consola
rails g devise:views
Y vemos que Rails acaba de crear varios archivos nuevos en nuestra aplicación.
Los modelos en Rails usan un nombre en singular, y sus correspondientes tablas de base de datos usan un nombre en plural. Rails provee un generador para crear modelos, el cual la mayoría de desarrolladores en Rails tienden a usar para crear nuevos modelos. En nustro caso Devise ofrece un generador especifico que crea el modelo de usuario definiendo la base de nustro sistema de autenticación.
consola
rails generate devise User
Esta línea crea un modelo de usuario y un nuevo archivo: app/models/user.rb
Ve a db/migrate y deberías tener en los archivos algo así como db/migrate/20230218222747_devise_create_users.rb
El número es la fecha de creacíon.
consola
rails db:migrate
Este comando toma el archivo de migración y lo ejecuta, de manera que genere tablas en la base de datos.
NOTA Las migraciones nos permiten hacer cambios sobre el esquema de la base de datos de forma iterativa y consistente. Una migración es un archivo que se crea dentro de la carpeta db/migrate y que contiene instrucciones para modificar el esquema de la base de datos (crear tablas, agregar columnas, eliminar columnas, eliminar tablas, etc.). Cuando creas un modelo desde la línea de comandos con el generador de Rails, automáticamente se crea una migración con las instrucciones para crear la tabla.
CONTROL + C (para parar el servidor)
consola
rails server # (para volver a iniciar el servidor)
Tendrás que reiniciar la aplicación cada vez que instales una gema o cada vez que ejecutes rails db:migrate
Los mensajes flash son los mensajes en sitios web que dicen “Gracias por registrarse en …” o “Gracias por suscribirse a …”
app/views/layouts/application.html.erb
Agrega lo siguiente Debajo de <div class="container">
y antes de <br>
:
<% flash.each do |name, msg| %>
<% if msg.is_a?(String) %>
<div class="alert alert-<%= name.to_s == 'notice' ? 'success' : 'danger' %> alert-dismissible fade show text-center">
<%= msg %>
</div>
<% end %>
<% end %>
Ahora vamos a complementar nuestra aplicación con un sistema básico de traducción estática, normalmente, Rails usa Inglés como su lenguaje base, pero existe una gema que nos permitirá usar el lenguaje español en los términos donde usualmente usa inglés.
Así que para instalar esta gema, debes ir al Gemfile y justo despues de la línea # gem "image_processing", "~> 1.2"
debes colocar gem 'rails-i18n'
al final deberá quedarte así:
# gem "image_processing", "~> 1.2"
# I18n
gem 'rails-i18n'
Ahora le diremos a nuestra aplicación que acepte los lenguajes español : es, e ingles : en, esto lo hacemos en el archivo config/application.rb
. Dentro de ese archivo y justo después de la línea config.load_defaults 7.0
debemos colocar lo siguiente:
config.i18n.available_locales = %w[es en]
y luego debemos ir a nuestro controlador principal app/controllers/application_controller.rb
y cambiar todo su contenido por el siguiente:
class ApplicationController < ActionController::Base
before_action :i18n_setup
protected
def i18n_setup
I18n.locale = 'es'
end
end
Con lo anterior siempre estaremos forzando ek lenguaje español en cada petición/ejecución.
Ahora, ve a la consola donde está corriendo el servidor…
CONTROL + C (para parar el servidor)
consola
luego instala la gema usando:
bundle install
y despues…
bin/dev # (para volver a iniciar el servidor)
app/views/pages/home.html.erb
¡Actualicemos la página de inicio!
<div class="jumbotron text-center">
<h2>¡Bienvenidos a Instagram!</h2>
<% if user_signed_in? %>
# haz algo
<% else %>
<p>
<%= link_to "Iniciar sesión", new_user_session_path, class: "btn btn-default btn-lg" %>
<%= link_to "Regístrate", new_user_registration_path, class: "btn btn-primary btn-lg" %>
</p>
<% end %>
</div>
app/views/layout/_header.html.erb
Reemplaza
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<%= link_to "Home", root_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Quiénes somos", about_path, class: "nav-link" %>
</li>
</ul>
por
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Rails Girls Cali</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<%= link_to "Home", root_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Quiénes somos", about_path, class: "nav-link" %>
</li>
<% if user_signed_in? %>
<li class="nav-item">
<%= link_to "Cerrar sesión", destroy_user_session_path, class: "nav-link", data: { turbo_method: :delete } %>
</li>
<% else %>
<li class="nav-item">
<%= link_to "Regístrate", new_user_registration_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Iniciar sesión", new_user_session_path, class: "nav-link" %>
</li>
<% end %>
</ul>
</div>
</div>
</nav>
NOTA Usa el tabulador, la tecla Tab (Tab ↹) nos permite indentar el texto que este en nuestra selección y así mantener el código organizado y indentado.
Cuando corrimos el comando rails g devise:views
el generador creo un serie de vistas con formularios responsable del registro de usuario, el inicio de sesion etc.
Todos estas vistas están ubicadas dentro de la carpeta app/views/devise.
Ahora vamos a mejorar un poco estos archvos agregando CSS de Bootstrap.
app/views/devise/registrations/new.html.erb
Esta vista se encarga del formulario de registro de usuarios en tu app.
<div class="form-wrapper container">
<div class="row justify-content-md-center mt-3">
<div class="col-lg-6">
<h1>Regístrate</h1>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), data: { turbo: false }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div>
<div class="form-group mt-3">
<%= f.label :password %>
<%= f.password_field :password, class: "form-control" %>
</div>
<div class="form-group mt-3">
<%= f.submit "Regístrate", class: "btn btn-success" %>
</div>
<% end %>
<div class="form-group text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
app/views/devise/registrations/edit.html.erb
Esta vista se encarga del formulario de edición de la información de usuarios en tu app.
<div class="form-wrapper container">
<div class="row justify-content-md-center mt-3">
<div class="col-lg-6">
<h1>Editar <%= resource_name.to_s.humanize %></h1>
<%= form_for(resource, :as => resource_name, url: registration_path(resource_name), data: { turbo: false }, html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", autofocus: true %>
</div>
<div class="form-group mt-3">
<%= f.label :password %> <i>(déjala en blanco si no quieres cambiarla)</i>
<%= f.password_field :password, class: "form-control", autocomplete: "off" %>
</div>
<div class="form-group mt-3">
<%= f.label :current_password %> <i>(necesitamos tu contraseña actual para confirmar los cambios)</i>
<%= f.password_field :current_password, class: "form-control" %>
</div>
<div class="form-group mt-3">
<%= f.submit "Actualizar", class: "btn btn-success" %>
</div>
<% end %>
<h3>Cancelar mi cuenta</h3>
<p>Unhappy? <%= button_to "Cancelar mi cuenta", registration_path(resource_name), data: { confirm: "¿Estás segura/o?" }, method: :delete, class: "btn btn-warning" %></p>
<%= link_to "Volver", :back %>
</div>
</div>
</div>
app/views/devise/passwords/new.html.erb
Esta vista está encargada de mostrar el formulario para la solicitud de contraseña al sistema en caso de olvido.
<div class="form-wrapper container">
<div class="row justify-content-md-center mt-3">
<div class="col-lg-6">
<h1>¿Olvidaste tu contraseña?</h1>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), data: { turbo: false }, html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", autofocus: true %>
</div>
<div class="form-group mt-3 d-grid">
<%= f.submit "Envíame las instrucciones para restablecer mi contraseña", class: "btn btn-block btn-success" %>
</div>
<% end %>
<div class="form-group text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
app/views/devise/passwords/edit.html.erb
En esta vista se encuentra el formulario para el cambio de la contraseña de un usuario de tu app
<div class="form-wrapper container">
<div class="row justify-content-md-center mt-3">
<div class="col-lg-6">
<h1>Cambia tu contraseña</h1>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), data: { turbo: false }, html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<div class="form-group">
<%= f.label :password, "Nueva contraseña" %>
<%= f.password_field :password, class: "form-control", autofocus: true %>
</div>
<div class="form-group mt-3">
<%= f.label :password_confirmation, "Confirmar nueva contraseña" %>
<%= f.password_field :password_confirmation %>
</div>
<div class="form-group mt-3">
<%= f.submit "Cambiar mi contraseña", class: "btn btn-success" %>
</div>
<% end %>
<div class="form-group text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
app/views/devise/sessions/new.html.erb
¡Este es el formulario de login (inicio de sesión) de tu Instagram app!
<div class="form-wrapper container">
<div class="row justify-content-md-center mt-3">
<div class="col-lg-6">
<h1>Iniciar Sesion</h1>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name), data: { turbo: false }) do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", autofocus: true %>
</div>
<div class="form-group mt-3">
<%= f.label :password %>
<%= f.password_field :password, class: "form-control" %>
</div>
<div class="checkbox">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<div class="form-group mt-3">
<%= f.submit "Ingresa", class: "btn btn-success" %>
</div>
<% end %>
<div class="form-group text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
app/views/devise/shared/error_messges.html.erb
¡Esta es la vista respecto de cómo se visualizan los errores!
<% if resource.errors.any? %>
<div id="error_explanation">
<h2>
Los siguientes errores están presentes:
</h2>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
app/views/layouts/_header.html.erb
Debajo de <% if user_signed_in? %>
:
<li class="nav-item">
<%= link_to "Mi cuenta", edit_user_registration_path, class: "nav-link" %>
</li>
Ya tenemos todo un sistema de registro, autenticacion de usuarios con notificaciones super poderoso. Si quieres conocer mas acerca de como configurar y usar Devise mas alla de esta guia encontraras un enlace al final de la guia.
Posts
serán nuestras publicaciones o, mejor dicho, ¡Las imágenes que publicamos en nuestro Instagram!
Un scaffold es un generador automático de modelo + controlador + vistas. Scaffold significa andamio en inglés. Es un generador de código el cual nos permite tener las funcionalidades básicas de administración de un modelo, es decir el CRUD (Create, Read, Update, Delete), y que son típicas para cualquier sistema transaccional. Entonces ya tendremos dos modelo de datos, uno para los usuarios que creamos con Devise y ahora sus respectivos posts utilizando el generador de código scaffold.
consola
rails generate scaffold posts description:string
Con este comando le estamos diciendo a Rails de crear un scaffold posts
con un campo description
que es de tipo string
. Por ahora nustra publicación de Instagram solo tiene una descripción.
Usando scaffold hemos creado todo el código necesario para un CRUD, éste nos ha creado los modelos, controladores, vistas, assets, rutas y las migraciones.
Hablando de migraciones, ahora tenemos que ejecutar la migración.
consola
rails db:migrate
NOTA Las migraciones nos permiten hacer cambios sobre el esquema de la base de datos de forma iterativa y consistente. Una migración es un archivo que se crea dentro de la carpeta db/migrate y que contiene instrucciones para modificar el esquema de la base de datos (crear tablas, agregar columnas, eliminar columnas, eliminar tablas, etc.). Cuando creas un modelo desde la línea de comandos con el generador de Rails, automáticamente se crea una migración con las instrucciones para crear la tabla.
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy ]
# GET /posts or /posts.json
def index
@posts = Post.all
end
# GET /posts/1 or /posts/1.json
def show
end
# GET /posts/new
def new
@post = Post.new
end
# GET /posts/1/edit
def edit
end
# POST /posts or /posts.json
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to post_url(@post), notice: "El post fue exitosamente creado." }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1 or /posts/1.json
def update
respond_to do |format|
if @post.update(post_params)
format.html { redirect_to post_url(@post), notice: "El post fue exitosamente actualizado." }
format.json { render :show, status: :ok, location: @post }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1 or /posts/1.json
def destroy
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: "El post fue exitosamente eliminado." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow a list of trusted parameters through.
def post_params
params.require(:post).permit(:description)
end
end
Las plantillas parciales (partials) son una forma de dividir el proceso de representación en partes más manejables. Los parciales permiten extraer fragmentos de código de sus plantillas en archivos separados y también reutilizarlos en todas sus plantillas. Los parciales tienen como prefijo un subrayado, de manera de no confundirlas con vistas regulares
Modifica el parcial del formulario apps/views/posts/_form.html.erb
Reemplaza todo el contenido por:
<%= form_with(model: post) do |form| %>
<% if post.errors.any? %>
<div style="color: red">
<h2><%= pluralize(post.errors.count, "error") %> están evitando que el post sea guardado:</h2>
<ul>
<% post.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= form.label :description %>
<%= form.text_field :description, class: "form-control" %>
</div>
<div class="form-group mt-3">
<%= form.submit class: "btn btn-primary" %>
</div>
<% end %>
Cambia todo lo que hay en este archivos: app/views/posts/new.html.erb por:
<div class="form-wrapper">
<h2>Crear un post</h2>
<%= render "form", post: @post %>
</div>
<div>
<%= link_to "Back to posts", posts_path %>
</div>
Y lo que hay en este archivo app/views/posts/edit.html.erb por
<div class="form-wrapper">
<h2>Actualizar un post</h2>
<%= render "form", post: @post %>
</div>
<div class="text-center edit-links">
<%= link_to "Mirá este post", @post %> |
<%= link_to "Eliminar este Post", post_path(@post), data: { turbo_method: :delete, turbo_confirm: "¿Estás segura que quieres eliminar este post?" } %> |
<%= link_to "Lista de posts", posts_path %>
</div>
app/views/layouts/_header.html.erb
Debajo de <% if user_signed_in? %>
:
<li class="nav-item">
<%= link_to 'Nuevo post', new_post_path, class: "nav-link"%>
</li>
index
de Postsreemplaza root 'pages#home'
por root 'posts#index'
Las asociaciones se utilizan para definir relaciones entre tablas de una base de datos. Existen dos tipos de asociaciones que se pueden modelar en una base de datos relacional:
En nustro ejemplo usaremos One to many (uno a muchos)
En una relación uno a muchos cada registro de una tabla está relacionado a un registro de otra tabla. Por ejemplo, imagina que cada publicación pertenece a un usuario.
Un Post belongs_to
un Usuario.
Una Publicación pertenece a un Usuario.
En el model Post
app/models/post.rb remplaza el contenido por:
class Post < ApplicationRecord
belongs_to :user
end
Esto significa que cada publicación estará relacionada con un usuario.
consola
rails generate migration add_user_id_to_posts user_id:integer:index
Y despues
rails db:migrate
rails db:migrate
porque acabamos de crear una migración.TODO: * ##### Cada vez que hagas una migración, debes reiniciar el servidor de Rails.
consola
CONTROL + C (para parar el servidor)
bin/dev # (para volver a iniciar el servidor)
has_many
PostsEn el modelo User
app/models/user.rb remplaza el contenido por:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
end
Para evitar que un Post pueda ser guardado sin descripción, podemos usar la validaciones de Ruby on Rails, para usarlas añade al archivo models/post.rb
la línea validates :description, presence: true
justo después de la línea belongs_to :user
. Quedando finalmente el archivo post.rb
así:
class Post < ApplicationRecord
belongs_to :user
validates :description, presence: true
end
NOTA: intenta interactuar hasta este punto con la aplicación, creando o editando Posts, es posible que te encuentres con algunos errores, los abordaremos más adelante :)
Una vez que un usuario tiene acceso a la plataforma, hay que ver los permisos que este usuario tiene.
Tenemos que asegurar que un usuario no puede cambiar la publicación de otro usuario.
También que las acciones (crear, cambiar o borrar una publicación) se puede hacer solamente si estas autenticado.
Ahora vamos a cambiar el codigo para que un usuario pueda crear, editar y borrar sus publicaciones y no aceder a todas.
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy ]
# GET /posts or /posts.json
def index
@posts = Post.all
end
# GET /posts/1 or /posts/1.json
def show
end
# GET /posts/new
def new
@post = current_user.posts.build
end
# GET /posts/1/edit
def edit
end
# POST /posts or /posts.json
def create
@post = current_user.posts.build(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to post_url(@post), notice: "El post fue exitosamente creado." }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1 or /posts/1.json
def update
respond_to do |format|
if @post.update(post_params)
format.html { redirect_to post_url(@post), notice: "El post fue exitosamente actualizado." }
format.json { render :show, status: :ok, location: @post }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1 or /posts/1.json
def destroy
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: "El post fue exitosamente eliminado." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
def correct_user
@post = current_user.posts.find_by(id: params[:id])
redirect_to posts_path, notice: "¡No tienes permisos para editar este post!" if @post.nil?
end
# Only allow a list of trusted parameters through.
def post_params
params.require(:post).permit(:description)
end
end
Devise crea algunos métodos de ayuda y filtros para que puedas manejar la autenticación en tu aplicación. El filtro que ppodemos agregar sobre los controladores que quieres proteger es el siguiente:
before_action :authenticate_user!
En nuestra aplicación vamos a proteger todas las acciones del controlador posts exceptuando index
y show
que son las que nos van a permitir listar y mostrar los detalles de una publicación.
Añade before_action
al Controlador Posts
app/controllers/posts_controller.rb
Debajo de before_action :set_post ...
before_action :authenticate_user!, except: %i[index show]
:correct_user
Añade el método before_action
a tu controlador de Posts
app/controllers/posts_controller.rb
Debajo de before_action :authenticate_user! ...
before_action :correct_user, only: %i[edit update destroy]
Con tu coach trata de crear nuevos usuarios y posts, luego trata de editar la información de los posts con diferentes usuarios.
NOTA sólo si ves este error undefined method
user_url’ for #<Devise::RegistrationsController:0x0000000000….>` comunicate con tu coach, el problema es debido a una incompatibilidad de redireccionamiento entre Devise y un sistema interno de Ruby on Rails llamado Turbo.
la solución es sencilla, sólo debes añadir la siguiente línea de código al bloque de configuración del archivo config/initializers/devise.rb
config.navigational_formats = ['*/*', :html, :turbo_stream]
Tu coach puede ver más información al respecto aquí
ActiveStorage permite a los desarrolladores gestionar la carga de archivos, el almacenamiento en la nube y la gestión de documentos vinculados a modelos en todas sus aplicaciones. En nustro ejemplo, Active Storage nos permite manejar la carga de imagen de cada publicación.
Ejecuta el comando de instalación:
consola
rails active_storage:install
Y despues
rails db:migrate
Con estos comandos acabamos de instalar Active Storage y actualizar la base de datos con campos necesarios para almacenar archivos en nustra aplicación.
Para poder lidiar con imágenes, es bueno tener un editor de imágenes, algo como Photoshop pero en consola (just kiding), por el momento usaremos ImageMagick, y debemos instalarlo, como estamos trabajando con Cloud9 preferiremos usar ImageMagick en lugar de VIPS que por rendimiento es más recomendado.
para instalarlo, en una consola debes digitar el siguiente comando, y aceptar la instalación, dile a tu coach que supervise esta instalación:
consola
sudo yum install ImageMagick
Ahora necesitamos instalar un libreria para procesar nuestras imagenes con las caracteriticas y el tamaño de las imagenes que se suben a Instagram, entonces agregaremos la gema image_processing
.
Dentro del archivo /gemfile busca la linea gem 'image_processing', '~> 1.2'
y quitale el #
para que se vea de la siguiente forma:
# Use Active Storage variant
gem "image_processing", "~> 1.2"
Acabamos simplemente de remover un character que significa que esta linea es un comentario.
Ahora esta linea es parte del codigo y podemos ejecutar bundle install
para instalar esta nueva gema.
consola
CONTROL + C (para parar el servidor)
bundle install
Y despues
bin/dev
Para iniciar el servidor otra vez.
Actualiza el model Post
/app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
validates :description, presence: true
has_one_attached :image
def user_name
user.email
end
def squared_img
return 'https://via.placeholder.com/600' unless image.attached?
image.variant(resize_to_fill: [600, 600])
end
end
Aquí hemos añadido dos nuevos métodos user_name
squared_img
, qué crees que hacen esos dos métodos? discútelo con tu coach…
para parender más del redimensionamiento de imágenes ver aquí
Finalmente, para forzar a Rails usar ImageMagick y no VIPS, debemos añadir esta línea config.active_storage.variant_processor = :mini_magick
justo después de la línea config.active_storage.service = :local
en el siguiente archivo config/environments/development.rb
/app/views/posts/_form.html.erb
Justo despues de
<div class="form-group">
<%= form.label :description %>
<%= form.text_field :description, class: "form-control" %>
</div>
Añade:
<div class="form-group mt-3">
<%= form.label :image %>
<%= form.file_field :image %>
</div>
/app/controllers/posts_controller.rb
def post_params
.
def post_params
params.require(:post).permit(:description, :image)
end
.
Aqui estamos permitiendo que nuestro controlador pueda manejar un parametro adicional que se llama image
.
index
de Posts/app/views/posts/index.html.erb
<% if user_signed_in? %>
<div class="main-wrapper posts">
<div class="row">
<% @posts.each do |post| %>
<div class="col-xs-12 col-sm-6 col-md-4">
<div class="image center-block">
<%= link_to (image_tag post.squared_img, class:'img-fluid'), post_path(post) %>
</div>
<p class="text-end small text-muted mb-0">
<%= time_ago_in_words post.created_at %>
</p>
<p class="description">
<b>
<%= post.user_name if post.user %>
</b>
<%= truncate(strip_tags(post.description.to_s), length: 70) %>
</p>
</div>
<% end %>
</div>
</div>
<% else %>
<div class="jumbotron text-center">
<h1>¡Bienvenidos a Instagram!</h1>
<p>
<%= link_to "Iniciar sesión", new_user_session_path, class: "btn btn-default btn-lg" %>
<%= link_to "Regístrate", new_user_registration_path, class: "btn btn-primary btn-lg" %>
</p>
</div>
<% end %>
show
de Posts/app/views/posts/_post.html.erb
<div id="<%= dom_id post %>" class="posts-wrapper row">
<div class="post">
<div class="image center-block">
<%= image_tag post.squared_img, class:'img-responsive' %>
</div>
<div class="post-head">
<div class="name">
<%= post.user.email if post.user %>
</div>
</div>
<p class="description">
<%= post.description %>
</p>
</div>
</div>
Ahora vamos a colocar más bonito nuestra aplicación especialmente cuando un Post es presentado
Vamos a crear el archivo app/assets/stylesheets/post.css.scss
con el siguiente contenido:
.main-wrapper {
margin-top: 100px
}
.posts-wrapper {
padding-top: 40px;
margin: 0 auto;
max-width: 642px;
width: 100%;
}
.post {
background-color: #fff;
border-color: #edeeee;
border-style: solid;
border-radius: 3px;
border-width: 1px;
margin-bottom: 60px;
}
.post-head {
height: 64px;
padding: 14px 20px;
color: #125688;
font-size: 15px;
line-height: 18px;
.thumbnail {}
.name {
display: block;
}
}
.image {
text-align: center;
padding-top: 20px;
}
.description {
padding: 24px 24px;
font-size: 15px;
line-height: 18px;
}
En este caso, nos estamos saliendo del archivo principal de estilos, esto es una decisión comun dentro del desarrollo de software para no tener un súper archivo lleno de muchas líneas de código… lo que hacemos es dividir ese archivo en partes que tengan más sentido (divide y venceras)m y en este caso hemos creado otro archivo de estilo sólo para volver más bonitos los posts.
Sin embargo, en Ruby on Rails, debemos una sola vez hacer tres pasos más cuando tomamos esta decisión:
post.css.scss
, para esto vamos a reemplazar el contenido completo del archivo app/assets/config/manifest.js
por://= link_tree ../images
//= link_tree ../builds
//= link post.css
Aquí pueden notar que usamos post.css
en lugar de post.css.scss
esto es porque al final del día un explorador web sólo entiende CSS, JS y HTML, y SCSS (o SASS) sólo lo usamos para poder escribir los estilos mucho más rápido, SCSS es entonces un meta-lenguaje que nos ayuda a escribir de forma más ordenada reglas de estilo, pero como el explorador web no lo entiende debemos transformar ese SCSS a CSS y enlazarlo así, al final.
# gem "sassc-rails"
y quitarle el “#” del principio, para al final tener la siguiente línea (aquí estamos descomentando una línea de código):# Use Sass to process CSS
gem "sassc-rails"
Ahora esta linea es parte del codigo y podemos ejecutar bundle install
para instalar esta nueva gema.
consola
CONTROL + C (para parar el servidor)
bundle install
Y despues
bin/dev
Para iniciar el servidor otra vez.
Deste modo debes ir al archivo app/views/layouts/application.html.erb
y abajo de la línea <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
colocar:
<%= stylesheet_link_tag "post", "data-turbo-track": "reload" %>
app/views/posts/show.html.erb
cambiamos el contenido de todo el archivo por el siguiente:
<%= render @post %>
<div class="text-center edit-links">
<%= link_to "Editar este post", edit_post_path(@post) %> |
<%= link_to "Lista de posts", posts_path %>
</div>
y añade los siguientes estilos al final de este archivo
app/assets/stylesheets/post.css.scss
.edit-links {
margin-top: 20px;
margin-bottom: 40px;
}
consola
rails generate migration AddNameToUsers name:string
consola
rails db:migrate
consola
CONTROL + C
bin/dev
Actualiza app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :i18n_setup
protected
def i18n_setup
I18n.locale = 'es'
end
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end
end
app/views/devise/registrations/new.html.erb, app/views/devise/registrations/edit.html.erb
Justo antes de
<div class="form-group">
<%= f.label :email %>
Añade
.
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, autofocus: true, class: "form-control" %>
</div>
.
RETO añade la clase de estilo “mt-3” al form-group
del email, pregúntale a tu coach cómo hacerlo y qué significa
app/models/post.rb
Reemplaza
user.email
``
con...
```ruby
user.name || user.email
De esta manera sólo se pueden ver tus posts. Para poner esto de otra manera: Un usuario sólo puede editar (o borrar) sus posts (y no los posts de otros usuarios ). ¿Tiene algun sentido?
app/views/posts/show.html.erb
Reemplaza
<div class="text-center edit-links">
<%= link_to "Editar este post", edit_post_path(@post) %> |
<%= link_to "Lista de posts", posts_path %>
</div>
con
<% if @post.user == current_user %>
<div class="text-center edit-links">
<%= link_to "Editar este post", edit_post_path(@post) %> |
<%= link_to "Lista de posts", posts_path %>
</div>
<% else %>
<div class="text-center edit-links">
<%= link_to "Lista de posts", posts_path %>
</div>
<% end %>
consola
rails g model Comment user:references post:references content:text
Este comando nos genera una migración db/migrate/ para añadir los campos en la base de datos y un modelo app/model/comment.rb.
consola
rails db:migrate
consola
CONTROL + C
rails server
Ahora nuestro modelo app/model/comment.rb debe estar configurado para indicar a quién pertenecen los comentarios.
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
end
Aquí estas lineas indican que un comentario pertenece a un user
y también pertenece a un post
.
Por último, en app/models/user.rb and app/models/post.rb añade la siguiente línea a cada uno:
has_many :comments, dependent: :destroy
En config/routes.rb Reemplaza
resources :posts
con…
resources :posts do
resources :comments
end
consola
rails g controller comments
El controlador de comentarios solo va a tener las acciones de crear y borrar comentarios.
Actalualiza app/controllers/comments_controller.rb con:
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
@comment.user_id = current_user.id
if @comment.save
flash[:success] = "¡Has comentado este post!"
redirect_back fallback_location: root_path
else
flash[:alert] = "Revisa el formulario de comentarios, algo salió mal :/"
render root_path
end
end
def destroy
@comment = @post.comments.find(params[:id])
@comment.destroy
flash[:success] = "Comentario eliminado :("
redirect_back fallback_location: root_path
end
private
def comment_params
params.require(:comment).permit(:content)
end
def set_post
@post = Post.find(params[:post_id])
end
end
Reemplaza app/views/posts/_post.html.erb
<div id="<%= dom_id post %>" class="posts-wrapper row">
<div class="post">
<div class="image center-block">
<%= image_tag post.squared_img, class:'img-fluid' %>
</div>
<div class="post-bottom">
<div class="post-head">
<div class="avatar">
<%= image_tag "https://robohash.org/#{post.user&.email}.png?set=set4" %>
</div>
<div class="user-name">
<%= post.user_name if post.user %>
</div>
<div class="time-ago">
<%= l post.created_at, format: :short %>
</div>
</div>
<div class="description">
<%= post.description %>
</div>
<hr>
<div id="comments">
<% post.comments.each do |comment| %>
<%= render "comments/comment", comment: comment %>
<% end %>
</div>
</div>
<%= render "comments/new", post: post %>
</div>
</div>
ahora vamos a añadir dos parciales para lidiar con la creación y visualización de los comentarios, recuerda divide y venceras, dile a tu coach que te explique el concepto de parcial y el rol del helper render
en la vista de _post.html.erb
en la carpeta app/views/comments vamos a crear dos archivos con el siguiente contenido
app/views/comments/_comment.html.erb
<div id="<%= dom_id comment %>" class="comment">
<div class="user-name">
<%= comment.user.name %>
</div>
<div class="comment-content">
<%= comment.content %>
</div>
<div class="time-ago">
<%= time_ago_in_words comment.created_at %>
</div>
</div>
app/views/comments/_new.html.erb
<% if current_user %>
<div class="comment-like-form row">
<div class="comment-form col">
<%= form_for [post, post.comments.new] do |f| %>
<div class="form-group">
<%= f.text_field :content, class: 'form-control', placeholder: 'Añade un comentario...' %>
</div>
<% end %>
</div>
</div>
<% end %>
En app/javascript/stylesheets/post.css.scss reemplaza todo el contenido con lo siguiente:
.main-wrapper {
margin-top: 15px
}
.image {
text-align: center;
padding-top: 20px;
}
.posts-wrapper {
padding-top: 40px;
margin: 0 auto;
max-width: 642px;
width: 100%;
}
.post {
background-color: #fff;
border-color: #edeeee;
border-style: solid;
border-radius: 3px;
border-width: 1px;
margin-bottom: 60px;
.post-head {
flex-direction: row;
height: 64px;
color: #125688;
font-size: 15px;
line-height: 18px;
margin-bottom: 15px;
.avatar {
display: inline;
img{
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid #d62d2d;
}
}
.user-name, .time-ago {
display: inline;
margin-left: 5px;
}
.user-name {
font-weight: 800;
}
.time-ago {
color: #A5A7AA;
float: right;
}
}
}
.post-bottom {
.user-name, .comment-content {
display: inline;
}
.description {
color: #555;
margin-bottom: 15px;
}
.user-name {
font-weight: 500;
margin-right: 0.3em;
color: #125688;
font-size: 15px;
}
.user-name, .caption-content {
display: inline;
}
.comment {
margin-top: 7px;
.user-name {
font-weight: 800;
margin-right: 0.3em;
}
.time-ago {
text-align: right;
color: #777;
font-size: 10px;
}
padding-bottom: 15px;
}
margin-bottom: 7px;
padding-top: 24px;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 10px;
font-size: 15px;
line-height: 18px;
}
.comment_content {
font-size: 15px;
line-height: 18px;
border: medium none;
width: 100%;
color: #4B4F54;
}
.comment-like-form {
padding-top: 24px;
padding-bottom: 24px;
margin-left: 24px;
margin-right: 24px;
min-height: 68px;
align-items: center;
border-top: 1px solid #EEEFEF;
flex-direction: row;
justify-content: center;
}
.form-wrapper {
width: 60%;
margin: 20px auto;
background-color: #fff;
padding: 40px;
border: 1px solid #eeefef;
border-radius: 3px;
}
.edit-links {
margin-top: 20px;
margin-bottom: 40px;
}
Tal vez lo has notado, pero cada vez que haces un nuevo cambio, es necesario que refresques la página, este es un comportamiento de la web tradicional, sin embargo, actualmente para no perder la atención del usuario final, es importante mantenerlo con el nuevo contenido aún sin tener que refrescar la página manualmente.
Vamos a realizar está configuración para cada comentario nuevo, cada comentario que se cree se publicará en los exploradores de otros usuarios sin que estos tengan que refrescar su página, y lo vamos a hacer de manera muy sencilla con sólo dos líneas más de código…
app/views/posts/show.html.erb
añade la línea <%= turbo_stream_from @post %>
app/models/comment.rb
justo despues de la línea belongs_to :post
añade la línea broadcasts_to :post
NOTA debes reiniciar el servidor
Abre dos exploradores con la aplicación, en ambos exploradores ingresa a un post existente (si no existe uno, lo debes crear), crea un comentario en uno de ellos, y veras que en el otro explorador sin necesidad de refrescar la página estará publicado el comentario nuevo, mágico verdad?
Cada vez que creamos un nuevo post debemos definir una descripción, pero actualmente la descripción sólo puede tener texto plano, hagamos esto más divertido colocándole texto enriquecido:
Para hacerlo debemos usar la característica de Rails llamada ActionText, para instalarla debemos ir a una consola y digitar:
consola
rails action_text:install
y luego correr las migraciones
rails db:migrate
NOTA: reinicia el servidor
En el archivo views/posts/_form.html.erb cambia la siguiente línea
<%= form.text_field :description, class: "form-control" %>
por esta línea:
<%= form.rich_text_area :description, class: "form-control" %>
y añade esta línea has_rich_text :description
justo debajo de la línea validates :description, presence: true
en el archivo/modelo Post app/models/post.rb
Ahora ve y trata de crear un nuevo Post, veras que lo vas a poder hacer usando un editor de texto, donde también podrás añadir imágenes al instante!
–
Aquí están algunos recursos utiles para poder seguir aprediendo. Algunos sirvieron también en la elaboración de esta guía.
Introducción a Rails - codigofacilito http://rubysur.org/introduccion.a.rails/
MVC (Model, View, Controller) Explicado - codigofacilito: https://codigofacilito.com/articulos/mvc-model-view-controller-explicado
Asociaciones - Make it Real: https://makeitrealcamp.gitbook.io/ruby-on-rails-5/asociaciones
Make it Real - Ruby on Rails: https://guias.makeitreal.camp/ruby-on-rails-i
Ruby on Rails: El desarrollo web que no molesta: http://rubyonrails.org.es/
Introducción a Ruby on Rails: https://uniwebsidad.com/libros/introduccion-rails
Tutorial de Ruby on Rails (3a. edición.) - Michael Hartl https://spanish.railstutorial.org/book