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