Associated records get overwritten when changing one record - ruby-on-rails

When i try to edit the value of a column of one record, which is associated to a parent record (e.g. Jobs has many Jobdetails) every record gets overwritten instead of just this one record.
For example: a job has many jobdetails. I want to be able to edit the title of one jobdetail. when i save the record it works but every other jobdetail belonging to the job has the same title now. The creation of new records works without problems, only the editing is making troubles.
Models
Jobs has_many :jobdetails
Jobdetail belongs_to :job
Routes
resources :jobs do
resources :jobdetails
end
Jobdetails_controller.rb
def edit
#job = Job.find(params[:job_id])
#jobdetail = Jobdetail.find(params[:id])
end
def update
#job = Job.find(params[:job_id])
#jobdetail.update(jobdetail_params)
respond_to do |format|
if #job.jobdetails.update(jobdetail_params)
format.html { redirect_to job_jobdetail_path(#job, #jobdetail), notice: 'Jobdetail was successfully updated.' }
else
format.html { render :edit }
format.json { render json: #jobdetail.errors, status: :unprocessable_entity }
end
end
end
def set_jobdetail
#job = Job.find(params[:job_id])
#jobdetail = Jobdetail.find(params[:id])
end
i think the duplicated syntax in set_jobdetail and in the edit action are not necessary.
I tried several different syntaxes but they all wont work. thanks in advance!

Update the line that's doing a "massive" update:
if #job.jobdetails.update(jobdetail_params)
To do a single one:
if #jobdetail.update(jobdetail_params)
With #job.jobdetails.update you're getting all the jobdetails associated to #job, and updating all of them with the values from jobdetail_params. As you've already initialized the specific jobdetail you want to update (#jobdetail = Jobdetail.find(params[:id])), you must to invoke update on that object.

Related

nested form rails 4 save existing record on create

I am struggling to get this working. I have three models
Student
Classroomattnd
Classroom
Using the has_many :through relationship. All my relationships are defined correctly and I have setup the nested form using the accepts_nested_attributes.
So when creating a new student I want to select from a list of classrooms instead of creating a new classroom. The form part also works fine the part I am not getting is when I create the student it complains about the following error.
Couldn't find Classrooom with ID=3 for Student with ID=
I have searched around for few days now but can not get the answer I need to get this working.
def new
#student = Student.new
#student.classrooms.build
end
def edit
end
def create
#student = Student.new(student_params)
respond_to do |format|
if #student.save
format.html { redirect_to #student, notice: 'Student was successfully created.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
Can someone help here, someone must of face this issue before?
Also in the rails console when I run the following it works:
classroom = Classroom.last
student = Student.create(name: 'Dave', classrooms:[classroom])
Your parameter handling isn't supporting nesting. You can look at request parameters in your server log or inspect the fieldnames of your generated form to be sure of your target. It's going to be something along the lines of
def student_params
params.require(:student).permit(:student => [:name, :classroom => [:id, :name]])
end
Or maybe as below. In this second case I'm not assuming everything in the form is nested under a student container. Also note the switch from classroom to classroom_attributes which is a change I have sometimes needed to make even though the form above is what the docs indicate.
def student_params
params.require(:name).permit(:classroom_attributes => [:id, :name])
end
Hopefully that gives you a notion of how to tailor your parameter definition to what your form is generating. Also note your error messages give you indication of what part of your definition is failing, eg the missing Student id in the error you quote.

Keep changes on reload if validation fails

I'm working with validations in rails, stuff like:
validates_presence_of :some_field
I've noticed that if the validation fails, all changes are overwritten with existing values from the database. This makes some sense, as the page is basically being reloaded (as I gather from my development log), however this increases the risk of user error/frustration, as a single error in one field will require the hapless fellow to re-enter the changes he made to all fields.
My question: How can I get rails to reload the data that was just submitted if validation fails? That way, the user can correct the mistake without needing to re-enter the rest of his revisions.
Thanks for any advice.
Edit:
My update method, as requested, is as follows:
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
if #company.update(company_params)
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation)
end
end
Full disclosure regarding my controller: the above update is from my incorporations_controller even though I'm updating my Company model. Company has_one :incorporation. I did this because, in the larger context of my app, it made my associations much cleaner.
Update your controller to this
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to({:action => "index"})}
else
format.html{render :edit}
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
To add to the correct answer, you can clean up your code quite a bit:
def update
#incorporation = Incorporation.find params[:id]
respond_to do |format|
if #incorporation.update company_params
format.html { redirect_to({:action => "index"})}
else
format.html { render :edit }
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
If you're using accepts_nested_attributes_for, you definitely should not hack the associated objects on the front-end.
You should look up fat model, skinny controller (let the model do the work):
#app/models/company.rb
class Company < ActiveRecord::Base
before_update :set_name
attr_accessor :name_string, :name_suffix
private
def set_name
if name_string && name_suffix
self[:name] = "#{name_string} #{name_suffix}"
else
self[:name] = "Company #{id} (Untitled)"
end
end
end
This will allow you to populate the name of the `company. To edit your nested/associated objects directly is an antipattern; a hack which will later come back to haunt you.
The key from the answer is: render :edit
Rendering the edit view means that your current #company / #incorporation data is maintained.
Redirecting will invoke a new instance of the controller, overriding the #incorporation, hence what you see on your front-end.

Item description table to create a corresponding entry into the notes table

So I am still fairly new to rails & just getting the hang of it. So my scenario is that when an item description table is being filled out by an employee, I would require automatically a corresponding entry into the Notes Table, with the items that have been selected from the Item Description table to also show up in the Notes view.
What is the best way to go about this? Can anybody suggest a course of action?
For example : first you must etc... then you must etc....
My model for my item_description.rb is:
class ItemDescription < ActiveRecord::Base
belongs_to :job
end
my model for my note.rb is :
class Note < ActiveRecord::Base
belongs_to :job
end
In my item_description controller my create block of code looks like this:
def create
#job = Job.find(params[:job_id])
if #job.item_descriptions.create(item_description_param)
redirect_to job_item_description_path(#job), :notice => 'Equipment was successfully created.'
end
respond_to do |format|
if #item_description.save
format.html { redirect_to #item_description, notice: 'Item description was successfully created.' }
format.json { render action: 'show', status: :created, location: #item_description }
else
format.html { render action: 'new' }
format.json { render json: #item_description.errors, status: :unprocessable_entity }
end
end
end
Are you saying that when you submit a form for a new item_description, you'd like a new note object to be created based on the value(s) that are chosen in this new item_description form?
If so, can you please post the form for the new item_description as well as schema for the item_description model and the note model (what attributes do they have)?
It seems to me that this would be a simple job from your item_description create action. Inside create, just create a new note and pass it the value(s) you gathered from the params of your item_description form
Hopefully I'm understanding the situation correctly?
EDIT:
Whatever the attributes are that your collecting in the form (whether they be strings, or whatever), everything is available in your params hash. Below is code for your controller (assuming the values you're collecting for the new note are represented in the code below by job_attribute_1 and job_attribute_2 - or however many attributes you need to create a new note).
def create
#job = Job.find(params[:job_id])
if #job.item_descriptions.create(item_description_param)
#job.notes.create!(job_attribute_1: params[:job_attribute_1], job_attribute_2: params[:job_attribute_2]
redirect_to job_item_description_path(#job), :notice => 'Equipment was successfully created.'
end
job = Job.create!
job.item_description.create! field1: "information1", field2: "information2"
job.note.create! field3: "information3", field4: "information4"
Does that answer your question?

Rails 4 - Delete rows from another table before updating

I have the following code in my members controller:
def update
#member.phone_numbers.destroy_all
respond_to do |format|
if #member.update(member_params)
format.html { redirect_to #member, notice: 'Member was successfully updated' }
else
format.html { render action: 'edit' }
end
end
end
I have to delete existing records from phone_numbers before updating members, because the phone numbers must be inserted again (because of possible ordering changes and other reasons, but it does not matter).
The question is: It works, but if members fail to update, all the phone numbers will be already deleted.
What could be done to avoid the problem if the #member.update fails?
You may consider marking the phone numbers for destruction instead of actually deleting them.
#member.phone_numbers.map(&:mark_for_destruction)
Then, when you do #member.update, it should do the update and the destruction of the associated phone numbers all at once. Here's the API for #mark_for_destruction: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-mark_for_destruction
Otherwise, you can look into setting up a transaction block. The API explains this well enough: http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
Wrap this into a helper method that wraps the statements into a transaction and call from the controller:
# Member Model
def delete_phone_numbers_and_update(params)
Member.transaction do
phone_numbers.destroy_all
update(params)
end
end
# Controller
def update
respond_to do |format|
if #member.delete_phone_numbers_and_update(member_params)
format.html { redirect_to #member, notice: 'Member was successfully updated' }
else
format.html { render action: 'edit' }
end
end
end

Save referenced resource on update_attributes (create nested resource on edit)

I have something like issue tracking system where there are issues and they have some comments.
Now on one page I want to give user an option to edit some stuff of "issue" as well as add a comment. Editing of and issue is a standard stuff like in /edit but also I want to create a comment and validate if it's not blank.
I've figured out that I can build a comment and make a form for it, but how should I check simultaneously that both issue attributes and comment attributes are valid? Because each update should be followed by a new comment, but I don't want to create a new comment if the issue attributes are no valid.
I would approach this by first adding fails_validation? methods to both your Issues and Comments models to check for problems.
Second, you will have to manually load the #issue form data from params[] and validate it BEFORE you save it (can't use update_attributes(params[:issue]).) Create a new Comment and load it via params[]. Then you can test the validation on both models and go back to the edit action if either fails.
If both pass you can save #issue and then #comment as normal.
def update
#issue = Issue.find(params[:id])
# manually transfer form data to the issue model
#issue.title = params[:issue][:title]
#issue.body = params[:issue][:body]
#...
#comment = #issue.comments.new(params[:comment])
# validate both #issue and #comment
if #issue.fails_validation? || #comment.fails_validation?
flash[:error] = "Your edits or your comment did not pass validation."
render :action => "edit",
end
# validation passed, save #issue then #comment
respond_to do |format|
if #issue.save
#comment.save
format.html { redirect_to #issue, notice: 'Issue successfully updated. Comment created' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #issue.errors, status: :unprocessable_entity }
end
end
end
Not the most elegant solution, but it should work.
You can validate the comment model and the issue model in their respective classes.
It is not clear to me whether you are using 'accepts_nested_attributes_for' in Issue for comments. If you are, then the standard IssueController#update will not save the record if issue is invalid and consequently, it will not create the comment records as well.
Here is the standard IssueController#update:
class IssueController < ApplicationController
def update
#issue = Issue.find(params[:id])
if #issue.update_attributes(params[:issue])
redirect_to issues_path, notice: 'issue updated'
else
render action: 'edit'
end
end

Resources