Home > Blockchain >  how can i create three child records while updating a parent record in rails?
how can i create three child records while updating a parent record in rails?

Time:10-06

i have a relation like a teacher can have at max three students. I have a association like teacher has many students and student belongs to teacher. so while updating a teacher record im showing a form where student 1 followed by a text filed and student 2 followed by a test field and student 3 followed by a text filed.

the problem here is always last student data only getting saved. while updating. so how can i add a form so that it creates a student record if there are no records and update records if there is already records present.

how the form should be and the controller logic for this? i dont want to use nested form.

[params[:student1],params[:student2],params[:student3]].each do |student|
  @teacher.student.update_attributes(student)
end

and form is like

<%  students = @teacher.students %>
 <% if students.present? %>
 <% students.each do |student| %>
<div >    
      <%= label_tag "student", "name*" %>
      <%=
        text_field_tag(
           "student[name]",
            student.name,
           class: "form-control form-control-inline"
        )
      %>
    </div>
     <% end %>
    <% else %>
    <div >    
      <%= label_tag "name", "name*" %>
      <%=
        text_field_tag(
           "student1[name]",
            "",
           class: "form-control form-control-inline"
        )
      %>
    </div>
   <div >    
      <%= label_tag "name", "name*" %>
      <%=
        text_field_tag(
           "student2[name]",
            "",
           class: "form-control form-control-inline"
        )
      %>
    </div>
    <div >    
      <%= label_tag "name", "name*" %>
      <%=
        text_field_tag(
           "student3[name]",
            "",
           class: "form-control form-control-inline"
        )
      %>
    </div>
    <% end %>

can some one help me what i need to do? should i use build method in controller ? instead of update attributes ? if i use build will it work like a create method when there are no records and acts as a update method when there are 3 student records.

CodePudding user response:

I think find_or_create_by is what you are looking for. https://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by

CodePudding user response:

I really don't understand your reluctance to use a nested form, because it seems to me that Rails's built-in functionality for handling nested attributes is extremely well suited to your question, especially as you already have the teacher-student associations set up.

I understand you've asked to avoid nested forms, but just in case it's a matter of not fully appreciating how simple Rails makes it to do the precise thing you're after (or for other readers who would prefer to use idiomatic Rails), here's my suggestion to try and persuade you that nested forms is the way to go.

For example, let's assume a really simple schema:

  create_table "teachers", force: :cascade do |t|
    t.string "name"
  end

  create_table "students", force: :cascade do |t|
    t.string "name"
    t.integer "teacher_id"
    t.index ["teacher_id"], name: "index_students_on_teacher_id"
  end

Then set up the models:

# models/student.rb
class Student < ApplicationRecord
  belongs_to :teacher
end

# models/teacher.rb
class Teacher < ApplicationRecord
  has_many :students

  accepts_nested_attributes_for :students, limit: 3, reject_if: :all_blank, allow_destroy: true
end

The key piece here is the accepts_nested_attributes_for in the Teacher model. The limit means it won't process more than three items. The reject_if: :all_blank means that it'll ignore processing the item if its attributes are blank (without this, it would potentially add phantom students with names that are empty strings).

Your new/edit teacher views:

# views/teachers/new.html.erb
<h1>New teacher</h1>

<%= render "form", teacher: @teacher %>

<br>

<div>
  <%= link_to "Back to teachers", teachers_path %>
</div>

# views/teachers/edit.html
<h1>Editing teacher</h1>

<%= render "form", teacher: @teacher %>

<br>

<div>
  <%= link_to "Show this teacher", @teacher %> |
  <%= link_to "Back to teachers", teachers_path %>
</div>

And then the all important form partial that can be configured to handle the association within the form using fields_for:

# views/teachers/_form.html.erb
<%= form_with(model: teacher) do |form| %>
  <% if teacher.errors.any? %>
    <div style="color: red">
      <h2><%= pluralize(teacher.errors.count, "error") %> prohibited this teacher from being saved:</h2>

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

  <div>
    <%= form.label :name, style: "display: block" %>
    <%= form.text_field :name %>
  </div>

  <fieldset>
    <legend>Students</legend>
    <%= form.fields_for :students do |student_form| %>

    <div>
      <%= student_form.label :name, style: "display: block" %>
      <%= student_form.text_field :name %>
    </div>

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

The final piece is the controller. By default a new teacher (Teacher.new) will have no students and so the form will not contain any boxes to hold the students' names. Therefore one must first initialise the new students (in your case you want three per teacher.

Similarly, when editing a teacher, there may be 0 - 3 students assigned. So one must first establish how many are already persisted, and if less than the maximum, add blank ones.

# controllers/teachers_controller.rb
class TeachersController < ApplicationController
 # GET /teachers/new
  def new
    @teacher = Teacher.new
    3.times { @teacher.students.build }
  end

  # GET /teachers/1/edit
  def edit
    @teacher = Teacher.find(params[:id])
    ([email protected]).times {@teacher.students.build }
  end
end

In addition, to persist this data, we need to allow the nested attributes through:

# controllers/teachers_controller.rb (cont.)

    private

    # Only allow a list of trusted parameters through.
    def teacher_params
      params.require(:teacher).permit(
        :name,
        students_attributes: [:id, :name, :_destroy]
      )
    end

And so the create and update methods will look like

# controllers/teachers_controller.rb (cont.)

  def create
    @teacher = Teacher.new(teacher_params)

    if @teacher.save
      redirect_to teacher_url(@teacher), notice: "Teacher was successfully created."
    else
      render :new, status: :unprocessable_entity
    end

  end

  # PATCH/PUT /teachers/1
  def update
    @teacher = Teacher.find(params[:id])
    if @teacher.update(teacher_params)
      redirect_to teacher_url(@teacher), notice: "Teacher was successfully updated."
    else
      render :edit, status: :unprocessable_entity
    end
  end
  • Related