--- title: "Ejercicio guiado blog" --- {%hackmd @themes/orangeheart %} <style> img { width: 100%; border: 2px solid #ef7060; } </style> ## Crear proyecto 1) Crear el proyecto, utilizando PostgreSql como base de datos: ```bash rails new blog -d postgresql ``` 2) Creamos la base de datos: ``` rails db:create ``` --- ## Devise [Devise](https://github.com/heartcombo/devise) es una gema que facilita la implementación de la autenticación en las aplicaciones. 1) Agregamos devise al proyecto en nuestro Gemfile: ```bash echo gem 'devise' >> Gemfile ``` 2) Ejecuta el siguiente comando para instalar la gema: ``` bundle ``` 3) Ejecutar el generador de Devise ```bash rails g devise:install ``` 4) Crear el modelo de usuarios de la aplicación, generalmente se usa **User** como nombre: ```bash rails g devise User ``` 5) Corremos las migraciones: ``` rails db:migrate ``` ## Probar las rutas de Devise Al momento que ejecutamos `rails g devise:install` Devise crea, entre otras cosas, las siguientes rutas: |Ruta|Nombre|Descripción| |----|------|-----------| |**GET**<br> `/users/sign_up`|new_user_registration<br>new_user_registration_path|***registrations/new.html.erb***<br>Formulario de registro| |**POST**<br>`/users`||Registrarse| |**GET**<br>`/users/sign_in`|new_user_session<br>new_user_session_path|***sessions/new.html.erb***<br>Formulario de login| |**DELETE**<br>`/users/sign_out`|destroy_user_session<br>destroy_user_session_path|| Para ver el listado completo de rutas podemos ejecutar el siguiente comando: ``` rails routes -c devise --expanded ``` <details> <summary>Mostrar rutas</summary> ``` --[ Route 1 ]-------------------------------------- Verb | GET URI | /users/sign_in(.:format) Controller#Action | devise/sessions#new Prefix | new_user_session --[ Route 2 ]-------------------------------------- Verb | POST URI | /users/sign_in(.:format) Controller#Action | devise/sessions#create Prefix | user_session --[ Route 3 ]-------------------------------------- Verb | DELETE URI | /users/sign_out(.:format) Controller#Action | devise/sessions#destroy Prefix | destroy_user_session --[ Route 4 ]-------------------------------------- Verb | GET URI | /users/password/new(.:format) Controller#Action | devise/passwords#new Prefix | new_user_password --[ Route 5 ]-------------------------------------- Verb | GET URI | /users/password/edit(.:format) Controller#Action | devise/passwords#edit Prefix | edit_user_password --[ Route 6 ]-------------------------------------- Verb | PATCH URI | /users/password(.:format) Controller#Action | devise/passwords#update Prefix | user_password --[ Route 7 ]--------------------------------- ---- Prefix | Verb | PUT URI | /users/password(.:format) Controller#Action | devise/passwords#update --[ Route 8 ]-------------------------------------- Prefix | Verb | POST URI | /users/password(.:format) Controller#Action | devise/passwords#create --[ Route 9 ]-------------------------------------- Prefix | cancel_user_registration Verb | GET URI | /users/cancel(.:format) Controller#Action | devise/registrations#cancel --[ Route 10 ]------------------------------------- Prefix | new_user_registration Verb | GET URI | /users/sign_up(.:format) Controller#Action | devise/registrations#new --[ Route 11 ]------------------------------------- Prefix | edit_user_registration Verb | GET URI | /users/edit(.:format) Controller#Action | devise/registrations#edit --[ Route 12 ]------------------------------------- Prefix | user_registration Verb | PATCH URI | /users(.:format) Controller#Action | devise/registrations#update --[ Route 13 ]------------------------------------- Prefix | Verb | PUT URI | /users(.:format) Controller#Action | devise/registrations#update --[ Route 14 ]------------------------------------- Prefix | Verb | DELETE URI | /users(.:format) Controller#Action | devise/registrations#destroy --[ Route 15 ]------------------------------------- Prefix | Verb | POST URI | /users(.:format) Controller#Action | devise/registrations#create ``` </details> Para ello primero lanzamos el servidor de desarrollo con el comando`rails s` y luego visitamos las siguientes rutas: - http://localhost:3000/users/sign_in ![](https://hackmd.io/_uploads/rJL1odqFn.png) - http://localhost:3000/users/sign_up ![](https://hackmd.io/_uploads/rkSend5Fh.png) --- ## Agregar links de Devise Para que el usuario pueda registrarse, ingresar y salir de la aplicación vamos a agregar un controlador **pages** como página de inicio y definirla como la ruta raíz: ``` rails g controller pages home ``` Abrimos `config/routes.rb` y agrega lo siguiente: ```ruby= Rails.application.routes.draw do devise_for :users root "pages#home" end ``` Abrimos `app/views/pages/home.html.erb` y agrega lo siguiente: ```erb= <div> <% if signed_in? %> <%= link_to "Salir", destroy_user_session_path, method: :delete %> <% else %> <%= link_to "Registrarse", new_user_registration_path %> <%= link_to "Ingresar", new_user_session_path %> <% end %> </div> ``` Rails 7 ahora usa Turbo y el método debe llamarse un poco diferente. Así que el código fijo ahora se vería así: ```er= <div> <% if signed_in? %> <%= link_to "Salir", destroy_user_session_path, data: { turbo_method: :delete } %> <% else %> <%= link_to "Registrarse", new_user_registration_path %> <%= link_to "Ingresar", new_user_session_path %> <% end %> </div> ``` ![devise gif](https://mach-911.github.io/assets/gif/devise/sign_up.gif) --- ## Scaffold de Post 1) Crear un modelo de Post usando scaffold: ``` rails g scaffold Post image title description:text ``` ```mermaid erDiagram USER { id integer PK email string password string } POST { id integer PK title string description text image string user_id integer FK "Un post pertenece a un usuario" } USER ||--|{ POST :publica ``` 2) Agregamos la migración para relacionar los modelos: ``` rails g migration AddUsersToPosts user:references ``` 3) Ejecutamos la migración con `rails db:migrate` 4) Asociamos el modelo de **User** con los **Post**: ```ruby=0 # app/models/post.rb class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :posts end ``` 5) Y lo mismo con **Post** hacia **User**: ```ruby=0 # app/models/user.rb class Post < ApplicationRecord belongs_to :user end ``` 6) Podemos crear Post mediante consola para ver si está conectado: ```irb! Post.create!(image: 'https:/robohash.org/1', title: 'Servicio generador de imágenes', description: 'Servicio web generador de robots, aliens y monstruos', user_id: User.last.id) ``` Consultando la información en la tabla: ``` +-[ RECORD 1 ]+------------------------------------------------------+ | id | 1 | | image | https:/robohash.org/1 | | title | Servicio generador de imágenes | | description | Servicio web generador de robots, aliens y monstruos | | created_at | 2023-07-11 08:35:19.064116 | | updated_at | 2023-07-11 08:35:19.064116 | | user_id | 1 | +-------------+------------------------------------------------------+ ``` 7) Al crear el post se lo vamos asociar al usuario conectado agregamos lo siguiente en el controlador **app/controllers/posts_controller.rb** (línea:25) ```ruby=23 def create @post = Post.new(post_params) @post.user = current_user respond_to do |format| if @post.save format.html { redirect_to post_url(@post), notice: "Post was successfully created." } format.json { render :show, status: :created, location: @post } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @post.errors, status: :unprocessable_entity } end end end ``` --- ## Publicar post Ahora abrimos de nuevo **app/views/pages/index.html.erb** y agremos el link para permitirle publicar al usuario conectado (en línea:5): ```erb= <div> <% if signed_in? %> <%= link_to "Salir", destroy_user_session_path, data: { turbo_method: :delete } %> <br> <%= link_to "Publicar", new_post_path %> <% else %> <%= link_to "Registrarse", new_user_registration_path %> <%= link_to "Ingresar", new_user_session_path %> <% end %> </div> ``` Luego de eso, ya podemos publicar posts como se ve a continuación: ![gif post](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZzg1dWxhaDZqM3JjMTRqeXRhNGxnZ3A3ZGw4Z3VnMnJwbXFsMmc3cSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/V2IlC2w0RboVMPGyAB/giphy.gif) Por lo general, cuando visitas un Blog, el usuario se encuentra con la vista index de posts. Para redirigir y permitir mostrar esta vista, tenemos varias opciones. **Opción 1:** dejar post index como ruta root: ```ruby= # config/routes.rb Rails.application.routes.draw do resources :posts root "posts#index" end ``` **Opción 2:** renombrar la ruta index de post, como "user_root", de esta forma cuando inicia sesión el usuario de va a redirigir a esta ruta: ```ruby= #config/routes.rb Rails.application.routes.draw do resources :posts, except: [:index] root "home#index" get '/posts', to: 'posts#index', as: 'user_root' end ``` **Opción 3:** Sobreescribir el método de Devise `after_sign_in_path_for`: ```ruby= # app/controllers/application_controller.rb class ApplicationController < ActionController::Base def after_sign_in_path_for(resource) posts_path end end ``` > Fuente: [Wiki Devise](https://github.com/heartcombo/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in) --- ## Añadir paginación en los posts [Pagy](https://github.com/ddnexus/pagy) es una gema que está disponible para la paginación y que está tomando mucha popularidad ya que presume ser la mejor en cuanto a eficiencia y optimización. ### Instalación de Pagy Agregamos la gema **pagy** al proyecto: ```bash bundle add pagy ``` ### Configuración Abrimos el archivo **app/controller/aplication_controller.rb** y añadimos lo siguiente: ```ruby= class ApplicationController < ActionController::Base include Pagy::Backend end ``` >Podemos incluir el módulo `Pagy::Backend` en cada controlador o incluirlo de forma global en el archivo que muestra el ejemplo anterior. Abrimos el archivo **app/helpers/application_helper.rb** y añadimos lo siguiente: ```ruby= module ApplicationHelper include Pagy::Frontend end ``` >Podemos incluir el módulo `Pagy::Frontend` en cada controlador o incluirlo de forma global en el archivo que muestra el ejemplo anterior ### Implementación Una vez instalada la gema e incluidos los módulos, podemos comenzar a trabajar la paginación en las colecciones. El primer paso es crear una instancia de la clase Pagy en nuestro controlador **app/controllers/posts_controller.rb** podemos usar el método `pagy` y pasarle la colección que queremos paginar, esto creará una instancia y devolverá el objeto de Pagy por nosotros: ```ruby=5 def index @pagy, @posts = pagy(Post.all) end ``` Esto nos permite acceder fácilmente a la información sobre la paginación, como la página actual, la cantidad de elementos por página y la cantidad total de páginas. Para ver el funcionamiento hasta ahora, podemos agregar lo siguiente en nuestro archivo **db/seeds.rb**: ```ruby 130.times { |i| i+=1 Post.create( image: "https://robohash.org/#{i}", title: "Post #{i}", description: "Post #{i} description", user_id: 2) } ``` Y ejecutamos la siembra con `rails db:seed`, con esto tendremos una buena cantidad de posts para la demostración: ### Visualizar los enlaces de páginación El módulo `Pagy::Frontend` ofrece varios métodos útiles para facilitar la visualización y los aspecto de navegación de la paginación, como los siguientes helpers para usar en las vistas: - `<%== pagy_nav(@pagy) %>`: <img src="https://hackmd.io/_uploads/BkHP7kTKh.png" style="width:140px" /> - `<%== pagy_info(@pagy) %>`: <img src="https://hackmd.io/_uploads/B13ioyTt3.png" style="width: 180px" /> Esto mostrará los enlaces de paginación en el formato estándar, pero también podemos personalizar la apariencia usando diferentes estilos. Por ejemplo, podemos usar el helper `pagy_bootstrap_nav`. En primer lugar debemos incluir este extra en `config/initializers/pagy.rb`: ```ruby require 'pagy/extras/bootstrap' ``` Y ahora en lugar de usar `<%== pagy_nav(@pagy) %>` usamos el siguiente helper en la vista: - `<%== pagy_bootstrap_nav(@pagy) %>`: <img src="https://hackmd.io/_uploads/Hk5btGRKh.png" style="width:210px" /> ### Demostración ![pagy demo](https://mach-911.github.io/assets/gif/pagy_posts.gif) ## Valores predeterminados de Pagy Por medio de un nuevo archivo que podemos crear en **config/initializers/pagy.rb**, podemos modificar todas las opciones que nos proporciona Pagy [aquí](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb) 👈, esto afectará a todos los objetos de Pagy. ### Limitar la cantidad de items Abrimos el archivo **config/initializers/pagy.rb** y agregamos lo siguiente: ```ruby= Pagy::DEFAULT[:items] = 10 ``` > Todas las asignaciones con `Pagy::DEFAULT[:key]` se fusionarán con cada nueva instancia de Pagy ### Cambiar el idioma Se necesita cargar un diccionario para el idioma español. Pagy ya proporciona varios diccionarios listos para usar en nuestras aplicaciones, entre ellos se encuentra el disponible en [este archivo](https://github.com/ddnexus/pagy/blob/master/lib/locales/es.yml) 👈 el de español. Para usar esta configuración integrada abrimos el archivo **config/initializers/pagy.rb** y agregamos lo siguiente: ```ruby= Pagy::I18n.load(locale: 'es') ``` ### Pagy::Countless Esta es una subclase de Pagy que proporciona paginación sin necesidad de ningún `:count`, lo que resulta ser especialmente útil en los siguientes casos: - desplazamiento infinito - cuando la barra de navegación completa no es un requisito y se desea mejor rendimiento Para configurar este modo, debemos abrir **config/initializers/pagy.rb** ```ruby= require 'pagy/extras/countless' ``` Abrimos la vista de posts **app/views/posts/index.html.erb**: ```erb= <p style="color: green"><%= notice %></p> <h1>Posts</h1> <%= turbo_frame_tag :posts do %> <%= render @posts %> <% end %> <%= turbo_frame_tag :pagination, loading: :lazy, src: posts_path(format: :turbo_stream) %> ``` Creamos un nuevo archivo en **app/views/index.turbo_stream.erb** y la añadimos lo siguiente: ```erb= <%= turbo_stream.append :posts do %> <%= render @posts %> <% end %> <%= turbo_stream.replace :pagination do %> <% if @pagy.next.present? %> <%= turbo_frame_tag :pagination, loading: :lazy, src: posts_path(format: :turbo_stream, page: @pagy.next) %> <% end %> <% end %> ```