Search code examples
ruby-on-railsnested-attributes

Nested fields in form is not rendering


Im new to ruby on rails so i need some tips please.

Im trying to render some checkboxes on the edit view for a user. I have tried to follow the documentation for the nested_attributes but the checkboes does not render. Here is the relation between the two models:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :softwares
  has_many :courses

  accepts_nested_attributes_for :softwares
  accepts_nested_attributes_for :courses

The Edit view for a user

<div class="container">
  <div class="row">
    <div class="col-md-12 mt-5">
      <%= form_for @student, url: auth_student_path(@student), method: :put do |f| %>
      

         <div class="col-md-12 mb-5 mt-5">
            <div class="row">
                <h3 class="mb-3 filter_heading">Softwares</h3>
            

            <% @softwares.each do |sf|%>
              <%= f.fields_for :softwares do |software| %>
                <div class="col-md-3">
                  <div class="courses">
                    <%= software.label sf.title %>
                    <%= software.check_box :title, {multiple: true}, sf.title, nil %>
                  </div>

                 <% sf.courses.each do |crs|%>
                    <%= f.fields_for :courses do |course|%>
                      <div class="mt-1 courses-checkbox">
                          <%= course.label crs.name %>
                          <%= course.check_box :name, {multiple: true}, crs.name , nil %>
                      </div>
                    <% end %>
                  <% end%>
                </div>
              <% end %>
            <% end%>
          </div>
         

        <div class="form-group">
          <%= f.submit "Save", class:"btn btn-primary"%>
        </div>
      <% end %>
    </div>
  </div>
</div>

The Controller


module Auth
    class StudentsController < ApplicationController
        before_action :authenticate_user!
        before_action :set_student, only: %i[delete_certificates]

        def edit
            authorize! :edit, @user
            @softwares = Software.all
            @student = User.find(params[:id])
        end

        def update
            authorize! :update, @user
            @student = User.find(params[:id])

            if @student.update(student_params)               
                redirect_to edit_auth_student_path(@student)
            else               
                redirect_to edit_auth_student_path(@student)
            end
        end

        def show


        def set_student
            @student = User.find(params[:student_id])
        end

        private
    
        def student_params
            params.require(:user).permit(
              :email,
              :firstname,
              :lastname,
              :phone,
              :locked,
              :approved,
              :role,
              badges: [],
              certificates: [],
              softwares_attributes: [:title],
              courses_attributes: [:name],
            )
          end
    end
end
    

Please help me.


Solution

  • You don't need accepts_nested_attributes_for just to select existing records and associate them with something. Its only needed if you need to create/update the other record (the course or software) at the same time.

    I'm also guessing you don't actually want to have a one-to-many assocation and duplicate every course and every software for each user - instead you want a many to many assocation and some data normalization.

    So create a join table to hold the assocation between users and courses for example:

    class User < ApplicationRecord
      has_many :enrollments, foreign_key: :student_id
      has_many :courses, through: :enrollments
    end
    
    # rails g model enrollment student:belongs_to course:belongs_to
    class Enrollment < ApplicationRecord
      belongs_to :student, class_name: 'User'
      belongs_to :course
    end
    
    class Course < ApplicationRecord
      has_many :enrollments
      has_many :students, through: :enrollments
    end
    

    And then you just create inputs that use the course_ids / course_ids= setter and getter created by has_many :courses, through: :enrollments.

    <%= form_with(model: @student, url: auth_student_path(@student), method: :put) do |f| %> 
      <div class="field">
        <%= f.label :course_ids, 'Select your courses' %>
        <%= f.collection_select :course_ids, @courses, :id, :name, multiple: true %>
      </div>
    
      # ...
    <% end %>
    

    And then you just whitelist an array of ids in your controller:

    params.require(:user)
          .permit(
            # ...
            course_ids: []
          )
    

    In fact if your ever passing existing records as anything but an ID you're doing it very wrong.

    There are still plenty of issues with this code but this should at least be nudge in the correct direction.