Demo
En este Post continuaremos con la parte anterior llamada Como crear un CRUD con Ruby on Rails 6 y Bootstrap 4 – Parte 3 en donde creamos el Controlador Postres con las vistas HTML y las rutas, en este Parte 4 vamos a trabajar en la creación de las Vistas del sistema CRUD (Create, Read, Update y Delete), estas vistas las trabajaremos con Bootstrap 4.
Partes
Antes de continuar con este Post te invito a escuchar el Podcast: “Que Hacer Cuando Estamos En Casa”:
Spotify:
Sound Cloud:
Bien ahora continuemos con el Post: Como crear un CRUD con Ruby on Rails 6 y Bootstrap 4 – Parte 4.
En la Parte 3 creamos el Controlador Postre y al hacerlo creamos de paso las vistas HTML para nuestro sistema CRUD, estas vistas las vamos a estilizar usando Bootstrap 4.
Los archivos que usaremos son 4: crear.html.erb, leer.html.erb, actualizar.html.erb y el archivo principal para listar todos los registros en una tabla index.html.erb. Para eliminar un registro no necesitaré una vista, ya que usaré JavaScript en la vista principal (index.html.erb) para eliminar un registro.
Los 4 archivos crear.html.erb, leer.html.erb, actualizar.html.erb e index.html.erb se encuentran en app > views > postres
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/app ├── /app ├── /assets ├── /channels ├── /controllers ├── /concerns ├── application_controller.rb ├── postres_controller.rb ├── /helpers ├── /javascript ├── /jobs ├── /mailers ├── /models ├── /views ├── /layouts ├── /postres // Archivos con las vistas HTML del sistema CRUD ├── actualizar.html.rb ├── crear.html.rb ├── index.html.rb ├── leer.html.rb ├── /bin ├── /config ├── /db ├── /lib ├── /log ├── /public ├── /storage ├── /test ├── /tmp ├── /vendor ├── .gitignore ├── .ruby-version ├── config.ru ├── Gemfile ├── Gemfile.lock ├── package.json ├── Rakefile ├── Gemfile.lock ├── README.md |
Bueno ahora usaremos estos 4 archivos para empezar a crear las interfaces correspondientes a cada vista HTML del CRUD.
Crear (Vista)
Esta vista va contener diferentes elementos, pero centrémonos en el formulario que es la parte más importante a mi parecer, ya que mediante el se insertarán los datos a la Base de datos, específicamente a la tabla postres, los campos que usaré en mi el formularios son: nombre, precio, stock, img y url (Este último es la url o slug del registro o postre)
El archivo para esta vista es crear.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<form method="POST" action="/postres/insertar" accept-charset="UTF-8" role="form" id="crearpostre" name="crearpostre" enctype="multipart/form-data"> <!-- Protección CSRF --> <%= token_tag nil %> <div class="row"> <div class="col-md-12"> <section class="panel"> <div class="panel-body"> <div class="form-group"> <label for="nombre" class="negrita">Nombre:</label> <div> <input class="form-control" placeholder="Torta de Chocolate" name="nombre" type="text" id="nombre" onload="crearUrlAmigable(this.value);" onkeypress="crearUrlAmigable(this.value);" required> </div> </div> <div class="form-group"> <label for="precio" class="negrita">Precio:</label> <div> <input class="form-control" placeholder="4.50" name="precio" type="text" id="precio" required> </div> </div> <div class="form-group"> <label for="stock" class="negrita">Stock:</label> <div> <input class="form-control" placeholder="40" name="stock" type="text" id="stock" required> </div> </div> <div class="form-group"> <label for="img" class="negrita">Selecciona una imagen:</label> <div> <input class="form-control" type="file" name="img" id="img" required> </div> </div> <div class="form-group"> <label for="url" class="negrita">URL:</label> <div> <input type="text" class="form-control" name="url" id="url" readonly> </div> </div> <button type="submit" class="btn btn-info">Guardar</button> <%= link_to 'Cancelar', url_for(:back), class: "btn btn-warning" %> <br> <br> </div> </section> </div> </div> </form> |
Aquí he realizado un truco temporal hasta que encuentre una mejor forma de hacerlo, si te das cuenta el campo nombre llama a la función JavaScript crearUrlAmigable() en los eventos onload y onkeypress
1 2 3 |
<input class="form-control" placeholder="Torta de Chocolate" name="nombre" type="text" id="nombre" onload="crearUrlAmigable(this.value);" onkeypress="crearUrlAmigable(this.value);" required> |
La función crearUrlAmigable() crea la url en base al nombre que el usuario tipee en el campo nombre, el código JavaScript es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function crearUrlAmigable(str) { // Reemplaza los carácteres especiales | simbolos con un espacio str = str.replace(/[`~!@#$%^&*()_\-+=\[\]{};:'"\\|\/,.<>?\s]/g, ' ').toLowerCase(); // Corta los espacios al inicio y al final del string str = str.replace(/^\s+|\s+$/gm, ''); // Reemplaza el espacio con guión str = str.replace(/\s+/g, '-'); var input = document.getElementById('url'); input.value = str; } |
Asimismo he creado un truco para obtener el nombre de la imagen y almacenarlo en la base de datos, lo que hago es que el campo nimg llame a la función JavaScript cargarImagen() en el evento onchange,
1 2 3 |
<input class="form-control" type="file" name="nimg" id="nimg" onchange="cargarImagen()" required> |
El código de la función cargarImagen() es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function cargarImagen() { var name = document.getElementById('nimg'); //alert('Nombre: ' + name.files.item(0).name); //alert('Tamaño: ' + name.files.item(0).size); //alert('Tipo: ' + name.files.item(0).type) nas = name.files.item(0).name var ini = document.getElementById('img'); ini.value = nas; } |
En el campo url se genera la url en base al nombre que el usuario tipee, esto se genera con la función JavaScript crearUrlAmigable(), asimismo he colocado el campo img que obtiene el nombre de la imagen con la función JavaScript cargarImagen()
1 2 3 4 5 6 7 8 9 |
<div class="form-group"> <label for="url" class="negrita">URL:</label> <div> <input type="text" class="form-control" name="url" id="url" readonly> <input type="text" class="form-control" name="img" id="img" readonly hidden> </div> </div> |
El campo url esta como solo lectura (readonly) y el campo img esta como solo lectura (readonly) y oculto (hidden).
Bueno con esto la vista Crear se ve de la siguiente manera:
Leer (Vista)
Esta vista va servir para ver los Detalles de un registro, es sencilla pero muy útil. Mediante un foreach llamamos a los datos de un determinado registro por el id
El archivo para esta vista es leer.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<% @postres.each do |postres| %> <section class="text-center"> <div class="container"> <h3 class="jumbotron-heading"><%= postres.nombre %></h3> </div> </section> <div class="album py-5 bg-light"> <div class="container"> <div class="row"> <div class="col-md-5"> <img src="/assets/img/<%= postres.img %>" class="img-fluid" title="<%= postres.nombre %>" alt="<%= postres.nombre %>"> </div> <div class="col-md-5" align="left"> <strong>Stock:</strong> <%= postres.stock %> <br><br> <strong>Precio:</strong> <%= postres.precio %> <br><br> <strong>Creado:</strong> <%= postres.created_at.strftime("%d/%m/%Y") %> </div> </div> </div> </div> <% end %> |
Bien, esta vista parece muy sencilla, pero es muy importante, en ella se muestran los detalles de un determinado registro, la vista Leer se vería de la siguiente manera:
Actualizar (Vista)
En esta vista vamos a colocar los datos de un determinado registro para poder editarlos y enviar los cambios a la Base de Datos, lo que hago es mediante un foreach obtener y colocar los datos de un determinado registro (por id) en los campos correspondientes del formulario.
El archivo para esta vista es actualizar.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<% @postres.each do |postres| %> <form method="POST" action="/postres/editar/<%= postres.id %>" accept-charset="UTF-8" role="form" id="actualizarpostre" name="actualizarpostre" enctype="multipart/form-data"> <!-- Protección CSRF --> <%= token_tag nil %> <div class="row"> <div class="col-md-12"> <section class="panel"> <div class="panel-body"> <div class="form-group"> <label for="nombre" class="negrita">Nombre:</label> <div> <input class="form-control" placeholder="Torta de Chocolate" name="nombre" type="text" id="nombre" onload="crearUrlAmigable(this.value);" onkeypress="crearUrlAmigable(this.value);" value="<%= postres.nombre %>" required> </div> </div> <div class="form-group"> <label for="precio" class="negrita">Precio:</label> <div> <input class="form-control" placeholder="4.50" name="precio" type="text" id="precio" value="<%= postres.precio %>" required> </div> </div> <div class="form-group"> <label for="stock" class="negrita">Stock:</label> <div> <input class="form-control" placeholder="40" name="stock" type="text" id="stock" value="<%= postres.stock %>" required> </div> </div> <div class="form-group"> <label for="img" class="negrita">Selecciona una imagen:</label> <div> <input class="form-control" type="file" name="img" id="img" required> <br> <br> Imagen Actual: <br> <br> <img src="/assets/img/<%= postres.img %>" class="img-fluid" title="<%= postres.nombre %>" alt="<%= postres.nombre %>"> </div> </div> <div class="form-group"> <label for="url" class="negrita">URL:</label> <div> <input type="text" class="form-control" name="url" id="url" value="<%= postres.url %>" readonly> </div> </div> <button type="submit" class="btn btn-info">Guardar</button> <%= link_to 'Cancelar', url_for(:back), class: "btn btn-warning" %> <br> <br> </div> </section> </div> </div> </form> <% end %> |
Bueno la vista Actualizar se ve de la siguiente manera:
Eliminar
Por último he creado una vista principal en donde listaremos todos los registros en una tabla HTML y al lado derecho de cada registro colocaré 3 botones: Detalles, Editar y Eliminar
Para esta vista usare el archivo index.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<!-- Mensajes --> <% if flash[:notice] %> <div class="alert alert-success" role="alert" id="notice" class="notice"> <%= flash[:notice] %> </div> <% end %> <table class="table table-striped table-bordered table-hover"> <thead> <tr> <th>Nombre</th> <th>Precio</th> <th>Stock</th> <th>Imagen</th> <th>Acciones</th> </tr> </thead> <tbody> <% @postres.each do |postres| %> <tr> <td class="v-align-middle"> <%= postres.nombre %> </td> <td class="v-align-middle"> <%= postres.precio %> </td> <td class="v-align-middle"> <%= postres.stock %> </td> <td class="v-align-middle"> <img src="/assets/img/<%= postres.img %>" width="30" class="img-fluid" title="<%= postres.nombre %>" alt="<%= postres.nombre %>"> </td> <td class="v-align-middle"> <form method="POST" action="/postres/eliminar/<%= postres.id %>" accept-charset="UTF-8" class="form-horizontal" role="form" onsubmit="return ConfirmDelete()"> <!-- Protección CSRF --> <%= token_tag nil %> <a href="/postres/leer/<%= postres.url %>" class="btn btn-dark">Detalles</a> <a href="/postres/actualizar/<%= postres.id %>" class="btn btn-primary">Editar</a> <button type="submit" class="btn btn-danger" onclick="return ConfirmDelete();">Eliminar</button> </form> </td> </tr> <% end %> </tbody> </table> |
Con esto la Vista Principal se ve de la siguiente manera:
Cuando el usuario hace clic en el botón Eliminar, este llama a la función JavaScript ConfirmDelete(), el código de esta función es el siguiente:
1 2 3 4 5 6 7 8 9 |
function ConfirmDelete() { var x = confirm("Estas seguro de Eliminar?"); if (x) return true; else return false; } |
La función ConfirmDelete() lanza una alerta preguntándole al usuario: Estas seguro de Eliminar? y si el usuario le de clic en el botón aceptar, el sistema procede a borrar el registro y si da clic en cancelar el sistema no hace nada.
Hasta aquí ya tenemos creadas las vistas HTML con Bootstrap 4 para el sistema CRUD.
Ten Paciencia, lo que quiero es que entiendas todo el proceso para Crear este Proyecto y no llenarte el capitulo de mucho contenido porque te puedes marear y no tendrás un óptimo aprendizaje.
Nota (s)
- Los Pasos y opciones mencionadas en este capitulo del tutorial pueden cambiar, esto no depende de nosotros, si no de los Desarrolladores que dan soporte a Ruby on Rails, que suelen cambiar sus opciones de despliegue y configuración en futuras versiones.
- En la siguiente y última Parte, realizaremos ciertas configuraciones y otros detalles.
Síguenos en nuestras Redes Sociales para que no te pierdas nuestros próximos contenidos.