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