Tutorial Tienda Virtual
https://github.com/railsgirls-cali/example-store-2017
https://railsgirls-cali-store-2017.herokuapp.com/
Puedes ver la guía más detallada en Google Docs aquí.
En la terminal:
rails new tienda
cd tienda
bundle install
En la terminal:
rails server
En el navegador, ve a la URL: localhost:3000 . Esta es la página de inicio por defecto para las aplicaciones Rails.
Como necesitamos una tienda, pues quisiéramos tener algo para vender en ella, lo cual vamos a llamar “productos” o “products” en inglés.
Usaremos la funcionalidad scaffold de Rails para generar un código base que nos permita listar, añadir, eliminar, editar y ver objetos, en nuestro caso productos.
En la terminal:
rails generate scaffold product name:string description:text picture:string price:float quantity:integer
Al correr el generador, vemos que rails de nuevo ha generado otro montón de archivos, todos ellos relacionados con lo que le llamamos productos (products).
El código que hemos generado ha debido de crear una página básica dedicada a gestionar productos (products).
Si vamos a localhost:3000/products vamos a tener nuestro primer error :(
Para solucionarlo, en la terminal:
rake db:migrate
Este comando nos permite crear la tabla de productos en la base de datos dónde vamos a almacenar toda la información de la tienda.
Para asegurarnos que todos nuestros productos tengan un foto vamos a agregar una validación al “Product”:
class Product < ApplicationRecord
validates_presence_of :picture
end
Cómo mencionamos en el paso 2, Rails crea una página de inicio por defecto. Cambiemos nuestra página de inicio para que sea el listado de productos de nuestra tienda:
root :to => 'products#index'
Por defecto en vez de la foto se está mostrando la URL. Modifiquemos la lista de productos y el detalle:
Lista de prodcutos: app/views/products/index.html.erb
<p id="notice"><%= notice %></p>
<h1>Listado de Productos</h1>
<ul>
<% @products.each do |product| %>
<li>
<h3><%= product.name %></h3>
<%= link_to image_tag(product.picture, width: '200'), product %>
<%= number_to_currency product.price, precision: 0 %>
<%= link_to 'Detalle', product %></li>
<% end %>
</ul>
<p>
<%= link_to 'New Product', new_product_path %>
</p>
Detalle del producto: app/views/products/show.html.erb
<p id="notice"><%= notice %></p>
<h1><%= @product.name %></h1>
<%= link_to image_tag(@product.picture, width: 300), @product %>
<p>
<%= @product.description %>
</p>
<p>
<strong>Precio:</strong>
<%= number_to_currency @product.price, precision: 0 %>
</p>
<p>
<strong>Cantidades disponibles:</strong>
<%= @product.quantity %>
</p>
<%= link_to 'Regresar a productos', products_path %>
<%= link_to 'Eliminar producto', product_path(@product), method: :delete %>
Para comprar un producto vamos a tener que “tocar” todo el MVC y las rutas.
Agregamos al archivo config/routes.rb
una nueva ruta:
get 'products/:id/purchase', to: 'products#purchase', as: :purchase_product
Lo siguiente es modificar el controlador app/controllers/products_controller.rb
agregando la acción comprar (purchase):
def purchase
@product = Product.find(params[:id])
@product.decrement_quantity
@product.save!
respond_to do |format|
format.html { redirect_to @product, notice: 'Producto comprado' }
end
end
Añadimos al modelo app/models/product.rb
un nuevo método:
def decrement_quantity
self.quantity -= 1
end
Por último en app/views/products/show.html.erb
, agregamos un botón después del precio:
<h3>
<% if @product.quantity > 0 %>
<%= link_to 'Comprar', purchase_product_path(@product), style: "color:blue" %>
<% else %>
<%= link_to 'Lo lamentamos, se han acabado las unidades disponibles', products_path, style: "color:blue" %>
<% end %>
</h3>
Prueba haciendo click en un producto para ver el detalle y presionando el botón comprar para ver como se decrementan las unidades disponibles.
La aplicación no luce muy bien todavía. Vamos a hacer algo con esto. Usaremos el proyecto Bootstrap para darle estilo en forma fácil.
Agregamos una nueva gema (libreria) config/Gemfile
:
gem 'bootstrap-sass', '~> 3.3.6'
Cambiamos la extensión del archivo app/assets/stylesheets/application.css
por scss
y reemplazmos su contenido por:
@import "bootstrap-sprockets";
@import "bootstrap";
@import "products";
@import "scaffolds";
Nota: Debemos reiniciar el servidor después de hacer este cambio.
Ahora agregamos los estilos.
Agregamos al archivo app/assets/stylesheets/application.scss
:
.navbar-default {
background-color: #d3360b;
a {
color: white !important;
}
.navbar-brand {
font-size: 24px;
}
}
Sustituimos completamente el archivo app/assets/stylesheets/products.scss
:
.product-cont {
text-align: center;
h3 a {
color: #d3360b;
margin-bottom: 12px;
}
a:hover {
background: none;
}
p {
margin-top: 10px;
font-size: 16px;
}
}
.product-details {
h1 {
color: #d3360b;
margin-bottom: 24px;
}
p {
font-size: 18px;
}
.description {
margin-top: 100px;
}
}
Sustituimos completamente el archivo app/assets/stylesheets/scaffolds.scss
:
body {
background-color: #fff;
color: #333;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
}
p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
}
pre {
background-color: #eee;
padding: 10px;
font-size: 11px;
}
a {
color: #000;
&:visited {
color: #666;
}
&:hover {
color: #fff;
background-color: #000;
}
}
div {
&.field, &.actions {
margin-bottom: 10px;
}
}
#notice {
color: green;
}
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
#error_explanation {
width: 450px;
border: 2px solid red;
padding: 7px;
padding-bottom: 0;
margin-bottom: 20px;
background-color: #f0f0f0;
h2 {
text-align: left;
font-weight: bold;
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
margin-bottom: 0px;
background-color: #c00;
color: #fff;
}
ul li {
font-size: 12px;
list-style: square;
}
}
También tenemos que actualizar las vistas para aplicar estos estilos.
Primero actualizamos el “encuadre” de la aplicación. Sustituimos completamente el archivo app/views/layouts/application.html.erb
:
<!DOCTYPE html>
<html>
<head>
<title>Tienda Rails Girls Cali 2017</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= render 'layouts/header' %>
<% if notice.present? %>
<div id="notice" class="alert alert-success alert-dismissible">
<%= notice %>
</div>
<% end %>
<div class="container-fluid">
<%= yield %>
</div>
</body>
</html>
Como se pueden dar cuenta en el layout de la aplicación, estamos renderizando un archivo header
(la cabecera). El contendio de este archivo será incluido en el layout.
Entonces creamos un nuevo archivo app/views/layouts/_header.html.erb
, con el siguiente contenido:
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%= link_to 'Tienda Rails Girls Cali', root_path, class: "navbar-brand" %>
</div>
</div>
</nav>
Además del layout debemos actualizar las demás vistas de productos.
Para esto vamos a reemplazar todo el código de app/views/products/index.html.erb
, show.html.erb
, new.html.erb
, edit.html.erb
y form.html.erb
.
Vamos al repositorio para copiar el código de estas vistas.
Para hacer la aplicación más interesante y útil, sería ideal poder buscar por los productos de manera fácil y rápida desde cualquier página.
Para ello, vamos a colocarle un campo de búsqueda en la cabecera (header) que acabamos de poner.
<%= form_tag search_products_path, class: 'navbar-form navbar-left pull-right',
role: 'search', method: :get do %>
<div class="form-group">
<%= text_field_tag :product_name, @product_name, class: 'form-control',
placeholder: 'Buscar productos' %>
</div>
<%= submit_tag 'Buscar', class: 'btn btn-default' %>
<% end %>
Ahora agregamos una nueva ruta para las búsquedas, en el archivo config/routes.rb
modificamos:
resources :products
por
resources :products do
get 'search', on: :collection
end
Esto hará que nuestra aplicación responda a la url /products/search
(que es lo mismo que el search_products_path que se ve en el código agregado) que es a donde nuestro formulario enviará los datos ingresados en el campo de texto de búsqueda.
Agregamos la nueva acción “buscar” al controlador app/controllers/products_controller.rb
:
def search
@product_name = params[:product_name]
@products = Product.search(@product_name)
end
También debemos ir a nuestro modelo de Producto (que se comunica con la base de datos) app/models/product.rb
y agregar la lógica de búsqueda como tal:
def self.search(term)
self.where("lower(name) LIKE lower(?)", "%#{term}%")
end
Lo que hemos escrito significa que va a buscar los productos por nombre, sin importar mayúsculas o minúsculas.
Por último creamos la vista app/views/products/search.html.erb
y colocamos lo siguiente:
<h1><%= "Resultados de productos: #{@product_name}" %></h1>
<br>
<br>
<ul>
<% @products.each do |product| %>
<li>
<h3><%= product.name %></h3>
<%= link_to image_tag(product.picture, width: '200'), product %>
<%= number_to_currency product.price, precision: 0 %>
<%= link_to 'Detalle', product %></li>
<% end %>
</ul>
<%= link_to 'Regresar a productos', products_path %>
Refresca la página y escribe una palabra o parte de ella en la caja de búsqueda para ver los resultados de productos.
En esta parte vamos a realizar las funcionalidades de crear, editar y eliminar productos. Esto lo vamos a hacer en una ruta diferente a la que acceden los usuarios normales, que es la ruta del administrador.
Primero vamos a crear las siguientes carpetas app/controllers/admin
, app/views/admin
y app/views/admin/products
.
Dentro de config/routes.rb
agregamos:
namespace :admin do
resources :products
end
Una vez tenemos la ruta vamos a crear el controlador app/controllers/admin/products_controller.rb
:
class Admin::ProductsController < ApplicationController
def index
@products = Product.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @products }
end
end
def new
@product = Product.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: @product }
end
end
def edit
@product = Product.find(params[:id])
end
def create
@product = Product.new(product_params)
respond_to do |format|
if @product.save
format.html { redirect_to admin_products_url, notice: 'Producto creado.' }
format.json { render json: @product, status: :created, location: @product }
else
format.html { render action: "new" }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
def update
@product = Product.find(params[:id])
respond_to do |format|
if @product.update_attributes(product_params)
format.html { redirect_to admin_products_url, notice: 'Producto actuaizado.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
def destroy
@product = Product.find(params[:id])
@product.destroy
respond_to do |format|
format.html { redirect_to admin_products_url, notice: 'Producto eliminado.'}
format.json { head :no_content }
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:name, :description, :picture, :price, :quantity)
end
end
El siguiente y último paso es actulizar las vistas.
Eliminámos los archivos app/views/products/_form.html.erb
, app/views/products/edit.html.erb
y app/views/products/new.html.erb
.
Creamos el archivo app/views/admin/products/_form.html.erb
:
<%= form_for [:admin, @product] do |f| %>
<% if @product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% @product.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<label>Nombre:</label><br>
<%= f.text_field :name %>
</div>
<div class="field">
<label>Descripción:</label><br>
<%= f.text_area :description %>
</div>
<div class="field">
<label>Imágen:</label><br>
<%= f.text_field :picture %>
</div>
<div class="field">
<label>Precio:</label><br>
<%= f.text_field :price %>
</div>
<div class="field">
<label>Cantidad:</label><br>
<%= f.number_field :quantity %>
</div>
<div class="actions">
<%= f.submit 'Guardar' %>
</div>
<% end %>
Creamos el archivo app/views/admin/products/edit.html.erb
:
<h1>Editar Producto</h1>
<%= render 'form' %>
<%= link_to 'Regresar', admin_products_path %>
Creamos el archivo app/views/admin/products/new.html.erb
y colocamos en el:
<h1>Crear Producto</h1>
<%= render 'form' %>
<%= link_to 'Regresar', admin_products_path %>
Creamos el archivo app/views/admin/products/index.html.erb
:
<p id="notice"><%= notice %></p>
<h1>Listado de Productos</h1>
<table>
<thead>
<tr>
<th>Picture</th>
<th>Nombre</th>
<th>Descripción</th>
<th>Precio</th>
<th>Cantidad</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @products.each do |product| %>
<tr>
<td><%= image_tag product.picture, width: '50' %></td>
<td><%= product.name %></td>
<td><%= product.description %></td>
<td><%= number_to_currency product.price, precision: 0 %></td>
<td><%= product.quantity %></td>
<td><%= link_to 'Editar', edit_admin_product_path(product) %></td>
<td><%= link_to 'Borrar', admin_product_path(product), method: :delete,
data: { confirm: 'Estás seguro que deseas borrar este producto?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'Nuevo Producto', new_admin_product_path %>
Ahora podemos ir a nuestro navegador web, usando la ruta /admin/products
, por ejemplo, para ver una lista de productos pero poder administrarlos, la idea de hacer esto es para que haya una parte de la aplicación encargada de la administración y otra separada para hacer las compras, posibilitando que luego podamos añadir más fácilmente permisos de acceso.
Vamos a agregar una simple gema de autenticación llamada Devise. Esta nos proporciona formularios de autenticación y métodos para manejar las sesiones de usuario.
Lo primero que tenemos que hacer es instalar la gema. Agregamos al archivo Gemfile
la línea:
gem 'devise'
Info: https://github.com/plataformatec/devise
Ahora instalamos la gema mediante el comando en la terminal:
bundle install
Configuramos nuestro proyecto para usar Devise con:
rails generate devise:install
Creamos un modelo de Devise para el administrador:
rails generate devise Admin
Ahora necesitamos crear un usuario administrador en nuestra base de datos, para esto abrimos la consola de Rails escribiendo:
rake db:migrate
Creamos un usuario administrador directamente en la consola de Rails:
Admin.create(email: 'admin@tienda.com', password: 'railsgirls', password_confirmation: 'railsgirls')
Y en el controlador de productos para administradores (app/controllers/admin/products_controller.rb
) le decimos que antes de hacer cualquier acción debe autenticar el administrador:
before_action :authenticate_admin!
Reiniciamos el servidor de Rails y vamos a la dirección http://localhost:3000/admin/products
. Ahora nos debería pedir autenticarnos. Lo hacemos con los datos de email y password del usuario administrador creado anteriormente.
Modificamos la barra de navegación del layout principal app/views/layouts/header.html
como lo hicimos en una anterior ocasión, para agregar un enlace para cerrar la sesión:
<% if admin_signed_in? %>
<ul class="nav navbar-nav navbar-right">
<li>
<%= link_to "Cerrar Sesión", destroy_admin_session_path, method: :delete %>
</li>
</ul>
<% end %>
Refrescamos la página y probamos el enlace de cierre de sesión.
Eso es todo por ahora, esperamos que tu tienda esté funcionando y que tengas las bases para seguir avanzando. ;)
¡Ahora veremos en secuencia los pasos necesarios para subir tu primer proyecto a la plataforma Github!
Dashboard
.
https://c9.io/account/sshSSH keys
ssh-rsa...
SSH and GPG keys
.
https://github.com/settings/sshgit@github.com:sunombre/nombredelproyecto.git
git init
git remote add origin git@github.com:sunombre/nombredelproyecto.git
git add .
git commit -m "Mi Primer commit"
git push -u origin master
Ahora tienes tu repositorio actualizado en GitHub
¿Cómo subir nuestra aplicación en la web, de forma que otros puedan verla? Con un servicio llamado Heroku que permite subir tu aplicación en un servidor gratis en cuestión de segundos.
wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh
heroku login
Email: (Introduce tu correo electrónico)
Password ( Introduce tu contraseña - se mostrará en blanco y es normal )
heroku keys:add
heroku create #crea una nueva URL para la aplicación
/gemfile
group :development, :test do
gem 'sqlite3'
end
group :production do
gem 'pg'
gem 'rails_12factor'
end
Nota: Después de crear un grupo producción
a tu Gemfile, debes cambiar a utilizar bundle install --without production
bundle install --without production
git add --all
git commit -m "¡Lista para subir a Heroku!"
git push origin master
git push heroku master
heroku open
heroku rename instagram #Reemplaza "instagram" con el nombre de tu proyecto
heroku run rake db:migrate #Para correr las migraciones
Miguél Díaz (miguel.diaz@codescrum.com)
Victor Cortés (ie.cortex@gmail.com)