Nested form update action producing duplicate results - ruby-on-rails

During the update action of a nested form, instead of updating the current nested records, it seems to create new nested records.
This is what my controller looks like :
class ApplicationsController < ApplicationController
before_filter :set_user_and_job
def new
job = params[:job_id]
#application = Application.build(job)
end
def create
#application = Application.new(application_params)
#application.save
redirect_to root_url, :notice => "You have now applied!"
end
def edit
#application = Application.find(params[:id])
#answers = []
#job.questions.each do |question|
#application.answers.each do |answer|
#answers << answer if answer.question_id == question.id
end
end
end
def update
#application = Application.find(params[:id])
#application.update_attributes(application_params)
redirect_to root_url, :notice => "You have updated your application!"
end
def destroy
Application.find(params[:id]).destroy
flash[:success] = "Application Deleted."
redirect_to root_url
end
def show
#application = Application.find(params[:id])
#answers = []
#job.questions.each do |question|
#application.answers.each do |answer|
#answers << answer if answer.question_id == question.id
end
end
end
private
def set_user_and_job
#user = current_user
#job = Job.find(params[:job_id])
end
def application_params
params.require(:application).permit(:job_id, :user_id, answers_attributes:[:question_id, :content]).merge(user_id: current_user.id, job_id: params[:job_id])
end
end
This is what my edit view looks like :
<% provide(:title, " Edit this application") %>
<div class="row">
<div class="span6">
<h2> Job: <%= #job.job_title %></h2>
<p> <%= #job.job_summary %> </p>
</div>
<div class="span6">
<h2> Applicant: <%= #user.name %></h2>
</div>
<div class="span12">
<h3>Edit your job application below.</h3>
</div>
</div>
<%= form_for [#job, #application] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<% #job.questions.each_with_index do |question| %>
<%= f.fields_for :answers, question do |question_field| %>
<%= question_field.label :content, question.content %>
<%= question_field.text_area :content, :value => "" %>
<%= question_field.hidden_field :question_id, :value => question.id %>
<% end %>
<% end %>
<%= f.submit "Submit the application", class: "button" %>
<% end %>
The Application Model itself:
# == Schema Information
#
# Table name: applications
#
# id :integer not null, primary key
# user_id :integer
# job_id :integer
# created_at :datetime
# updated_at :datetime
#
class Application < ActiveRecord::Base
belongs_to :job
belongs_to :user
validates :job_id, presence: true
validates :user_id, presence: true
has_many :answers
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
def self.build(job_id)
application = self.new
job = Job.find(job_id)
job.questions.count.times do
application.answers.build
end
application
end
end
And the Answer Model :
# == Schema Information
#
# Table name: answers
#
# id :integer not null, primary key
# application_id :integer
# question_id :integer
# created_at :datetime
# updated_at :datetime
# content :string(255)
#
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :application
validates :content, presence: true
end
From searching, I found this link, RoR nested attributes produces duplicates when edit , which suggests that I add the :id to application_params, however when I do that, I get the error
ActiveRecord::RecordNotFound in ApplicationsController#update
Couldn't find Answer with ID=5 for Application with ID=17
(That's also a bit weird, because the actual id of the answer is 39. 5 is actually the ID of the question :S )
What are your thoughts on this? Mentors of SO, help much appreciated :)

update_only does not work for has_many relationships. You need to add the nested attribute :id field to your strong parameters:
def application_params
params.require(:application).permit(:job_id, :user_id,
answers_attributes:[:id, :question_id, :content]).merge(user_id: current_user.id,
job_id: params[:job_id])
end

Try adding update_only to your call to accepts_nested_attributes_for.
accepts_nested_attributes_for :nested_attribute, update_only: true

Related

ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR: null value in column "content_id" violates not-null constraint

I'm currently building a rails 5 application in which a shout can be a TextShout or a PhotoShout. When creating a shout, content_id is not being pass in the params therefore raising a Not Null Value error. In the migration for the shout I can not find where the error is coming from. I have the following files.
show.html.erb
<%= form_for #shout do |form| %>
<%= form.hidden_field :content_type, value: "TextShout" %>
<%= form.fields_for :content do |content_form| %>
<%= content_form.text_field :body, placeholder: "shout here!", required: true %>
<%= content_form.submit "Shout!" %>
<% end %>
<% end %>
<%= form_for #shout do |form| %>
<%= form.hidden_field :content_type, value: "PhotoShout" %>
<%= form.fields_for :content do |content_form| %>
<%= content_form.file_field :image, required: true %>
<%= content_form.submit "Shout!" %>
<% end %>
<% end %>
<%= render #shouts %>
migration/_make_shouts_polymorphic.rb
class MakeShoutsPolymorphic < ActiveRecord::Migration[5.1]
class Shout < ApplicationRecord
belongs_to :content, polymorphic: true
end
class TextShout < ApplicationRecord; end
def change
change_table(:shouts) do |t|
t.string :content_type
t.integer :content_id
t.index [:content_type, :content_id]
end
reversible do |dir|
Shout.reset_column_information
Shout.find_each do |shout|
dir.up do
text_shout = TextShout.create(body: shout.body)
shout.update(content_id: text_shout.id, content_type: "TextShout")
end
dir.down do
shout.update(body: shout.content.body)
shout.content.destroy
end
end
end
remove_column :shouts, :body, :string
end
end
shouts_controller.rb
class ShoutsController < ApplicationController
def show
#shout = Shout.find(params[:id])
end
def create
shout = current_user.shouts.create(shout_params)
redirect_to root_path, redirect_options_for(shout)
end
private
def shout_params
{ content: content_from_params }
end
def content_from_params
case params[:shout][:content_type]
when "TextShout" then TextShout.new(text_shout_content_params)
when "PhotoShout" then PhotoShout.new(photo_shout_content_params)
end
end
def text_shout_content_params
params.require(:shout).require(:content).permit(:body)
end
def photo_shout_content_params
params.require(:shout).require(:content).permit(:image)
end
def redirect_options_for(shout)
if shout.persisted?
{notice: "Shouted Successfully"}
else
{alert: "Could not shout"}
end
end
end
neither text_shout_content_params and photo_shout_content_params are permitting a content_id, but it looks like you're trying to (or at least you should be intending to) create one in your content_from_params
def content_from_params
case params[:shout][:content_type]
when "TextShout"
shout = TextShout.new(text_shout_content_params)
when "PhotoShout"
shout = PhotoShout.new(photo_shout_content_params)
end
shout.save!
params[:shout][:content_id] = shout.id
params[:shout]
end
If you want the content to have an id you will have to create the shout using create instead of new. create will attempt to add a record to the database and will assign an id.
It may also be beneficial to use create! which will raise an error if the shout has invalid data and cannot be saved.
def content_from_params
case params[:shout][:content_type]
when "TextShout" then TextShout.create(text_shout_content_params)
when "PhotoShout" then PhotoShout.create(photo_shout_content_params)
end
end

How to submit nested attributes via parent _form submit?

Upon clicking submit only the Duel attributes are passing - not Dueler.
duels_controller.rb
def new
#duel = Duel.new
#user = User.find(params[:challenge_daddy]) # This pulls in the ID for Challenged User
# Current User
#duel.duelers << Dueler.new(user_id: current_user.id, user_name: current_user.name, user_last_name: current_user.last_name)
#current_user_challenges = current_user.challenges.order(:created_at)
# Challenged User
#duel.duelers << Dueler.new(user_id: #user.id, user_name: #user.name, user_last_name: #user.last_name)
#challenged_user_challenges = #user.challenges.order(:created_at)
respond_with(#duel)
end
I think I have to submerge the dueler info (i.e. full_name and collection_select) within something like <%= simple_form_for(#dueler) do |f| %>, but then I don't want two separate submit buttons. When the user clicks submit the dueler and duel information should both submit since they go hand-in-hand. Right now only the duel information submits and the duelers are never created.
duels/_form.html.erb
<%= simple_form_for(#duel) do |f| %>
<%= current_user.full_name %> WILL <%= collection_select(:dueler, :challenge_id, #current_user_challenges, :id, :full_challenge, include_blank: true) %>
<%= #user.full_name %> WILL <%= collection_select(:dueler, :challenge_id, #challenged_user_challenges, :id, :full_challenge, include_blank: true) %>
THE LOSER WILL <%= f.text_field :consequence %>.
<%= f.submit %>
<% end %>
UPDATE
Originally I had this in the _form:
<%= f.fields_for :duelers do |dueler| %>
<%= render 'dueler_fields', :f => dueler %>
<% end %>
But I took it out because the duels_controller new logic wasn't passing into it so I moved the code directly into the _form, but now I'm not sure what should take the place of <%= f.fields_for :duelers do |dueler| %>
class Dueler < ActiveRecord::Base
belongs_to :user
belongs_to :challenge
belongs_to :duel
end
class Duel < ActiveRecord::Base
belongs_to :user
belongs_to :challenge
has_many :duelers
accepts_nested_attributes_for :duelers, :reject_if => :all_blank, :allow_destroy => true #correct
end
class DuelsController < ApplicationController
before_action :set_duel, only: [:show, :edit, :update, :destroy, :duel_request]
respond_to :html
def index
#duels = Duel.joins(:duelers).all
redirect_to duel(#duel)
end
def duel_request
#dueler = #duel.duelers.where(user_id: current_user)
end
def show
#dueler = Dueler.find_by(user_id: current_user.id)
respond_with(#duel)
end
def user_challenges
#user = User.find_by_name(params[:name])
#challenges = #user.challenges.order(:created_at)
end
def new
#duel = Duel.new
#user = User.find(params[:challenge_daddy])
#duel.duelers << Dueler.new(user_id: current_user.id, user_name: current_user.name, user_last_name: current_user.last_name)
#current_user_challenges = current_user.challenges.order(:created_at)
#duel.duelers << Dueler.new(user_id: #user.id, user_name: #user.name, user_last_name: #user.last_name)
#challenged_user_challenges = #user.challenges.order(:created_at)
respond_with(#duel)
end
def edit
end
def create
#duel = Duel.new(duel_params)
#duel.save
#redirect_to duel_request_url(#duel)
respond_with(#duel)
end
def update
#duel.update(duel_params[:duelers_attributes])
respond_with(#duel)
end
def destroy
#duel.destroy
respond_with(#duel)
end
private
def set_duel
#duel = Duel.find(params[:id])
end
def duel_params
params.require(:duel).permit(:consequence, :reward, duelers_attributes: [:id, :user_id, :challenge_id, :accept])
end
end
If you are using has_many and belongs_to with accepts_nested_attributes you will need to use inverse_of to prevent Rails from attempting to lookup records (which of course don't exist because you haven't yet created them)
Change your Duel model has_many declaration to:
has_many :duelers, inverse_of: :duel
For further details on this and an example of a nested form with has_many relationship using Simple Forms check out:
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through

Rails - Use fields_for to create multiple nested objects

Hoping someone can help out with this. I have two models order and date_order. Each order can have multiple date_orders, and I should be able to create many date_orders as I create an order.
How do I do that? As you can see, my code is working well for creating ONE date_order and relating it to the created order.
UPDATE: I have tried to create many "builders" in my orders/new file. It worked on the view, and created an order when I entered multiple dates and times. But the fields_for did not create any date_orders.
orders_controller.rb
def new
#order = Order.new
#order.date_orders.build
end
def create
#order = Order.new(order_params)
if #order.save
flash[:success] = "blah"
redirect_to #order
else
render 'new'
end
end
private
def order_params
params.require(:order).permit(:user_id, :purpose,
date_orders_attributes: [:id, :order_date, :time_start, :time_end, :order_id])
end
order.rb
class Order < ActiveRecord::Base
has_many :date_orders, :dependent => :destroy
accepts_nested_attributes_for :date_orders, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
date_order.rb
class DateOrder < ActiveRecord::Base
belongs_to :order
end
order/new.html.erb
<%= form_for(#order, :html => {:multipart => true}) do |f| %>
## SOME QUESTIONS ##
<%= f.fields_for :date_orders do |builder| %>
<%= builder.label :date %>
<%= builder.date_field :order_date %>
<%= builder.label :starting_time %>
<%= builder.time_field :time_start %>
<%= builder.label :ending_time %>
<%= builder.time_field :time_end %>
<% end %>
<% end %>
Build more orders_dates:
class OrdersController < ApplicationController
def new
#order = Order.new
5.times { #order.date_orders.build } # < === HERE ===
end
private
def order_params
params.require(:order).permit(:user_id, :purpose,
# |- === HERE ===
date_orders_attributes: [:id, :content, :order_date, :time_start, :time_end, :order_id])
end
end
Update:
Also, add content to your strong params whitelist.

Rails fields_for not working

I am trying to use fields_for and create a nested form, however only one text field shows up, blank. I have 3 crewmember records.
crewmember model:
class Crewmember < ActiveRecord::Base
belongs_to :production
belongs_to :callsheet
validates :firstname, presence: true
validates :email, presence: true
def name
"#{firstname} #{lastname}"
end
end
callsheet model
class Callsheet < ActiveRecord::Base
attr_accessible :crewmembers_params
has_many :castmembers
has_many :crewmembers
accepts_nested_attributes_for :crewmembers
end
callsheets controller
class CallsheetsController < ApplicationController
def index
#callsheets = Callsheet.all
#departments = Department.where(production_id: current_user.default_working_production_id)
end
def show
#callsheet = Callsheet.find(params[:id])
end
def new
#callsheet = Callsheet.new
#departments = Department.where(production_id: current_user.default_working_production_id)
end
def edit
#callsheet = Callsheet.find(params[:id])
end
def create
#callsheet = Callsheet.new(callsheets_params)
#Callsheet.production_id = current_user.default_working_production_id
if #callsheets.save
redirect_to callsheet_path
else
render 'new'
end
end
def update
#callsheet = Callsheet.find(params[:id])
if #callsheet.update(callsheets_params)
redirect_to callsheet_path, :notice => "callsheets successfully updated."
else
render 'edit', :notice => "callsheets not updated."
end
end
def destroy
#callsheet = Callsheet.find(params[:id])
#callsheet.destroy
redirect_to callsheets_path
end
private
def callsheets_params
params.require(:callsheet).permit(:crewmembers_params [:id, :firstname])
end
end
form for new callsheet:
<%= form_for #callsheet do |f| %>
<% if #callsheet.errors.any? %>
<div id="error_explanation" class="alert alert-danger">
<strong>
<%= pluralize(#callsheet.errors.count, "error") %> prohibited
this call sheet from being saved:
</strong>
<ul>
<% #callsheet.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.fields_for :crewmember do |crewmember| %>
<fieldset>
<%= crewmember.label :firstname, "First Name" %><br />
<%= crewmember.text_field :firstname %>
</fieldset>
<% end %>
<% end %>
You don't need attr_accessible (that's only for Rails 3).
You should also rename all your models to snake_case, referencing with CamelCase:
#app/models/call_sheet.rb
class CallSheet < ActiveRecord::Base
has_many :cast_members
has_many :crew_members
accepts_nested_attributes_for :crew_members
end
As is the custom with fields_for, you also need to build the associated objects (if you're creating a new record) (you don't need to do this if editing):
#app/controllers/call_sheets_controller.rb
class CallSheetsController < ApplicationController
before_action :set_departments
def new
#callsheet = Callsheet.new
#callsheet.crew_members.build
end
def edit
#callsheet = Callsheet.find params[:id]
end
def update
#callsheet = Callsheet.find params[:id]
#callsheet.update callsheet_params
end
private
def set_departments
#departments = Department.where(production_id: current_user.default_working_production_id)
end
def callsheet_params
params.require(:callsheet).permit(crew_members_attributes: [:id, :firstname])
end
end
This will allow you to use:
<%= form_for #callsheet do |f| %>
<%= f.fields_for :crew_members do |crewmember| %>
<%= crewmember.label :firstname, "First Name" %><br />
<%= crewmember.text_field :firstname %>
<% end %>
<%= f.submit %>
<% end %>
--
When passing nested attributes through fields_for, you need several components:
The correct association in your parent model
An instantiated version of the associated model (#parent.build_child)
Correct fields_for definition
Passing correct parameters through your controller
I've outlined how to achieve the above, all of which you had incorrect.
You can also declare multiple validations in the same call:
#app/models/crew_member.rb
class CrewMember < ActiveRecord::Base
validates :firstname, :email, presence: true
end
Try changing
<%= f.fields_for :crewmember do |crewmember| %>
into
<%= f.fields_for :crewmember, #callsheet.crewmember || #callsheet.build_crewmember do |crewmember| %>

ActiveRecord::RecordNotFound: Couldn't find Answer with ID=5 for Application with ID=

I have a nested form, and am having trouble getting the create and update function to work.
I think what's happening is that the nested object is being created first, and is causing an error.
I get this error when trying to run the create action:
ActiveRecord::RecordNotFound in ApplicationsController#create
Couldn't find Answer with ID=5 for Application with ID=
Rails.root: /Users/stepan/Desktop/Sites/atlas
Application Trace | Framework Trace | Full Trace
app/controllers/applications_controller.rb:22:in `create'
This only happened after I allowed the :id parameter for the answers model. The reason I did that was to make it possible for the update action to work.
This is what my Controller Looks like ->
class ApplicationsController < ApplicationController
def new
#job = Job.find(params[:job_id])
#user = current_user
#application = Application.new()
#job.questions.count.times do
#application.answers.build
end
end
def create
#user = current_user
#job = Job.find(params[:job_id])
#application = Application.new(application_params)
#application.job_id = #job.id
if #application.save
redirect_to root_url, :notice => "You have now applied!"
else
render :action => 'new'
end
end
def edit
#job = Job.find(params[:job_id])
#user = current_user
#application = Application.find(params[:id])
#answers = []
#job.questions.each do |question|
#application.answers.each do |answer|
#answers << answer if answer.question_id == question.id
end
end
end
def update
#job = Job.find(params[:job_id])
#user = current_user
#application = Application.find(params[:id])
if #application.update_attributes(application_params)
redirect_to root_url, :notice => "You have updated your application!"
else
render :action => 'new'
end
end
def destroy
Application.find(params[:id]).destroy
flash[:success] = "Application Deleted."
redirect_to root_url
end
def show
#job = Job.find(params[:job_id])
#user = current_user
#application = Application.find(params[:id])
#answers = []
#job.questions.each do |question|
#application.answers.each do |answer|
#answers << answer if answer.question_id == question.id
end
end
end
private
def application_params
params.require(:application).permit(:id, :job_id, :user_id, answers_attributes:[:content, :question_id, :id]).merge(user_id: current_user.id)
end
end
This is the form:
<% provide(:title, " Apply to this job") %>
<div class="row">
<div class="span6">
<h2> Job: <%= #job.job_title %></h2>
<p> <%= #job.job_summary %> </p>
</div>
<div class="span6">
<h2> Applicant: <%= #user.name %></h2>
</div>
<div class="span12">
<h3>You're almost done! Answer the questions below and you'll be applied to the job.</h3>
</div>
</div>
<%= form_for [#job, #application] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<% #job.questions.each_with_index do |question| %>
<%= f.fields_for :answers, question do |question_field| %>
<%= question_field.label :content, question.content %>
<%= question_field.text_area :content, :value => "" %>
<%= question_field.hidden_field :question_id, :value => question.id %>
<% end %>
<% end %>
<%= f.submit "Submit the application", class: "button" %>
<% end %>
This is my Application Model:
# == Schema Information
#
# Table name: applications
#
# id :integer not null, primary key
# user_id :integer
# job_id :integer
# created_at :datetime
# updated_at :datetime
#
class Application < ActiveRecord::Base
belongs_to :job
belongs_to :user
validates :job_id, presence: true
validates :user_id, presence: true
has_many :answers
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
This is my answer model:
# == Schema Information
#
# Table name: answers
#
# id :integer not null, primary key
# application_id :integer
# question_id :integer
# created_at :datetime
# updated_at :datetime
# content :string(255)
#
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :application
validates :content, presence: true
end
There was a similar problem here --> Rails 4 Nested Attributes Unpermitted Parameters, But I don't quite understand what the answer is doing.
Help much appreciated!
Some issues you may benefit from looking into:
Application Create
Bluntly, I think you're trying to do too much with the application create function
There's a principle in Rails (and all MVC) called fat model skinny controller, meaning you should put as many actions into the model as possible. The controller really just needs to guide logic (call respective methods depending on model responses). Here's what I'd do:
#app/controllers/applications_controller.rb
before_filter :set_user
def new
job_id = params[:job_id]
#application = Application.build(job_id)
end
def create
#application = Application.new(application_params)
#application.save
redirect_to root_url, :notice => "You have now applied!"
end
private
def set_user
#user = current_user
end
def application_params
params.require(:application).permit(:job_id, :user_id, answers_attributes:[:question_id, :content]).merge(user_id: current_user.id, job_id: params[:job_id])
end
#app/models/application.rb
Class Application < ActiveRecord::Base
#Associations here
accepts_nested_attributes_for :answers
validates :title, :other_application_vars,
presence: true #-> shows errors if validation fails (no need for "if #application.save" logic.... needs testing)
def self.build(job_id) #-> class method -- not sure about passing arguments
application = self.new
job = Job.find(job.id)
job.questions.count.times do
application.answers.build
end
application
end
end
#app/models/answer.rb
Class Answer < ActiveRecord::Base
validates :question_id, :content,
presence: true
end
Strong Params
You mentioned it will only allow you to do this if you send the id param through. I don't have a super amount of experience here, but I've found you don't need to send the :id param through strong params. This is probably what's causing the issue - you're not setting the id param, and consequently it's getting confused
If you adopt a conventional way of doing this, you shouldn't have this error

Resources