No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Reg铆strate

Comienza en:

0D
16H
57M
6S

Interactuando con Cocoon para anidar formularios

27/36
Recursos

Aportes 23

Preguntas 7

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

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.

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.

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

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>

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.

Se me quitaron las ganas de aprender Rails

Para los que estan llevando el curso en la actualidad, si presentan algun problema con respecto al boton de 鈥淎gregar 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"
  },

Hola a todos, disculpen, tengo una duda de un problema que tengo con la parte de 鈥淎gregar 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

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

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.

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鈥, 鈥榙ata-association-insertion-node鈥 => 鈥.participants .participants-container鈥, 鈥榙ata-turbolinks鈥 => false do agregar .participants = f.simple_fields_for :participating_users do |g| = render 鈥榩articipating_user_fields鈥, f: g .participants-container .form-actions = f.button :submit

Para el reto propuesto por el profesor esta es mi soluci贸n:
En el modelo app\models\task.rb agregar la validaci贸n

validate :uniqueness_participant

 def uniqueness_participant
    users = participating_users.map{|p| p[:user_id] }
    return if users.uniq.length == users.length
    errors.add :base, I18n.t('task.errors.repeated_participant')
 end

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 鈥榓gregar 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