---
# System prepended metadata

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

---

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

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

{%pdf https://mach-911.github.io/assets/desarrollo-aplicaciones-ror/3.presentacion_%20asociando_elementos.pdf %}


# Ejercicio guiado bookstore

- Un libro no puede existir si no está relacionado con un autor
- Un libro solo puede ser publicado por una editorial
- Un autor puede tener muchos libros 


```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
    AUTHOR ||--|{ BOOK : escribe
    PUBLISHER ||--|{ BOOK : pubica
	AUTHOR {
		int id
		string name
		int age
	}
	BOOK {
		int id
		string name
		int publisher_id
		int author_id
	}
	PUBLISHER {
		int id
		string name
	}
```

Crear un nuevo proyecto:

```bash
rails new bookstore -d postgresql
```

Creamos la base de datos:

```bash
rails db:create
```

Crear los scaffolds

Para author:
```bash
rails g scaffold author name age:integer
```

Para publisher:

```bash
rails g scaffold publisher name
```

Para book:

```bash
rails g scaffold book name
```

Agregamos las claves foráneas al modelo Book:

```bash
rails g migration AddDetailsToBooks publisher:references author:references
```

Ejecutamos la migración para actualizar las tablas:

```bash
rails db:migrate
```

### Relacionamos los modelos:

Un author puede tener muchos libros:

```ruby=
#app/models/autor.rb
class Autor < ApplicationRecord
 has_many :books
end
```

Una editorial puede tener muchos libros:

```ruby=
#app/models/publisher.rb
class Publisher < ApplicationRecord
 has_many :books
end
```

Un libro pertenece a una editorial (*pusblisher*) y a un autor (*author*):

```ruby=
class Book < ApplicationRecord
  belongs_to :publisher
  belongs_to :author
end
```

### Agregamos datos desde la consola

```bash
rails c
```

**Autores**
```bash
Author.create!(name: "Stephen king")
Author.create!(name: "Marqués de sade")
Author.create!(name: "Goethe")
Author.create!(name: "Jane Austen")
Author.create!(name: "Victor Hugo")
Author.create!(name: "Charles Dickens")
```
**Editoriales**

```bash
Publisher.create!(name: "Arcano III")
Publisher.create!(name: "Edisiones SM")
Publisher.create!(name: "Tres Vientos")
```

### Anidando rutas

Anidar las ruta de autores y libros

```ruby=
#config/routes.rb
Rails.application.routes.draw do
  resources :publishers
  resources :autors do
    resources :books
  end
end
```

Ahora modificamos los métodos del controlador libros según las rutas.

El siguiente método auxiliar es para evitar repetir el código innecesariamente:

```ruby=1
#app/controllers/books_controller.rb
before_action :set_book, only: %i[ show edit update destroy ]
before_action :set_autor
```

```ruby=72
def set_autor
  @autor = Autor.find(params[:autor_id])
end
```
> Esto nos evitará tener que agregar la misma línea en todos los métodos del controlador

Modificamos el método create para cambiar la redirección:

```ruby=24
# app/controllers/books_controller.rb
def create
  @book = Book.new(book_params)
  respond_to do |format|
    if @book.save!
      format.html { redirect_to autor_book_path(@autor.id, @book.id), notice: "El libro fue creado correctamente." }
      format.json { render :show, status: :created, location: @book }
    else
      format.html { render :new, status: :unprocessable_entity }
      format.json { render json: @book.errors, status: :unprocessable_entity }
    end
  end
end
```

La misma modificación para el método update:

```ruby=40
# app/controllers/books_controller.rb
  def update
    respond_to do |format|
      if @book.update(book_params)
        format.html { redirect_to author_book_url(@author.id, @book.id), notice: "Book was successfully updated." }
        format.json { render :show, status: :ok, location: @book }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @book.errors, status: :unprocessable_entity }
      end
    end
  end
```

Finalmente modificamos los strong params: 

```ruby=68
def book_params
  params.require(:book).permit(:name, :author_id, :publisher_id)
end
```

Ahora si intetamos ir a visitar la siguiente URL por ejemplo: `http://localhost:3000/authors/1/books`, obtendremos un error porque aún nos falta modificar las rutas en las vitas.

Modificamos la vista index de book:

```erb=
<!-- app/views/books/index.html.erb -->
<p style="color: green"><%= notice %></p>
<h1>Books</h1>
<div id="books">
  <% @books.each do |book| %>
    <%= render book %>
    <p>
      <%= link_to "Ver este libro", autor_book_path(@autor.id, book.id) %>
    </p>
  <% end %>
</div>
<%= link_to "Nuevo libro", new_autor_book_path(@autor.id) %>
```

Lo mismo debemos hacer en la vista new de book:

```erb=
<!-- app/views/books/new.hrml.erb -->
<h1>New book</h1>

<%= render "form", book: @book %>

<br>

<div>
  <%= link_to "Back to books", author_books_path(@author.id) %>
</div>
```

También en la vista edit de book:

```erb=
<!-- app/views/books/edit.hrml.erb -->
<h1>Editing book</h1>
<%= render "form", book: @book %>
<br>
<div>
 <%= link_to "Ver este libro", author_book_path(@author.id, @book.id) %> |
 <%= link_to "Back to books", author_books_path(@author.id) %>
</div>
```

Continuamos con la vista show de book:

```erb=
<p style="color: green"><%= notice %></p>
<%= render @book %>
<div>
  <%= link_to "Editar este libro", edit_author_book_path(@author.id, @book.id) %> |
  <%= link_to "Volver a los libros", author_books_path(@author.id) %>

  <%= button_to "Eliminar este libro", author_book_path(@author.id, @book.id), method: :delete %>
</div>
```

Añadimos al author en los modelos asociados al formulario:


```erb=
<%= form_with(model: [@author, book]) do |form| %>
  <% if book.errors.any? %>
    <div style="color: red">
      <h2><%= pluralize(book.errors.count, "error") %> prohibited this book from being saved:</h2>
      <ul>
        <% book.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div>
    <%= form.label :name, style: "display: block" %>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>
```

En este punto, si intentamos crear un libros, ocurrirá lo siguiente:

![](https://hackmd.io/_uploads/HJMcM-1v2.png)

Para solucionarlo, podemos agregar primero un listado de autores de la siguiente manera: 

```html=18
<div>
  <%= form.label :author_ids, style: "display: block" %>
  <%= form.collection_radio_buttons(:author_id, Author.all, :id, :name) %>
</div>
```

![](https://hackmd.io/_uploads/r13NBZ1Dh.png)

De esta manera si volvemos a intentar nos quedará 1 error pendiente:

![](https://hackmd.io/_uploads/rkYorbyD3.png)

Para ya completar lo necesario para crear un libro, es agregando algo pareci para las editoriales:

```html=23
<div>
  <%= form.label :publisher_id, style: "display: block" %>
  <%= form.select :publisher_id, options_for_select(Publisher.all.pluck(:name, :id)) %>
</div>
```

![](https://hackmd.io/_uploads/HJIsq-1P3.png)


Ahora si finalmente podemos guardar libros:

![](https://hackmd.io/_uploads/SyI_9ZJw2.png)

## Extras

### Instalación bootstrap

- Ejecutar el siguiente comando

**bash**

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

**cmd**

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

