Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Interactuando con Cocoon para anidar formularios

27/36
Recursos

Aportes 21

Preguntas 6

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Explicas muy bien, pero llega un punto en que hablas muy muy rapido, en especial en esta parte que se torna un poco mas complicad.

Buenos videos, pero a modo de concejo se pudieron haber explicado mejor algunas cosas si se hubieran dividido en varios videos para explicarlos mejor

esta clase estuvo muy tesa pero a mi punto de vista si la parte del active record, y la forma como generas la relaciones y las posibilidades de modificar el nombre de las mismas si deberían tener unas dos clases mas bien enfocadas me parece super importante entender bien esta parte, a mi punto de vista esta clase quedo un poco cargada.

Hola a todos, disculpen, tengo una duda de un problema que tengo con la parte de “Agregar participante”, es algo que ya comentaron anteriormente pero no me ha funcionado nada de ello.
Adjuntaré mi código para un mejor entendimiento. Cabe recalcar que la consola del explorador no me marca nada y la terminan donde está ejecutándose el servidor solo menciona que se ha renderizado:

_form.html.haml

-# frozen_string_literal: true
= simple_form_for(@task) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?

  .form-inputs
    = f.input :name, label: t('.name')
    = f.input :description, label: t('.description')
    = f.input :due_date, label: t('.due_date')
    = f.association :category, label: t('.category')
    #addParticipants
      = link_to_add_association f, :participating_users, 'data-association-insertion-node' => '.participants .participants-container', 'data-turbolinks' => false do
        Agregar participante
    .participants
      = f.simple_fields_for :participating_users do |g|
        = render 'participating_user_fields', f: g
      .participants-container

  .form-actions
    = f.button :submit

_participating_user_fields.html

.nested-fields
  = f.input :user_id, as: :select, collection: User.all - [current_user]
  = f.input :role

task.rb

class Task < ApplicationRecord
  belongs_to :category
  belongs_to :owner, class_name: 'User'
  has_many :participating_users, class_name: 'Participant' 
  has_many :participants, through: :participating_users, source: :user

  validates :participating_users, presence: true

  validates :name, :description, presence: true
  validates :name, uniqueness: { case_insensitive: false }
  validate :due_date_validity

  accepts_nested_attributes_for :participating_users, allow_destroy: true

    def due_date_validity
        return if due_date.blank?
        return if due_date > Date.today
    end
end

Concuerdo con Harry. Explicas muy bien pero en los últimos videos estas hablando muy rápido al punto que se vuelve confuso

la explicación muy deficiente no se si es el afán por acabar los vídeos rápido pero parecía una metralleta hablando y llega un punto donde ya es imposible seguirlo

Los formularios anidados quieren decir que en un solo formulario haya interacción con dos modelos. En este caso será con Task y Participant, con la ayuda de la gema Cocoon.

Para instalar esta gema hay que agregarla en el Gemfile, lo siguiente es agregar una dependencia de Cocoon para que pueda trabajar de forma correcta, esta dependencia se agrega con yarn desde el siguiente repositorio: github:nathanvda/cocoon#c24ba53.

Configuración de cocoon

En el archivo eviroment.js:

const webpack = require('webpack')

environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        'window.jQuery': 'jquery',
        Popper: ['popper.js', 'default'],
    })
)

En el archivo application.js:

import 'cocoon'

Esta configuración es necesaria ya que cocoon tiene como dependencia a jquery.

Una vez hecho esto hacemos la migración, para migrar el modelo Participant (rails db:migrate).


Formulario de tasks

En el formulario de tasks es donde se requiere asociar con los participantes, para esto se usará simple_fields_for, un método que permite asociar un modelo anidado que exista en la estructura de modelos.

= simple_form_for(@task) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?

  .form-inputs
    = f.input :name, label: t('tasks.name')
    = f.input :description, label: t('tasks.description')
    = f.input :due_date, label: t('tasks.due_date')
    = f.association :category, label: t('tasks.category')
    
    .participants
      = f.simple_fields_for :paticipating_users

Con el método simple_fields_for se habilita la comunicación con participating_users, que es como se nombró la relación entre tareas y participantes.

Para el formulario principal el constructor es f (primera línea) y para el formulario anidado el constructor es g.

Para presentar el formulario anidado se usará un parcial, que es un archivo creado en la carpeta views>tasks con el nombre _participating_user_fields.html.haml. Y se llamará al formulario principal con la función render dentro del formulario anidado.

.participants
      = f.simple_fields_for :paticipating_users
        = render 'participating_users_fields', f: g

Parcial:

.nested-fields 
    = f.input :user_id, as: :select, collections: User.all - [current_user]
    = f.input :role

En el primer input se inserta un elemento select, para poder elegir entre todos los usuarios, pero le restamos el usuario actual para que no aparezca en la lista. Era necesario poner current_user en un arreglo para que Ruby pudiera realizar las operaciones entre arreglos con User.all (también es un arreglo) sin problemas.

Para los que estan llevando el curso en la actualidad, si presentan algun problema con respecto al boton de “Agregar un participante” y ya revisaron todo el codigo y no tienen ningun error, puede que el problema sea por las dependencias en el package.json

Me lanzaba un error de que no era posible cargar @popper.js/core

Y al final el errror es que bootstrap en su version 5 deja de usar jquery, para ello deben de asegurarse de tener el package.json com se los dejo adelante

Solo escriben las versiones que tengo  y ejecutan   
  - yarn install para descargar todas las dependencias
"dependencies": {
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "5.4.3",
    "bootstrap": "^4.5.3",
    "cocoon": "github:nathanvda/cocoon#c24ba53",
    "jquery": "^3.5.1",
    "popper.js": "^1.16.1",
    "roboto-fontface": "^0.10.0",
    "turbolinks": "^5.2.0",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12"
  },

Gracias a todos por los comentarios y preguntas! Obtuve varios errores pero luego los encontré leyendo cada uno de los comentarios. 💪🏽

Excelente sus clases profesor Johan Tique. Espero tener muchos cursos de UD.

Si me gustaria que me ayudaras al llegar aqui

#addParticipants
      = link_to_add_association f, ':participating_users', 'data-association-insertion-node' => '.participants .participants-container', 'data-turbolinks' => false do
      agregar

me sale error

Encountered a syntax error while rendering template: check -# frozen_string_literal: true = simple_form_for(@task) do |f| = f.error_notification = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? .form-inputs = f.input :name, label: t(’.name’) = f.input :description, label: t(’.description’) = f.input :due_date, label: t(’.due_data’) = f.association :category #addParticipants = link_to_add_association f, ‘:participating_users’, ‘data-association-insertion-node’ => ‘.participants .participants-container’, ‘data-turbolinks’ => false do agregar .participants = f.simple_fields_for :participating_users do |g| = render ‘participating_user_fields’, f: g .participants-container .form-actions = f.button :submit

Me parece demasiado complicado. La idea de las herramientas es agilizar el desarrollo, pero en este caso es más complicado que hacerlo a mano.

Para la implementación del código para esta clase ulitice la gema vanilla-nested

https://github.com/arielj/vanilla-nested

Ha resultado facil de integrar al proyecto.

# tasks.rb

class Task < ApplicationRecord
  belongs_to :category
  belongs_to :owner, class_name: 'User'
  has_many :participating_users, class_name: 'Participant'
  has_many :participants, through: :participating_users, source: :user

  validates :participating_users, presence: true
  validates :name, :description, presence: true
  validates :name, uniqueness: { case_sensitive: false }
  validate :due_date_validate
  
  accepts_nested_attributes_for :participating_users, reject_if: :all_blank, allow_destroy: true

  def due_date_validate
    return if due_date.blank?
    return if due_date > Date.today
    errors.add :due_date, I18n.t('tasks.errors.invalid_due_date')
  end
end
# create_participans.rb

class CreateParticipants < ActiveRecord::Migration[7.0]
  def change
    create_table :participants do |t|
      t.integer :role
      t.references :user, null: false, foreign_key: true, on_delete: :cascade
      t.references :task, null: false, foreign_key: true, on_delete: :cascade

      t.timestamps
    end
  end
end

# _form.html.erb

<%= simple_form_for(@task) do |form| %>
  <%= form.error_notification %>
  <%= form.error_notification message: form.object.errors[:base].to_sentence if form.object.errors[:base].present? %>

  <div class="form-inputs">
    <%= form.input :name, label: t('.name') %>
    <%= form.input :description, label: t('.description') %>
    <%= form.input :due_date, label: t('.due_time') %>
    <%= form.association :category, label: t('categories.category') %>
  </div>

  <%= link_to_add_nested(form, :participating_users, '#participants', partial: 'participating_user_fields', link_text: "Agregar") %>
  <div id="participants">
    <%= form.fields_for :participating_users do |g|%>
      <%= render 'participating_user_fields', form: g %>
    <% end %>
  </div>

  <div class="form-actions">
    <%= form.button :submit %>
  </div>
<% end %>
#  _participating_user_fields.html.erb

<div class="nested-fields">
  <%= form.input :user_id, as: :select, collection: User.all - [ current_user ] %>
  <%= form.input :role %>
  <%= link_to_remove_nested(form) %>
</div>

Bueno. Esto de verdad me quito sueño. Pensé que lo había resuelto simplemente colocando esta validación en el modelo Participant:

validates_uniqueness_of  :user_id,scope: [:task_id],  allow_blank: false, message: "participants cant be repeated in same task"

Pero dejaba pasar a los usuarios y no entendía el porque. Me puse a debugguear y encontré que como en el momento de correr la validación que añadí todavía no se ha seteado el task_id por lo que al momento de la validación lo que tiene es:

user_id: 1
task_id: nil

Por lo que la validación pasa ya que no existe ningun paritcipant con esos valores. Investigue mucho y lo que encontré que sirvió fue crear una migración para hacer la validación desde la base de datos.

class AddConstrainToParticipant < ActiveRecord::Migration[6.1]
  def change
    add_index :participants, [:user_id, :task_id], unique: true
  end
end

De esta manera manejo el error directamente en la base de datos. Por lo que cuando se inserte los datos erroneos se hará un rollback en la base de datos y se activará una excepcion que estoy manejando en el task_controller.

  rescue_from ActiveRecord::RecordNotUnique do |exception|
    flash[:alert] = "participants cant be repeated in the same task"
    redirect_to new_task_path
  end

Esa es la validación que coloqué en el taskt_controller donde seteo un mensaje y redirijo la vista nuevamente a crear un nuevo participante.

En la vista (_form.html.haml de taskts) hay que agregar el flash. Yo lo hice así:

= flash[:alert] if flash[:alert].present?

De esa manera aparece un mensaje con el error y se tienen que vovler a agregar todos los datos.

Si alguien puede comentarme como lo hizo ojalá exista una manera menos rebuscada.

En la parte final menciona que en la implementación actual es posible añadir muchos participantes y a estos añadirles al mismo usuario. No entendí muy bien esa parte. Creí que en la parte donde restábamos -[current_user] se resolvía eso. Tal vez entendí mal, por eso me atrevo a preguntar si es a eso a lo que se refiere. A no permitirle que se agregue el usuario actual porque eso no tendría sentido.

Tengo un detalles cuando le doy click a participantes no me muestra los campos y no busco que paso la consola no me marca error

Buenos dias. Revise todas las preguntas para ver si conseguia solucionar el error que al clickear en ‘agregar participantes’ no me aparece nada pero aun sigo sin solucionarlo.

task.rb

# == Schema Information
#
# Table name: tasks
#
#  id          :bigint           not null, primary key
#  name        :string
#  description :text
#  due_date    :date
#  category_id :bigint           not null
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#
class Task < ApplicationRecord
  belongs_to :category
  belongs_to :owner, class_name: 'User'
  #Relaciones
  has_many :participating_users, class_name: 'Participant'
  has_many :participants, through: :participating_users, source: :user

  #Las tareas siempre tienen participantes
  validates :participating_users, presence: true

  #Estas columnas deben existir para poder guardarse
  validates :name, :description, presence: true
  #Debe ser unico, no puede haber dos categorias con el mismo nombre
  validates :name, uniqueness: {case_sensitive: false}
  validate :due_date_validity

  #Para que el modelo acepte atributos anidados
  accepts_nested_attributes_for :participating_users, allow_destroy: true

  def due_date_validity
    return if due_date.blank?
    return if due_date > Date.today
    errors.add :due_date, I18n.t('taks.errors.invalid_due_date')
  end
end

metodo task_params de tasks_controller.rb

    # Only allow a list of trusted parameters through.
    def task_params
      params.require(:task).permit(
        :name,
        :description,
        :due_date,
        :category_id,
        participating_users_attributes: [
          :user_id,
          :role,
          :id,
          :_destroy
        ]
      )
    end

_form.html.haml

-# frozen_string_literal: true
= simple_form_for(@task) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?

  .form-inputs
    = f.input :name, label: t('.name')
    = f.input :description, label: t('.description')
    = f.input :due_date, label: t('.due_date')
    = f.association :category, label: t('.category')
    #addParticipants
      = link_to_add_association f, :participating_users, 'data-association-insertion-node' => '.participants .participants-container', 'data-turbolinks' => false do
        Agregar un participante
    .participants
      = f.simple_fields_for :participating_users do |g|
        = render 'participating_user_fields', f: g
      .participants-container
  .form-actions
    = f.button :submit

_participating_user_fields

.nested-fields
  = f.input :user_id, as: :select, collection: User.all - [current_user]
  = f.input :role

Tengo algún error aquí, no se despliegan los campos al hacer click en agregar participante sin embargo no muestra ningún error en el log del servidor ni en el navegador, pero al hacer click en Crear Task muestra lo siguiente en la consola:

…
Aquí les dejo mi código:

task.rb

class Task < ApplicationRecord
  belongs_to :category
  belongs_to :owner, class_name: 'User'
  has_many :participating_users, class_name: 'Participant'
  has_many :participants, through: :participating_users, source: :user

  validates :participating_users, presence: true

  validates :name, :description, presence: true
  validates :name, uniqueness: { case_sensitive: false }
  validate :due_date_validity

  accepts_nested_attributes_for :participating_users, allow_destroy: true

  def due_date_validity
    return if due_date.blank?
    return if due_date > Date.today

    errors.add :due_date, I18n.t('task.errors.invalid_due_date')
  end
end

tasks_controller.rb

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  # GET /tasks
  # GET /tasks.json
  def index
    @tasks = Task.all
  end

  # GET /tasks/1
  # GET /tasks/1.json
  def show
  end

  # GET /tasks/new
  def new
    @task = Task.new
  end

  # GET /tasks/1/edit
  def edit
  end

  # POST /tasks
  # POST /tasks.json
  def create
    @task = Task.new(task_params)
    @task.owner = current_user
    respond_to do |format|
      if @task.save
        format.html { redirect_to @task, notice: 'Task was successfully created.' }
        format.json { render :show, status: :created, location: @task }
      else
        format.html { render :new }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /tasks/1
  # PATCH/PUT /tasks/1.json
  def update
    respond_to do |format|
      if @task.update(task_params)
        format.html { redirect_to @task, notice: 'Task was successfully updated.' }
        format.json { render :show, status: :ok, location: @task }
      else
        format.html { render :edit }
        format.json { render json: @task.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /tasks/1
  # DELETE /tasks/1.json
  def destroy
    @task.destroy
    respond_to do |format|
      format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_task
    @task = Task.find(params[:id])
  end

  # Only allow a list of trusted parameters through.
  def task_params
    params.require(:task).permit(
      :name,
      :description,
      :due_date,
      :category_id,
      participating_users_attributes:
       [:user_id,
        :role,
        :id,
        :destroy]
    )
  end
end

_form.html.haml

= simple_form_for(@task) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?

  .form-inputs
    = f.input :name
    = f.input :description
    = f.input :due_date
    = f.association :category
    #addParticipants
      = link_to_add_association f, :participating_users, 'data-association-insertion-mode' => '.participants participants-container', 'data-turbolinks' => false do
        agregar participante
    .participants
      = f.simple_fields_for :participating_users do |g|
        = render 'participating_user_fields', f: g
        .participants-container
  .form-actions
    = f.button :submit

_participating_user_fields.html.haml

.nested-fields
  = f.input :user_id, as: :select, collection: User.all - [current_user]
  = f.input :role

Agradecido de antemano por cualquier ayuda que me puedan dar para solventar este error.

buenas noches estoy teniendo el error que los participantes no se despliegan.
en aplication.js

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import 'cocoon'

Rails.start()
Turbolinks.start()
ActiveStorage.start()

/*
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

require('cocoon')
*/

prove de ambas formas como sugirio el coma#ero y no funciono

esta es mi version del tasks.rb de models

# == Schema Information
#
# Table name: tasks
#
#  id          :bigint           not null, primary key
#  name        :string
#  description :text
#  due_date    :date
#  category_id :bigint           not null
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#  owner_id    :bigint           not null
#  code        :string
#
class Task < ApplicationRecord
      belongs_to :category
      belongs_to :owner, class_name: 'User'
      has_many :participating_users, class_name: 'Participant'
      has_many :participants, through: :participating_users, source: :user
      has_many :notes

      validates :participating_users, presence: true

      validates :name, :description, presence: true
      validates :name, uniqueness: {case_insensitive: false}
      validate :due_date_validity

      before_create :create_code
      after_create :send_email

      accepts_nested_attributes_for :participating_users, allow_destroy: true

      def due_date_validity
        return if due_date.blank?
        return if due_date >= Date.today
        errors.add :due_date, I18n.t('task.errors.invalid_due_date')
      end

      def create_code
        code = "#{owner_id}#{Time.now.to_i.to_s(36)}#{SecureRandom.hex(8)}"
      end

      def send_email
        (participants + [owner]).each do |user|
          ParticipantMailer.with(user: user, task: self).new_task_email.deliver!
        end
        
      end
end

en cuanto a las vistas tengo:

_participating_user_fields.html.haml

.nested-fields
    = f.input :user_id, as: :select, collection: User.all - [current_user]
    = f.input :role

_form.html.haml

-# frozen_string_literal: true
= simple_form_for(@task) do |f|
  = f.error_notification
  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?

  .form-inputs
    = f.input :name
    = f.input :description
    = f.input :due_date
    = f.association :category
    #addParticipants
      = link_to_add_association f, :participating_users, 'data-association-insertion-node' => '.participants .participants-container' , 'data-turbolinks' =>false do
        add participant
    .participants
      = f.simple_fields_for :participating_users do |g|
        = render 'participating_user_fields', f: g
      .participants-container

  .form-actions
    = f.button :submit

sigo sin encontrar la razon del error, mi suposicion es que es un problema de identacion.

Tengo el mismo error de no poder mostrar los users en Agregar participante y ya lei todos los comentarios. En verdad no se que hacer lol