When my Rails uniqueness validation fails, I would like to display the duplicate record using a partial in the form view when the user is sent back.
How do I accomplish this? Right now I only know how to alter the error message that is displayed.
There isn't a built-in way to do this in Rails, and there's probably a dozen ways to do it. Here's one approach, while not elegant, should get you what you want.
1) Check if there's a uniqueness validation error. Unfortunately there isn't an easy way to do this, so you'll have to check the name of the error in your controller. If there's an error, search for the existing record, and store it in an instance variable:
def create
#new_model = Model.new(params[:model])
if #new_model.save
# success...
else
if #new_model.errors[:field].index("has already been taken")
#existing_record = Model.where(field: model.field).first
end
render :new
end
end
2) Render the existing record in a partial in your view
<%= render partial: 'partial_name', locals: {existing_record: #existing_record} %>
Related
This one is a bit challenging so bear with me. Here is the summary. I added a custom validation to one of my models. After I added this everything works fine with all actions except the update action. If I restrict the new validator to only the create action, then the update action works fine. Below is the related code:
In my model
validate :start_must_be_before_end_time
def start_must_be_before_end_time
return if customer_start.blank? || customer_end.blank?
if customer_start > customer_end
errors.add(:customer_start, "start time must be before end time")
end
end
In my controller for the update action:
def update
#handover = Handover.find(params[:id])
if #handover.update_attributes(params[:handover])
UpdatedHandover.perform_async(#handover.id)
flash[:success] = "Handover Template Updated and Approvals Sent!"
redirect_to view_context.select_handover_cal(current_user)
else
flash[:error] = "Please correct the following errors in your form!"
render edit_handover_path(#handover.id)
end
end
So if the start time is before the end time in the create action, everything works fine. It renders the new action and displays the error. If this happens in the update action it gives me a missing template error for the edit action. The edit file is in the proper place and this works if the validator is restricted to the create action. I cannot figure out for the life of me why this is giving me so much trouble. This is rails 3.2.18. Thanks for your help!
You should pass template name to render method, not a path. So if you want to render 'edit.html.erb', pass 'edit'.
Change
render edit_handover_path(#handover.id)
to
render 'edit'
Note that if you used extra instance variables in edit template, you would need to set them in update action.
For better understanding of render:
When you use render, you pass the instantiated object (newly created or updated). When attempting to update the object, validation was triggered and, if somehow unsuccessful, you render edit, since your in memory object contains necessary validation errors.
But edit_something_path is used when you use redirect_to. When you have the object saved and you can get the persisted data from the database.
You problem can be solved in two ways:
render 'edit'
render :edit
# Your validation errors will persist
or
redirect_to edit_handover_path(#handover.id)
# Your validation errors will be gone
I think this is a pretty simple question but nothing I've read has answered my question directly:
I have a new products page with a standard form. After successfully submitting the form, I redirect to a custom controller action and view called "thanks".
On the "thanks" page, I want to be able to print the name of the product just created and possibly some other attributes.
How do I pass the object just created into my new action? Right now the controller looks like this:
def create
#product = Product.new(params[:product])
if #product.save
flash[:notice] = "Successfully created Product."
redirect_to thanks_path
else
render :action => 'new'
end
end
def thanks
end
You can't send object through redirect.
There are three ways to solve your problem:
Render the 'thanks' template directly(not action #thanks)
render 'thanks' # thanks template
You can send whatever instance variable to this template directly. #thanks is no longer needed in this case.
Drawback: The url won't be changed.
Convey messages through session
If you want to show certain messages, you can prepare it in #create and send it through session or flash(part of session actually). flash is better as you don't need to clear it manually.
Note: You may want to use ActiveRecord as session storage if the message size is big, otherwise you'll meet CookiesOverflow by default setting.
Send very simple message through session say obj_id
Similar to #2 but I thinks this is better than #2. In #thanks, you can construct complex message according to if obj_id is present, what is the id and then find related data through db.
You have two fairly decent options.
First, you could adjust the thanks_path route to take an id parameter, and call it like redirect_to thanks_path(#product). Then you can call it up in your thank you method like any standard show method. It might be worth mentioning that if you are going to be displaying sensitive information on the thank you screen, you may want to use a random uuid, instead of an id, to look up the product.
A better way might be to not redirect at all, but rather adjust your view from simply drawing the form to something like this:
<% if #product && !#product.new_record %>
THANK YOU MESSAGE GOES HERE
<% else %>
EXISTING FORM GOES HERE
<% end %>
So i have the following code:
def new
#all_areas = Area.all
#area = Area.new
end
The reason i am passing in all_areas is it's required for a drop down box in the form, Using mongoid and an Area can be recursively embedded in another Area.
My form has the following code:
<% if #all_areas %>
<%= f.label :parent_area %>
<%= f.collection_select(:parent_area, #all_areas, :_id, :name, prompt: "Select a Parent...") %>
<% end %>
However when i submit 'invalid values, i.e blank name, the 'new' page does not render the select box to select a parent.
What is going on here? Is this a bug?
My create action is pretty simple, if it fails validation i just do the following:
else
render 'new'
Why is #all_areas not passed to the view the second time? i have actually fixed it by changing the code in my create action to the following:
else
#all_areas = Area.all
render 'new'
But this is quite surprising, unless i am missing something?
It's not a bug. The reason for the behaviour is that the create action is actually quite separate from the new action, so the instance variables you assigned in new don't get carried over. render 'new' only renders the view called "new", it doesn't actually call the new action.
new happens when you issue a GET request to /areas/new. create happens when you POST to /areas. Because they're separate requests, the server doesn't remember any state - in fact, you could call create without ever calling new (say if you used curl from the command line).
Basically, your approach is correct, you need to set the #all_areas instance variable in both actions. You might want to extract it out into a separate private method to avoid the duplication.
I have a form where users can enter an isbn and it will try to lookup book data and save it.
When validation fails for the isbn lookup (for example if somebody entered it incorrectly), I would like it to redirect to another form where users can enter data in manually if the isbn lookup fails (but not if other validations like numerical price fail).
Any ideas on how to do this? Thanks for the help!
Trying to understand what you're trying to do: please correct me if my assumption is wrong.
If you can't save the model because the ISBN failed validation and you want to display a form for just the ISBN since the other fields are OK, there's a couple things you can do to hold the other attributes in the meantime:
Output them as hidden fields when you render the form
Store them in session so you can redirect
If you can't save the model then there doesn't seem to be any reason for redirecting to another action: the user is still trying to complete the create action, except you want to render a different form for just the ISBN.
Here's how I'd do it using session, so you can adapt this for redirecting to another action if you need to:
def create
book = Book.new( params[:book].reverse_merge(session[:unsaved_book]) )
if book.save?
session.delete[:unsaved_book]
flash[:notice] = 'I love it!'
redirect_to book
else
if book.errors.on[:isbn] && book.errors.length == 1
session[:unsaved_book] = params[:book]
flash[:error] = 'Sorry, wrong ISBN number.'
render 'unknown_isbn'
else
flash[:error] = 'Check your inputs.'
render 'new'
end
end
end
I'd give them the option to re enter the isbn if the lookup failed, as it might just be a typo.
For the redirecting part:
redirect_to invalid_input_path and return unless model.valid?
redirect_to isbn_lookup_failed_path and return unless model.do_isbn_lookup
....
I have a validation that needs to be done in controller. If it fails I need to go back to the view action back again with all values populated as it is on the page.
Is there a simple way to do that (using incoming params map).
This is the basic way all Rails controllers and scaffolds work. Perhaps you should try generating scaffolds?
def create
#banner_ad = BannerAd.new(params[:banner_ad])
if #banner_ad.save
flash[:notice] = 'BannerAd was successfully created.'
redirect_to :action => "show", :id => #banner_ad
else
render :action => "new"
end
end
end
I populate a #banner_ad here, attempt to save it, if it fails, I return to the form and the #banner_ad object is available to me. I then need to have a form that uses the Rails form helpers to populate the values from the object.
Depends on the flow of your app, really.
If the validation fails, you could pull the data out fo the database ...
if invalid?
#model = model.find(:id
end
Otherwise you might need to store the original values in hidden fields in the view and use those.