How to eager load a fields_for with validation - ruby-on-rails

I have this fields_for in my form:
= f.fields_for :parts do |builder|
= builder.input :body, label: builder.object.another_relation.description, required: false
In the main model I have this validation:
validates_associated :parts
In the parts model I have this validation:
validates :body, presence: true
This works well. When I leave some part empty and try to create the model, the form is re-displayed again with proper error messages.
But in the logs I see that the label is querying the database for each part, so I need to eager load this.
How?
This was my try:
= f.fields_for :parts, f.object.parts.includes(:question_part) do |builder|
= builder.input :body, label: builder.object.another_relation.description, required: false
This eager loads the relationship successfully, but when I leave some field empty and try to create the model, the form is re-displayed with all parts empty and no errors. I think this is normal, as I'm forcing the fields_for to load fields from that collection I'm passing.
Anyone knows how to solve this?

Here's a way to do it.
Imagine these 4 models, related to a question-answers site like StackOverflow, so everyone knows what we are talking about :)
So, a question has N parts, and also N answers. While an answer belongs to a question, and has N parts. Finally, an answer part belongs to a question part.
class Question
has_many :parts, class_name: 'QuestionPart', dependent: :destroy
accepts_nested_attributes_for :parts, allow_destroy: true
has_many :answers, dependent: :destroy
# ...
end
class QuestionPart
belongs_to :question
# ...
end
class Answer
belongs_to :question
belongs_to :user
has_many :parts, class_name: 'AnswerPart', dependent: :destroy
accepts_nested_attributes_for :parts, allow_destroy: true
# ...
end
class AnswerPart
belongs_to :answer
belongs_to :question_part
validates :body, presence: true
# ...
end
Let's take a look at the answer controller:
class AnswersController < ApplicationController
def new
#question = Question.find(params[:question_id)
#answer = #question.answers.new
#question.parts.each { |p| #answer.parts.build(question_part: p) }
end
def edit
#answer = Answer.find(params[:id])
#answer_parts = #answer.parts.includes(:question_part)
end
def create
#question = Question.find(params[:question_id])
#answer = current_user.answers.build(answer_params.merge(question: #question))
if #answer.save
redirect_to #question, notice: 'The answer was successfully created.'
else
render :new
end
end
def update
##answer = Answer.find(params[:id])
#answer = Answer.where(id: params[:id]).includes(parts: [:question_part]).first
if #answer.update(answer_params)
redirect_to #answer.question, notice: 'The answer was successfully updated.'
else
render :edit
end
end
private
def answer_params
params.require(:answer).permit(:body, parts_attributes: [:id, :question_part_id, :body])
end
end
Please notice how I retrieve the answer in the update method. I have commented out the standard way, while I'm retrieving it with a custom query (which should be moved into the Answer model). Here is where I had trouble. If anyone knows a better way, just comment or add a new answer.
Now for the templates:
# new.html.erb
...
<%= form_for [#question, #answer] do |f|
<%= render 'form', f: f, parts: #answer.parts
<% end %>
# edit.html.erb
...
<%= form_for [#answer] do |f|
<%= render 'form', f: f, parts: #answer_parts || #answer.parts
<% end %>
# _form.html.erb
...
<%= f.fields_for :parts, parts do |builder| %>
<%= builder.text_field :body, label: builder.object.question_part.description, required: false
<%= builder.hidden_field :question_part_id %>
<% end %>

Related

has_one nested association nullifies foreign key on edit route

I am building a nested form in ruby on rails.
The addition of a nested has_one association works fine. However, when I load the edit page, the foreign key company_id of the nested association is nullified.
I have tried update_only: true in accepts_nested_attributes_for and including :id in strong params as suggested in other similar answered questions on stackoverflow but nothing works for me.
Could anyone tell me what is actually causing the nested association to update and nullify its foreign key itself? My codes are as shown below. Thanks!
# company.rb
class Company < ApplicationRecord
has_one :mission
accepts_nested_attributes_for :mission, update_only: true
end
# mission.rb
class Mission < ApplicationRecord
belongs_to :company, optional: true
validates :description, presence: true, length: { maximum: 100 }
end
# companies_controller.rb
class CompaniesController < ApplicationController
def edit
#company = Company.find(params[:id])
#company.build_mission if #company.build_mission.nil?
end
def update
#company = Company.find(params[:id])
#company.assign_attributes(company_params)
if #company.valid?
#company.save
redirect_to companies_path
end
end
private
def company_params
params.require(:company).permit(mission_attributes: [:id, :description, :_destroy])
end
end
# edit.html.erb
<%= form_for #company, :url => company_path(#company), :html => {class: 'ui form', method: :put} do |f| %>
<%= f.fields_for :mission do |mission| %>
<div class="field">
<%= mission.label :mission %>
<%= mission.text_field :description %>
</div>
<% end %>
<%= f.button :submit => "", class: "ui button" %>
<% end %>
Hey I manage to solve the problem after a good sleep. Turns out i just have to play around with the if else condition at the companies controller level. The edit method should be amended to such:-
def edit
#company = Company.find(params[:id])
if #company.mission
else
#company.build_mission
end
end

Rails nested fields creation in loop

I have three model classes related to each other.
class Student < ActiveRecord::Base
has_many :marks
belongs_to :group
accepts_nested_attributes_for :marks,
reject_if: proc { |attributes| attributes['rate'].blank?},
allow_destroy: true
end
This class describes a student that has many marks and I want to create a Student record along with his marks.
class Mark < ActiveRecord::Base
belongs_to :student, dependent: :destroy
belongs_to :subject
end
Marks are related both to the Subject and a Student.
class Subject < ActiveRecord::Base
belongs_to :group
has_many :marks
end
When I try to create the nested fields of marks in loop labeling them with subject names and passing into in it's subject_id via a loop a problem comes up - only the last nested field of marks is saved correctly, whilst other fields are ignored. Here's my form view code:
<%= form_for([#group, #student]) do |f| %>
<%= f.text_field :student_name %>
<%=f.label 'Student`s name'%><br>
<%= f.text_field :student_surname %>
<%=f.label 'Student`s surname'%><br>
<%=f.check_box :is_payer%>
<%=f.label 'Payer'%>
<%= f.fields_for :marks, #student.marks do |ff|%>
<%#group.subjects.each do |subject| %><br>
<%=ff.label subject.subject_full_name%><br>
<%=ff.text_field :rate %>
<%=ff.hidden_field :subject_id, :value => subject.id%><br>
<%end%>
<% end %>
<%= f.submit 'Add student'%>
<% end %>
Here`s my controller code:
class StudentsController<ApplicationController
before_action :authenticate_admin!
def new
#student = Student.new
#student.marks.build
#group = Group.find(params[:group_id])
#group.student_sort
end
def create
#group = Group.find(params[:group_id])
#student = #group.students.new(student_params)
if #student.save
redirect_to new_group_student_path
flash[:notice] = 'Студента успішно додано!'
else
redirect_to new_group_student_path
flash[:alert] = 'При створенні були деякі помилки!'
end
end
private
def student_params
params.require(:student).permit(:student_name, :student_surname, :is_payer, marks_attributes: [:id, :rate, :subject_id, :_destroy])
end
end
How can I fix it?
#student.marks.build
This line will reserve an object Mark.
If you want multi marks, May be you need something like this in new action :
#group.subjects.each do |subject|
#student.marks.build(:subject=> subject)
end
Hope useful for you.

Simple_form: simple_fields_for not showing the form

I'm trying to create two model in just one form. I have user and unit model both are associated with has_many :through association.
user.rb
class User < ActiveRecord::Base
has_many :units, :through => :user_unit_assocs
has_many :user_unit_assocs
end
unit.rb
class Unit < ActiveRecord::Base
has_many :users, :through => :user_unit_assocs
has_many :user_unit_assocs
end
users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new(user_params)
#user.units.build
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
else
format.html { render :new }
end
end
end
private
def user_params
params.require(:user).permit(:username, :email, units_attributes:[:unitname])
end
end
This is my form, users new
<%= simple_form_for(#user) do |f| %>
<%= f.simple_fields_for :units do |unit| %>
<%= unit.input :unitname, required: true %>
<% end %>
<%= f.input :name, required: true %>
<%= f.input :email, required: true %>
<%= f.button :submit %>
<% end %>
When I run the code, the form only showing a input-field for :name and :email and there is no input-field for units:[:unitname]. How can I show the input-field in the form? Thanks in advance.
References:
1. Rails 4: accepts_nested_attributes_for and mass assignment
2. https://github.com/plataformatec/simple_form/wiki/Nested-Models
Add
def new
#user = User.new
#user.units.build
end
on your controller
You need to build the association in the new action, not the create action.

Deep Nested Rails 4 Form

I have 3 models Item which accepts nested attributes for questions and questions accept nested attributes for answers. I'm trying to create an item which has a question and an answer in the same form.
item.rb
class Item < ActiveRecord::Base
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions
end
question.rb
class Question < ActiveRecord::Base
belongs_to :item
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers
end
answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
end
item_controller.rb
class ItemsController < ApplicationController
def new
#item = #repository.items.new
questions = #item.questions.build
answers = questions.answers.build
end
def create
#item = Item.new(item_params)
if #item.save
redirect_to #item, notice: '...'
else
render action: 'new'
end
end
private
def item_params
params.require(:item).permit(:id, :content, :kind, :questions_attributes => [:content, :helper_text, :kind], :answers_attributes => [:content, :correct])
end
end
_form.haml
= simple_form_for(#item) do |f|
= f.input :kind
= f.input :content
= f.simple_fields_for :questions do |q|
= q.input :content
= q.simple_fields_for :answers do |a|
= a.input :content
= f.submit
The form is being displayed correctly and it saves the question model correctly. I can't seem to save the answer though.
I've already looked at a lot of online help but none are covering it with Rails 4 strong params.
I think your problem is with your strong params:
def item_params
params.require(:item).permit(:id, :content, :kind, questions_attributes: [:content, :helper_text, :kind, answers_attributes: [:content, :correct]])
end
Basically, when you pass a deep nested form (where you have multiple dependent models), you'll have to pass the attributes as part of the other model's attributes. You had the params as separate
I run into a similar issue and, while Richard Peck answer helped me as well, there is one thing that it was missing for me.
If you are deep-nesting you need to specify the id of the parent of the nested item. In this case to create an answers you need to make questions id explicit with q.input :id, otherwise you will run into this error.
= simple_form_for(#item) do |f|
= ...
= f.simple_fields_for :questions do |q|
= ...
= q.input :id
= q.simple_fields_for :answers do |a|
= ...
= f.submit

Building a nested form with a many-to-many relationship in Rails 4

I have a model (User) that has_many of another model (Profession) - and this is supposed to be represented by one (or multiple) select menu in a form.
I cannot get my head around why the select menu doesn't get rendered? Am I constructing the select helper in the wrong way? Or is something else wrong in the view or the controller? The name attribute of the User is showing up alright in the form.
The models:
class User < ActiveRecord::Base
has_many :occupations, dependent: :destroy
has_many :professions, through: :occupations
accepts_nested_attributes_for :occupations
end
class Profession < ActiveRecord::Base
has_many :occupations, dependent: :destroy
has_many :users, through: :occupations
end
class Occupation < ActiveRecord::Base
belongs_to :user
belongs_to :profession
end
The controller:
def edit
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user, notice: 'User was successfully created.'
else
render action: 'new'
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email, ocuppations_attributes: [:id, :user_id, :profession_id])
end
The view (compressed):
<%= form_for(#user) do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :occupations do |builder| %>
<%= builder.select :profession_id, Profession.all.collect {|x| [x.title, x.id]} %>
<% end %>
<% end %>
Shouldn't that be a collection select?
<%= builder.collection_select(:profession_id, Profession.all, :id, :title) %>

Resources