---
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:

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>
```

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

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>
```

Ahora si finalmente podemos guardar libros:

## Extras
### Instalación bootstrap
- Ejecutar el siguiente comando
**bash**
```bash
bin/importmap pin bootstrap --download
```
**cmd**
```bash
ruby bin/importmap pin bootstrap --download
```