Validation error with select input using ActiveRecord - ruby-on-rails

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.

Related

Rails redirect if validation fails

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?

nested attributes using reject_if not working after render

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.

How does rails populate forms?

Let's say I create a scaffold:
rails g scaffold Cat name:string age:integer
and I add a presence validation on the Cat model's age attribute:
validates :age, presence: true
When I attempt to create a cat via the form, and put in the cat's name but purposely leave out the cat's age the controller bounces me back to the form but that cat's name is still present in the name field!
How is this happening?
I would have thought the
#cat = Cat.new
would replace all of the invalid cat's attributes. Maybe if it were #cat ||= Cat.new I could understand that more.
Also, how can I make this behaviour happen in a more complex rails app? I have a simple forum where topics has_many replies. I create my new replies via a form in my topic show view:
topic#show:
#reply = Reply.new
topic/show.html.erb:
<%= form_for [#toplic, #reply] do |f| %>
<%= f.text_field :name placeholder: 'Create a new name...' %><br>
<%= f.text_area :description, placeholder: 'Create a new description...', rows: 5 %><br>
<%= f.submit 'Create Discussion' %>
<% end %>
While everything works perfectly, when I purposely leave out a reply's name, though I am redirected back to the form and an error flash shows, my form is completely empty. All of the attributes have vanished? Why is this?
The key to understanding how this works is to realize that in the case of a form failure, the controller action is not rerun, but rather the template is rendered using the existing state from the action.
In a typical Rails scaffold, your create action will look like this
def create
#cat = Cat.new(cat_params) # instance variable is initialized with the form values
if #cat.save
redirect_to #cat, notice: 'Success!'
else
# in the case of form failure, we will re-render the 'new' template
# this will NOT rerun the entire 'new' action, thus the #cat variable
# will still maintain the values from the form that we gave it above
render 'new'
# note the difference if we had instead done a redirect_to; this would
# cause the CatsController#new action to be re-run which would reinitialize
# the #cat variable according to the code within the 'new' action
# redirect_to new_cat_url
end
end
For your more complex example, you'll want to follow the same procedure, making sure you just re-render the form and don't redirect to another action (which will cause the state to be lost).
# TopicsController
def show
#topic = Topic.find(params[:id])
#reply = Reply.new
end
# RepliesController
def create
#reply = Reply.new(reply_params) # init the var with the form values
if #reply.save
redirect_to #topic, notice: 'Success!'
else
# this is the key - we need to re-render the template of the previous action
# in this case, it would be the TopicsController#show template
render 'topics/show'
# Remember - if we instead do a redirect_to #topic, then we will lose the form
# values which are currently set in the #reply variable.
end
end
In short, make sure you recognize when you are redirecting to a new action versus just re-rendering a template.
One important GOTCHA to be aware of when re-rendering a template is that you must make sure that all the instance variables which exist for the controller action are available when you render the template.
For example,
# TopicsController
def show
#topic = Topic.find(params[:id])
#reply = Reply.new
#foo = Foo.new
end
# RepliesController
before_action :set_topic
def create
#reply = Reply.new(reply_params)
if #reply.save
# ...
else
# we need to remember to set up a #foo variable here otherwise it will be undefined
# when used within the 'show' template
#foo = Foo.new
render 'topics/show'
end
protected
def set_topic
#topic = Topic.find(params[:topic_id])
end
OK, so, you go go /cat/new. Rails' route for this URL runs the method CatsController#new, which renders the new.html.erb template. You put in your data, then hit submit. The action for this form is to POST to /cats, which runs the CatsController#create method. This method does this following:
#cat = Cat.new(cat_params)
It then tries to save the Cat. If it succeeds, it redirects you to the Cat's URL. If not, it re-renders the new.html.erb template. That's where the name comes from — the CatsController#update method creates its Cat from the values you put into the original form.
For a typical scaffold create action:
# POST /products
# POST /products.json
def create
#product = Product.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
format.html { render action: "new" } #will re-submit
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
The magic happens in the render method! which will submit the previous POST request (won't go back to the new action while a redirect will do), this way the submited values are still there. (check this SO question for more details)
That's said, this behavior relies on following the convention, however sometimes you need to give it a hand specially with some inputs (e.g selects, checkboxes, radio) might require extra setup using selected or value options

Validation errors appearing before submitting information in Rails?

I have two models, Character and Initiative, and their relationship is Character has_one Initiative and Initiative belogns_to Character. I'm working on validation for Initiative, and I have it working, but the issue is all of my validation errors appear when creating a new Initiative record for a Character, before entering any information. Any ideas? Here's my code from Initiatives controller:
def new
#character = Character.find(params[:character_id])
#initiative = #character.create_initiative(params[:initiative])
end
def edit
#character = Character.find(params[:character_id])
#initiative = #character.initiative
end
def create
#character = Character.find(params[:character_id])
#initiative = #character.create_initiative(params[:initiative])
if #initiative.save
redirect_to character_path(#character), :notice => "initiative successfully created!"
else
render :action => "new"
end
end
def update
#character = Character.find(params[:character_id])
#initiative = #character.initiative
if #initiative.update_attributes(params[:initiative])
redirect_to character_path(#character), :notice => 'Initiative information was successfully updated.'
else
render :action => "edit"
end
end
And here's the validation itself from my model:
validates_presence_of :dex, :misc, :speed
validates_numericality_of :dex, :misc, :speed
I'm pretty sure the problem lies in the create or new methods, but I'm not sure why it's triggering the validation before a user enters any information. Any help? Maybe not a huge concern, since the code IS working, but I'd rather not display an error message before actually getting an error. Thanks!
shouldn't you be using build_initiative instead of create_initiative in your new action ? no need to save an object when sending to the user a form that intends to create it. Moreover, if your character has_one initiative, he can only have one so i doubt AR appreciates that you try to create another.
see http://guides.rubyonrails.org/association_basics.html#has_one-association-reference

How to stay at same url when validation fails

I'm using Ruby on Rails 2.3.8 and I've got a registration form in which I receive a parameter as follows: /registration/4, which 4 is the id of a user who recommended the user that is about to register in the website.
The problem is that if the validation fails when the user submits the registation (the form renders to the controller users, action create_particular) the site will redirect to /users/create_particular, and therefore I lose the parameter with value 4 that I had before. Besides, I want the user to stay at the same url, which is /registration/4
How can I do that?
Then you should rewrite your create method. You should use redirect_to :back instead of render :action
UPD
def new
#word = Word.new(params[:word])
#word.valid? if params[:word]
end
def create
#word = Word.new(params[:word])
if #word.save
redirect_to #word
else
redirect_to new_word_path(:word => params[:word] )
end
end
Looks quite dirty, but this is just a scratch
UPD 2
This is really not the best solution, but it works
# routes.rb
match 'words/new' => 'words#create', :via => :post, :as => :create_word
# words_controller
def new
#word = Word.new
end
def create
#word = Word.new(params[:word])
respond_to do |format|
if #word.save
format.html { redirect_to(#word, :notice => 'Word was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
# views/words/new.html.erb
<%= form_for(#word, :url => create_word_path) do |f| %>
...
<% end %>
Submit to the current URI (e.g. action=""). When the submission is valid, redirect. POST->Redirect->GET is a good habit.
From the top of my head:
Edit your controller (registrations_controller.rb file). Create method by default contains following piece of code:
if #registration.save
format.html { }
format.xml { }
else
format.html { }
format.xml { }
end
Add redirect_to (:back) between brackets to else format.html{}
Ok I solved the problem by doing the following:
1) I created two routes with the same path, but with different conditions method (one it's post and the other one is set to get)
2) I changed the form in order to post to the POST action defined above
3) I added render => :my_action when the validation fails
So that's pretty much it.
Thanks anyway for all your help.
Hidden field. That user ID param has a name by which you extract it in your controller, right? So just put that value in a hidden field of the same name, then it will survive a round-trip.
For example:
<%= hidden_field_tag :referring_user_id, params[:referring_user_id] %>

Resources