---
# System prepended metadata

title: Ejercicio guiado - catalog
tags: [Desarrollo de aplicaciones Ruby on Rails, ' guiado']

---

---
title: 'Ejercicio guiado - catalog'
tags: Desarrollo de aplicaciones Ruby on Rails, guiado
---
{%hackmd hackmd-dark-theme %}

<style>
	.markdown-body {
		color: #fff;
	}
	pre.part {
		background: #282A36;
		color: #FFB439;
	}
	.markdown-body h2 {
		border-bottom: none;
	}
	.hljs-keyword {
		color: #f5c;
		font-weight: bold;
	}
	.hljs-title {
		color: #3f7;
	}
	h1 {text-align: center}
</style>


# Ejercicio guiado - Catalogo


Referencia:

{%pdf https://mach-911.github.io/assets/desarrollo-aplicaciones-ror/1.presentacion-modelos-con-relaciones-n-a-n.pdf %}


Creamos un nuevo proyecto:

```bash
rails new catalog -d postgresql
```

>Modificamos los datos de conexión

Crear la base de datos:

```bash
rails db:create
```

Identificamos la relación de entidades:

```mermaid
%%{
  init: {
    'theme': 'base',
    'themeVariables': {
	  'background': '#fff',
      'primaryColor': '#FFB439',
      'primaryTextColor': '#222',
      'primaryBorderColor': '#FFB439',
      'tertiaryColor': '#fff'
	 },
	 'themeCSS': '.er.attributeBoxEven { fill:#ddd} .er.relationshipLine { stroke:#ccc}'
  }
}%%
erDiagram
    PRODUCTO }|--|{ CATEGORIA : tiene
	PRODUCTO {
		int id
		string nombre
		int precio
		string tamano
	}
	CATEGORIA {
		int id
		string nombre
	}
```


Normalizar las tablas:


```mermaid
%%{
  init: {
    'theme': 'base',
    'themeVariables': {
	  'background': '#fff',
      'primaryColor': '#FFB439',
      'primaryTextColor': '#222',
      'primaryBorderColor': '#FFB439',
      'tertiaryColor': '#fff'
	 },
	 'themeCSS': '.er.attributeBoxEven { fill:#ddd} .er.relationshipLine { stroke:#ccc}'
  }
}%%
erDiagram
    PRODUCTO ||--|{ CATEGORIA_PRODUCTO : esta
	CATEGORIA ||--|{ CATEGORIA_PRODUCTO : esta
	CATEGORIA_PRODUCTO {
	 int producto_id
	 int categoria_id
	}
	PRODUCTO {
		int id
		string nombre
		int precio
		string tamano
	}
	CATEGORIA {
		int id
		string nombre
	}
```

Creamos el modelo para Categoria a través de scaffold:

```
rails g scaffold category name
```

Creamos el modelo para Producto a través de scaffold:

```
rails g scaffold product name price:integer size:integer
```

Creamos una migración de representar la tabla intermedia entre categorias y productos:

```
rails g migration CreateJoinTableCategoriesProducts category product
```

>La tabla se creará en la base de datos, pero no establece relaciones por medio de claves foráneas

Realizamos la migración:

```
rails db:migrate
```

Agregamos las relaciones de las categorias a los productos:

```ruby
#app/models/category.rb
class Category < ApplicationRecord
  has_and_belongs_to_many :products
end
```

Agregamos las relaciones de productos a las categorías:

```ruby
class Product < ApplicationRecord
  has_and_belongs_to_many :categories
end
```

Agregamos [faker](https://github.com/faker-ruby/faker) al proyecto:

```bash
bundle add faker
```

Validamos a través del modelo que la categoría tenga nombre único:


```ruby
#app/models/category.rb
validates :name, uniqueness: true
```

Rellenamos la tabla de categoría con datos de prueba por medio de la siembra: 

```ruby
#db/seeds.rb
while Category.count < 10
  if !Category.pluck(:name).include?(Faker::Game.genre)
    unique_name = Faker::Game.genre
    Category.create(name: unique_name)
  end
end
```

Podemos correr ahora la migración y ejecutar la siembra: 

```
rails db:migrate db:seed
```

Definimos la ruta productos como root de la aplicación:

```ruby=
Rails.application.routes.draw do
  resources :products
  resources :categories
  root "products#index"
end
```

Agregamos el campo `category_ids` a los **strong params** al final del controlador: 

```ruby=67
#app/controllers/product_controller.rb
def product_params
  params.require(:product)
    .permit(:name, :price, :size, :category_ids => [])
end
```

Al crear un producto lo asociamos a una categoría, agreguemos el campo al formulario de producto:


```erb=28
<!-- app/views/products/_form.html.erb -->
<div>
    <%= form.label :category_ids, "Categoría", style: "display: block" %>
    <%= form.collection_check_boxes(:category_ids, @categories, :id, :name) do |b| %>
      <%= b.check_box %><%= b.label %><br>
    <% end %>
</div>
```

Ahora para que podamos cargar la información correctamente en el campo anteriormente creado al formulario, añadimos el método para traer las categorias en el controlador de productos:

```ruby=73
def set_categories
  @categories = Category.all
end
```

Y lo ponemos a disposición de las acciones del controlador a través de un `before_action`:

```ruby=3
before_action :set_categories, only: [:new, :edit]
```

Ahora tenemos listo para guardar toda esa información correctamente, debemos modificar el método `create` de la siguiente manera para devolver mensajes en español:

```ruby
def create
  @product = Product.new(product_params)
  respond_to do |format|
    if @product.save
      format.html { redirect_to product_url(@product), notice: "Producto creado con exito." }
      format.json { render :show, status: :created, location: @product }
    else
      format.html { render :new, status: :unprocessable_entity }
      format.json { render json: @product.errors, status: :unprocessable_entity }
    end
  end
end
```

## Agregando bootstrap con importmap

**1 )** Ejecutar el siguiente comando:

bash:
```bash
bin/importmap pin bootstrap --download
```

cmd:

```cmd
ruby bin/importmap pin bootstrap --download
```

**2 )** En el archivo `config/importmap.rb` remplazar las siguiente líneas: 

```ruby=8
pin "bootstrap" # @5.3.0
pin "@popperjs/core", to: "@popperjs--core.js" # @2.11.8
```

Por las siguientes:  

```ruby=8
pin "bootstrap", to: "bootstrap.min.js", preload: true
pin "popperjs", to: "popper.js", preload: true
```

**3 )** Modificas las siguiente línea en `config/initializers/assets.rb`: 

Descomentar y remplazar:

```ruby=12
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
```

```ruby=12
Rails.application.config.assets.precompile += %w( bootstrap.min.css popper.js )
```

**4 )** Añadir las siguientes líneas en el archivo `app/javascript/application.js`:


```ruby=4
import "popperjs" # primero debe ir popper
import "bootstrap"
```

**5 )** Añadir las siguientes líneas en el archivo `app/config/manifest.js`:

```javascript=5
//= link popper.js
//= link bootstrap.min.js
```

## Agregando usuarios


- Realizar autenticación básica con Devise
- Un usuario puede ir a cerrar sesión, registrarse, desde un navbar de bootstrap

Añadimos devise al proyecto:  

```bash
bundle add devise
```

Instalamos el generador: 

```
rails g devise:install
```

Generamos la migración: 

```bash
rails g devise User
```

Corremos la nueva migración:

```
rails db:migrate
```



Ahora podemos tener un partial con el bloque de código con la navegación y links para el registro e iniciar sesión en `app/views/shared/_navbar.html.erb`:


```erb
<nav class="navbar navbar-expand-lg bg-info bg-gradient">
  <div class="container">
    <%= link_to("Catalogo", root_path, class: "navbar-brand") %>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarContent">
      <div class="navbar-nav ms-auto mb-2 mb-lg-0">
        <li class="nav-item dropdown">
          <%= link_to(user_signed_in?? 
            "Hola #{current_user.email}" 
            : "Debes registrarte", "#", 
            class: "nav-link dropdown-toggle", 
              data: {bs_toggle: "dropdown"}, 
              aria_expanded: false) %>
          <div class="dropdown-menu">
            <% if user_signed_in? %>
              <%= button_to 'Cerrar sesión', destroy_user_session_path, class: 'dropdown-item', method: :delete %>
            <% else %>
              <%= link_to 'Iniciar sesión', new_user_session_path, class: 'dropdown-item'%>
              <hr class="dropdown-divider">
              <%= link_to 'Registro', new_user_registration_path, class: 'dropdown-item'%>
            <% end %>
          </div>
        </li>
      </div>
    </div>
  </div>
</nav>
```

Y ahora renderizarlo en el layout principal `app/views/layouts/application.html.erb`:  

```erb=
<!DOCTYPE html>
<html>
  <head>
    <title>Catalogo</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <%= render 'shared/navbar' %>
    <div class="container">
      <% if flash[:notice] %>
        <div class="alert alert-info"><%= notice %></div>
      <% end %>
      <%= yield %>
    </div>
  </body>
</html>
```

---

## Agregando reacciones a los productos

Ahora vamos a permitir que los usuarios puedan permitir a los productos, para eso veamos como quedaría las entidades



```mermaid
%%{
  init: {
    'theme': 'base',
    'themeVariables': {
	  'background': '#fff',
      'primaryColor': '#FFB439',
      'primaryTextColor': '#222',
      'primaryBorderColor': '#FFB439',
      'tertiaryColor': '#fff'
	 },
	 'themeCSS': '.er.attributeBoxEven { fill:#ddd} .er.relationshipLine { stroke:#ccc}'
  }
}%%
erDiagram
    PRODUCTO ||--|{ REACCION : tirne
	USUARIO ||--|{ REACCION : realiza
	PRODUCTO {
		int id
		string nombre
		int precio
		string tamano
	}
	REACCION {
		int product_id
		string user_id
	}
	USUARIO {
		int id
		string email
		string password
	}
```

Creamos el modelo para reacciones: 

```bash
rails g model Reaction product:references user:references kind
```


Ahora debemos establecer las relaciones para muchos a muchos usando `through`.


Partamos con productos: 

```ruby=3
has_many :reactions
has_many :users, through: :reactions
```

Ahora con usuarios: 

```ruby=6
has_many :reactions
has_many :products, through: :reactions
```

>Este tipo de relaciones se usa solo cuando creamos un modelo para la tabla intermedia

Ahora en el modelo de reacciones, limitamos el tipo de las mismas:

```ruby=4
validates :kind, acceptance: {
  accept: %w[like dislike boring neutral uninsterested interested]
}
```

Agregamos un método de clase para visualizar opciones fácilmente: 


```ruby=8
def self.kinds
  %w[like dislike boring neutral uninsterested interested]
end
```

Probamos el método de clase a través de la consola `rails c`: 

```
Reaction.kinds
```

Generamos el controlador para las reacciones: 

```
rails g controller ractions
```

Ahora en el controlador creamos un método para que el usuario pueda reaccionar: 

```ruby=
class ReactionsController < AplicattionController
  
  def user_reaction
    @user = current_user # capturamos al usuario actual
    @product = Product.find(params[:product_id])
    reaction = Reaction.find_by(user_id: @user.id, product_id: @product.id) 
    if reaction
      return flash.now[:alert] = "Ya haz reaccionado a este producto"
    else
      @new_reaction = Reaction.new(user_id: @user.id, product_id: @product.id, kind: params[:kind])
      respond_to |format|
        if @new_reaction.save!
          format.html { redirect_to product_path(@product), notice: "#{current_user.email} a reaccionado con un #{@new_reaction.kind}"}
        else 
          format.html { redirect_to product_path(@product), status: unprocessable_entity}
        end
      end
    end
  end
end
```

Ahora agregamos la restricción para que solo un usuario conectado pueda reaccionar:

```ruby=2
before_action :authenticate_user!
```
Creamos la ruta para el método nuevo que creamos: 

```
post '/reactions', to: 'reactions#user_reaction', as: 'user_reaction'
```

Agregamos una vista parcial para mostrar las opciones de reacciones: 

```erb
#app/views/reactions/_options.html.erb
<div class="container">
 <ul>
 <% Reaction.kinds.each do |kind| %>
 <li><%= button_to "#{kind}", user_reaction_path(product_id: @product.id, kind: kind),
method: :post %></li>
 <% end %>
 </ul>
</div>
```

Y agregamos otro parcial para mostrar el conteo de reacciones: 


```erb
<ul>
  <% Reaction.kinds.each do |kind| %>
    <li><%= @product.reactions.where(kind: kind).count %> - <%= kind %></li>
  <% end %>
</ul>
```

Llamamos a la vista parcial en la vista **show** del producto para renderizarla:

```erb
<p style="color: green"><%= notice %></p>

<%= render @product %>
<%= render 'reactions/counter' %>
<%= render 'reactions/options' %>
<div>
  <%= link_to "Edit this product", edit_product_path(@product) %> |
  <%= link_to "Back to products", products_path %>
  <%= button_to "Destroy this product", @product, method: :delete %>
</div>
```

 Creamos ahora un método para que el usuario vea los productos a los cuales ha reaccionado:
 
 ```ruby
 #app/controllers/reactions_controller.rb
def product_with_reactions
  @reactions = current_user.reactions
  product_ids = @reactions.map(&:product_id)
  @products = Product.where(id: product_ids)
end
 ```

Creamos una ruta para nuestro nuevo método: 

```ruby
#config/routes.rb
get '/my_reactions', to: 'reactions#product_with_reactions', as: 'my_reactions'
```

Para que nuestro método funcione, debemos crear la vista: 

```erb
<h1>Products</h1>
<ul>
  <% @products.each do |product| %>
    <li>
      <%= product.name %> - 
      <%= product.reactions.find_by(user_id: current_user.id).kind %>
      <p><%= link_to "Show this product", product %></p>
    </li>
  <% end %>
</ul>
```

