I have a form with nested attributes and my model code contains:
accepts_nested_attributes_for :current_skills, :allow_destroy => true, reject_if: lambda {|attributes| attributes['skill_id'].blank?}
In my controller:
def edit
#employee = Employee.find(params[:id])
...
#employee.current_skills.build
#employee.desired_skills.build
end
def update
...
#employee.current_skills.build
#employee.desired_skills.build
if #employee.update_attributes(employee_params)
redirect_to #employee, notice: 'Employee was successfully updated.'
else
render :edit
end
end
In the case of a validation failure, the update, as expected, is not being saved and the edit view is being rendered. My understanding is that I need the .build methods in the update action to resupply the empty fields to the form (this is the case in my testing); however, when including the .build methods to supply the fields, they are not being rejected by the model on the second submit and are being saved to the database (not as I would expect).
Is there a better way to supply the fields after a validation failure but also have them rejected if blank?
Thanks for the help!
If I were you I would do this:
def update
...
if #employee.update_attributes(employee_params)
redirect_to #employee, notice: 'Employee was successfully updated.'
else
#employee.current_skills.build if #employee.current_skills.empty?
#employee.desired_skills.build if #employee.desired_skills.empty?
render :edit
end
end
I hope it helps.
Edit: After read your comment, I must to say: your code is fine and it must to work. Is not standard build an association in update method, and I can't see why you need it (if is for the tests, remove from main code and work on the tests). At this point, you must check the params at the server log, or a callback in yours models that set the skill_id value. BTW if the record saved have an skill_id not nil, your accept_nested_attributes_for is working fine.
Related
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.
I have a issue with a custom validation that checks the number of associated objects. There are two classes, Event and Guest.
When the user creates a new event, he can select some guests to be invited. Upon save, I want to do some check on the number of invited guests.
The validation works fine when updating an existing Event. But upon creation of a new event, the validation fails because self.guests.count returns 0.
In the controller, I checked that i the selected guests are passed to the model in the same way: params[:event][:guest_ids].
In case of an update, I can see that an SQL INSERT INTO to update the association table is added to the transaction queue before my validation block is called. But in case of a new event, the SQL INSERT doesn’t show up before the validation fails.
What am I missing here? I was thinking about some workarounds, but I’m sure there is a proper way to implement this.
Rails version is 4.2.6
class Event < ActiveRecord::Base
has_and_belongs_to_many :guests
validate :check_guestlist
def check_guestlist
guest_count=self.guests.count
if guest_count < 3
errors.add(“Please invite more guests”)
end
end
class Guest < ActiveRecord::Base
has_and_belongs_to_many :events
end
class EventsController < ApplicationController
def create
#event = current_user.events.new(event_params)
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'Event was successfully created.' }
else
flash.now[:alert]='Event not saved.'
format.html { render action: "new" }
end
end
end
def update
#event = current_user.events.find(params[:id])
respond_to do |format|
if #event.update_attributes(event_params)
format.html { redirect_to #event, notice: 'Event was successfully updated.' }
else
flash.now[:alert]='Changes not saved.'
format.html { render action: "edit" }
end
end
end
end
Ok the reason why self.guests.count doesn't work on create is that this is actually doing a database query, with the scope of event.
SELECT COUNT(*) FROMguestsWHEREguests.event_id= 1
Now of course this fails because the validation occurs before the guests have been saved. Now on update it works because there are entries to be found in the database. However the validation is checking against the wrong information - the count of guests in the database doesn't necessarily match the number of self.guests - you could have added one more guest, which has yet to be saved.
Your solution does work but personally I think it would be better to write it as:
validates :guests, length: { minimum: 3, message: 'Please invite more guests' }
length will test the size of the guests array - e.g. self.guests.length would be effectively the same as self.guests.to_a.count
I managed to work around this problem by replacing a line of code in the custom validation.
If I replace
guest_count=self.guests.count
with
guest_count=self.guests.to_a.count
I get the expected results.
In case of an update, both expressions return the same result. But in the context of a new record, only the second one responds as expected.
Nevertheless, I still don't understand why the first version didn't work.
I have a form used to create clients, and in one of the fields I have to choose the language of the client. In the model I have a validation to check the field is not null, but the validation error is getting displayed even when a language is provided.
View:
<%= f.input :locale, as: :select, collection: locale_for_select, prompt: false %>
Model:
validates :locale, presence: true
Controller:
def new
end
def edit
end
def create
if #client.save
redirect_to #client, notice: t_notice('controllers.successfully_created', Client)
else
render action: "new"
end
end
def update
if #client.update_attributes(params[:client])
redirect_to #client, notice: t_notice('controllers.successfully_updated', Client)
else
render action: "edit"
end
end
I have used the browser's developer tools to check the value is actually being send, although the validation at the model fails.
Any idea about what's going on?
EDIT:
I have noticed this error only happens when creating a new client, not while editing an existing one. However, when I edit a client this new value is not being persisted to database
EDIT 2:
Using rails 3.2.22
Using ruby 2.1.6
EDIT 3:
This is strange because in the same form I have some other select inputs that are working properly, and which are treated in a similar way.
def create
#client = Client.new(params[:client])
if #client.save
redirect_to #client, notice: t_notice('controllers.successfully_created', Client)
else
render action: "new"
end
end
def update
#client = Client.find(params[:id])
if #client.update_attributes(params[:client])
redirect_to #client, notice: t_notice('controllers.successfully_updated', Client)
else
render action: "edit"
end
end
The problem was that I had added a localized field using Globalize, and I did not take this into account:
Because globalize uses the :locale key to specify the locale during
mass-assignment, you should avoid having a locale attribute on the
parent model.
What I did to solve this issue is renaming my :locale attribute to :client-locale and execute the corresponding migration to rename the column.
In a Rails 3.2 app, I have a validation for an attachment type.
Attachment model:
class Attachment < ActiveRecord::Base
validates_presence_of :name
validates_attachment_presence :attach, :message => "No file selected"
validate :check_type
def check_type
if self.costproject_id != nil
if self.attach_content_type != 'application/pdf'
self.errors.add(:pdf, " ONLY")
return false
end
end
end
But, the return false sends me to this URL:
http://localhost:3000/attachments
I want it to go back to the previous input screen:
http://localhost:3000/attachments/new?costproject_id=2
How do I accomplish that?
Thanks!!
UPDATE1
Perhaps the redirect has to take place in the controller?
format.html { render action: "new" }
Attachment controller:
# POST /attachments
# POST /attachments.json
def create
#attachment = Attachment.new(params[:attachment])
respond_to do |format|
if #attachment.save
format.html { redirect_to session.delete(:return_to), notice: 'Attachment was successfully created.' }
format.json { render json: #attachment, status: :created, location: #attachment }
else
format.html { render action: "new" }
format.json { render json: #attachment.errors, status: :unprocessable_entity }
end
end
end
I changed this line:
format.html { render action: "new" }
To:
format.html { redirect_to request.referer }
And now it goes back to where I want. But, I've lost the errors - they don't display.
To help you understand what's going on here. When you go to /attachments/new you are rendering a form. When you press submit, you are sending a POST request to /attachments, which invokes the create action.
You're create action appears to be solid and idomatic. However when you render action: "new" in the case of an error, it's not a full redirect, it's rendering the form in the context of the current action.
Normally this is fine, because idomatic rails would have you building a single, very similar, model object in both new and create, and the form for helper would render that object. However your new action is creating all kinds of objects based on a large assortment of query parameters, which I'm guessing is why you are seeing behavior you don't like.
I expect your final solution will involve bringing all those parameters into Attachment in some way, if they don't need to be saved to the database, you can make attr_accessors on Attachment
# Model
class Attachment < ActiveRecord::Base
attr_accessor :worequest_id, :workorder_id # etc
end
# View
<%= form_for #attachment do |f| %>
<%= f.hidden :worequest_id %>
<% end %>
Approaching it this way, your post request params will look like
{
attachment:
{
worequest_id: 1,
# etc
}
}
And you would also need to rework your query params to nest the inidividual ids inside of an attachment
/attachments/new?[attachment][worequest_id]=1
This way you could build attachment from params in both actions:
Attachment.new(params[:attachment])
And now your current create action should more or less work as expected, because now it's idomatic rails.
You still aren't going to get the new action with the same query params, but since you are taking those params and filling them in hidden fields on the form, they won't be lost when you try and fail to create. In any case, unless you do something to persist the values between requests, the POST to /attachments is going to wipe out the ery params.
Try this.
Replace
return false
With
redirect_to request.referrer || root_url
Note: root_url here is a catchall. Also this is Rails 4, I do not know if it also applies to Rails 3. Worth a try, though.
Debug ideas
First confirm a simple redirect_to root_url (or whatever name you use for your root) works in your controller
redirect_to root_url
Then, once redirect_to confirmed working, focus on getting the REST interface "request." information. There's a Rails 3 discussion here which may help you.
How to get request referer path?
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