No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Añadiendo notas con AJAX

33/36
Recursos

Aportes 10

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Les dejo el codigo utilizado en el minuto 6 ya que no lo consigo en otra parte.

<%- if @note.persisted? %>
    var parentNode = document.querySelector(".notes-container .notes")
    var div = document.createElement("div")
    div.innerHTML = "<%= j render 'tasks/notes/note', note: @note %>"
    parentNode.appendChild(div)
    document.getElementById("note_body").value = "";
    window.scrollTo({
        top: window.innerHeight,
        behavior: "smooth"
    });
<% end %>```

Seguro les encantó la funcionalidad que se implementó en este video. Muchos de ustedes se habrán dado cuenta que es una estrategia que ofrece una buena experiencia de usuario, pero, ¿qué pasa si necesito hacer esto en muchas partes de mi aplicación? ¿Siempre tendría que agregar un JS para cada una de las partes en las que quiero este tipo de funcionalidad?
Esto es algo que tal vez muchos quieran evitar.

Bueno, el día de hoy vengo a hablarles de turbolinks_render. Recordemos que turbolinks es una biblioteca de JS que ayuda a que la carga de las páginas parezca más rápida de lo que es, así como cachear las páginas para que el navegar hacia atrás o hacia adelante sea mucho más rápido y no siempre haga peticiones a nuestro server.

Ahora, turbolinks_render es una gema que usa la magia de turbolinks para reemplazar el contenido de la página cuando el server recibe una petición AJAX. Es decir, basta con que nos encarguemos de que nuestro form haga una petición AJAX y que el controlador en el server haga render de una página completa (no solo de un partial). La gema añade un middleware para que la respuesta contenga un JS que reemplace el contenido de la página con el contenido de la respuesta.

Para nuestro caso podríamos usar el form tal cual lo tenemos y el controller:

def create
    @note = @task.notes.new(note_params)
    @note.user = current_user
    @note.save
    render 'tasks/show', task: @task
 end

Podemos obtener el mismo resultado con un redirect_to (aunque esto puede causar que la misma página se guarde varias veces en la historia del navegador), pero este método hace que sea más limpio para el usuario.

Para quien tenga curiosidad este es el link al repo de turbolinks_render. Y para los que tengan aún más curiosidad, este es el JS que la gema envía y hace toda la magia.

Ahora con rails 7 hay muchos cambios por lo que es necesario hacer algnas configuraciones de otra manera para lograr el comportamiento deseado.
En esta versión de rails se usa Turbo Stream
Y con esto ya no es necesario crear el archivo create.js.erb, solo hay que modificar el controlador de creacion de notas asi:

 def create
    @note = @task.notes.new(note_params)
    @note.user = current_user
    @note.save

    if @note.persisted?
      respond_to do |format|
        format.turbo_stream do
          render turbo_stream: turbo_stream.append('notes', partial: 'tasks/notes/note',
          locals: { note: @note })
        end
      end
    end
  end

En cuento a la plantilla app\views\tasks\show.html.haml quedaria algo asi.


.notes-container
  #notes
    - @task.notes.each do |note|
      = render 'tasks/notes/note', note: note
  = simple_form_for Note.new, url: task_notes_path(task_id: @task.id), html: { data: {controller: "reset-form", action: "turbo:submit-end->reset-form#reset"}}, remote: true do |f|
    = f.input :body, label: false, as: :string, placeholder: 'Notes', input_html: { autocomplete: :off}

Finalmente es necesario crear el archivo app\javascript\controllers\reset_form_controller.js para lograr el comportamiento de limpiar el formulario una vez enviado

import { Controller } from  "@hotwired/stimulus";   

export default class extends Controller {
    reset() {
     this.element.reset()
    }
 }

Aqui mas información https://hotwired.dev/

El reto

ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    can :manage, Task, owner_id: user.id
    can :read, Task, participating_users: { user_id: user.id }
    can :create, Note, task: { owner_id: user.id }
  end
end

show.html.haml

%p#notice= notice

%p
  %b Name:
  = @task.name
%p
  %b Description:
  = @task.description
%p
  %b Due date:
  = @task.due_date
%p
  %b Category:
  = @task.category
-#rails routes | grep notes -> task_notes
.notes-container
  .notes
    - @task.notes.each do |note|
      = render 'tasks/notes/note.html.haml', note: note
    - if can? :create, @task
      = simple_form_for Note.new, url: task_notes_path(task_id: @task.id), remote: true do |f|
        = f.input :body, label: false, as: :string, placeholder: 'Ingrese su nota...', input_html: {autocomplete: :off}
= link_to 'Edit', edit_task_path(@task)
\|
= link_to 'Back', tasks_path

Mi solución para que solo el responsable añada notas o comentarios a la tarea con cancancan

  1. añadimos el siguiente código al controlador del note, justamente debajo de los callbacks,
    estas dos lineas nos permiten tener a dispocision los recursos del tasks, por medio de la
    relacion through.:

     load_resource :task
     load_and_authorize_resource :through => :task
    
  2. luego el abillity que es quien controla el acceso le agregue el siguiente código.

     	# * si el usuario es propietario del post puede escribir notas
     can :create, Note do |n|
     	# obtener el propietario de la tarea
     	owner_id = Task.find(n.task_id).owner_id
     	# obtener el pid del current user
     	user_id = user.id
     	# comparo si el current user es propietario de la tarea
     	owner_id == user_id
     end
    

Este código lo que hace es comparar el id del propietario de la tarea, contra el id del usuario actual y si es igual es porque es el propietario y le permite agregar la tarea sino no lo realiza.

Espero les sea de utilidad, saludos y nunca paren de aprender y compartir. 😃

Estoy usando Rails 7 y basta con usar el partial de _notes para que cada que se suba la nota haga un pequeño refresh de la página y aparezca la nota, sin el archivo create.js.erb

Hola Profesor Johan, estoy tratando de realizar la tarea para solo permitir a los propietarios de la tarea poder crear notas, como soy nuevo en programación, me pierdo un poco todavía, podrías darme una mano.

solucion a la prueba, en mi caso lo estoy haciendo con el sistema de plantillas erb, solo deje el permiso sobre el create ya dicha el propietario del tarea solo puede crear la nota y los otros pueden observarla

ability

lass Ability
  include CanCan::Ability

  def initialize(current_user)
   can :manage, Task, owner_id: current_user.id
   can :read, Task, participating_users: { user_id: current_user.id }
   can :manage, Note, task: { owner_id: current_user.id }
  end
end

vista

<% if can? :create, Note %>
  <div class="notas">
   
    <%= form_with method: :post, url: task_notes_path(@task.id), model: @task.notes.new do |form|%>
      <% if @task.errors.any? %>
        <div style="color: red">
          <h2><%= pluralize(@task.errors.count, "error") %> prohibited this note from being saved:</h2>

          <ul>
            <% @task.errors.each do |error| %>
              <li><%= error.full_message %></li>
            <% end %>
          </ul>
        </div>
      <% end %>

      <div>
        <%= form.label :body, style: "display: block" %>
        <%= form.text_area :body, id: "note_body" %>
      </div>

      <div>
        <%= form.submit %>
      </div>
    <% end %>
  </div>
<% end %>