Rails 2.3.4 Persisting Model on Validation Failure - ruby-on-rails

I have a standard html form post that is being persisted and validated with a rails model on the server side.
For the sake of discussion, lets call this a "car" model.
When
car.save
is invoked the validators fire and set a list of fields in error within the model.
How best can I get an object back that has the fields whose validation failed removed, i.e. such that I don't have to delete the fields on the form the user entered correctly?
The validation patterns they use are great but how do I get a hook to when the validation fails such that I can delete the bogus entries from the model?
I could see manually iterating over the errors list and writing a big case statement but that seems like a hack.

In your controller, render the new action from your create action if validation fails, with an instance variable, #car populated from the user input (i.e., the params hash). Then, in your view, add a logic check (either an if block around the form or a ternary on the helpers, your choice) that automatically sets the value of the form fields to the params values passed in to #car if car exists. That way, the form will be blank on first visit and in theory only be populated on re-render in the case of error. In any case, they will not be populated unless #car is set.

EDIT:
Don't do this (see comments) but if you must here is how:
I ended up using a call to
#car.invalid?
inside of a loop to remove the invalid entries:
params[:car].each do | key, array|
if #car.errors.invalid?(key)
#car[key.to_sym] = ''
end
end
render :action=> 'new'
Note the use of the render here vs. redirect. Getting error messages to persist from the rails model validation across a redirect appears tricky, see:
Rails validation over redirect
I still feel like there should be a helper method for this or a better way.

Related

Why does the object id change following failed validations in a rails controller?

I've generated a simple rails app using scaffolding. In the user model I have added a validation for the presence of name. I am using debug(#user.object_id) and showing at the top of my user edit page in edit.html.erb
I've purposefully left the name blank and tried to submit the form. The usual errors are rendered but every time I submit the form, the #user's object id changes. Could someone please explain why the object_id is changing? My assumption was that the #user is still the same object (since we're in the same page, just adding errors and re-rendering the edit.html.erb on failed update)
You probably confused by the fact, that render :edit in the update method just renders the edit template, but doesn't redirect to the edit page - that's right.
But, actually this is what happens in your scenario:
edit page is visited, #user assigned in the edit method of UsersController
form is submitted, update action is called and found #user is assigned in the update method and renders edit template
Thus, on submitting a form different method is called and the state changes
No, your assumption is not correct. HTTP calls are stateless, meaning that state does not persist between calls (i.e. every call is independent of each other). Every time your form is submitted, a new object is created and assigned to the variable #user. Since a new (and different) object is created during each call, their object_ids will be different.

Can I stop Rails from storing invalid attributes on the model after update is called?

I've been developing on Rails for awhile and can't believe I haven't ran into this problem - maybe I am missing something simple?
My edit page displays information about the model being viewed. The model's to_s method returns the name attribute in this case, which is displayed in breadcrumbs and the page header.
I have a validation that the name cannot be blank and a simple update method:
def update
#model.update(permitted_params)
respond_with #model
end
The default ActionController responder will render my edit page automatically when #model is invalid, which it does. But #model still retains the invalid attributes so my breadcrumbs and page header are blank as they both display model.name which is "".
I could solve this by
def to_s
name.presence || name_was
end
But this application will be fairly large and most of my models will follow this same view pattern with the header containing a model attribute that could be invalid. I feel like using this pattern in to_s on all of my models will be frustrating to keep up with.
My current solution is to define this method in my custom responder, which reloads the #model if it is invalid:
class ApplicationResponder < ActionController::Responder
def initialize(*)
super
#resource.reload if has_errors?
end
end
This works but now any invalid request has an extra call to the database when the model is reloaded. Probably not a big deal, but still a code smell in my opinion.
Is there something I can do to stop Rails from keeping invalid attributes on #model after update? I am using Rails 4.1.0beta1 and Ruby 2.1 and have tried on Rails 4.0.0 as well.
Why not also provide some client-side validations to prevent users from submitting invalid details in the first place?
If you provided validations that give the user feedback before the page is re-rendered, you won't have the problem of ever having to work out how to display that invalid data (since it sounds like you are displaying data on the edit page itself).
I would recommend you check out Parsley.js or other similar JS based client-side validation.
The kind of behavior you are seeing, where they are still "stored" in the model object is a good thing in general because it allows you to automatically repopulate the input fields in a form with those incorrect values, so that a user can see what they entered wrong and change it.
If you are wanting to maintain certain 'correct' values statically on the page, like in a breadcrumb, I would copy those values off into their own variables and track them separately. For the breadcrumb on the application I work on, we actually store the link history in the browsers IndexedDB, and don't rely on what the current value of an object in the database is.
From my experience, most of the time the identifying attribute of an object, like it's name, isn't editable. If it is, I tend to make the name of my page something a little more general, like 'Edit Profile,' instead of 'Edit ', or 'Edit Organization' instead of 'Edit Name_of_Organization.'
If you really want to keep rendering the original name, I would just save that and track it through hidden HTML inputs. What you're doing with the to_s method isn't foolproof: if a user enters an invalid name that's not blank, you're going to render that invalid name.

submit in rails without formhelpers

I'm new to rails and still learning the ropes via railstutorial, but the book does all changes to the db via form submissions (http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html). But how would I go about submitting (updating the db) without this, lets say I want to add some score which is computed on page ,(for example via a js counter - for simplicity lets just say its a constant 10) and my db consists of a column called score. Then after pressing a submit button how would I go about updating the db?
Thanks
Two trivial ways:
Use a form
Use a URL parameter with a link
The processing (other than GET v. POST) on the Rails side is identical--it's just a parameter.
If you're using JavaScript, there's not necessarily a reason to not use a form, though, since you could just update a form input element and submit normally.
It is quite simple, actually.
The constant 10 is submitted from the view. The submit needs to point to the controller action that will handle this request. So the submit button should build the url using :controller, :action, :id parameters. In a form, this is handled in the form_for declaration. You will deal with in the button_tag declaration.
The routes should be configured so that this message can reach the controller/ action.
The constant 10 is transported in the params hash. If the field is my_counter, then look for params[:my_counter]. If the form had been for a model, say tweets, then it might be in params[:tweet][:my_counter].
In the controller action, possibly update, you will first fetch the record to change with something like #score = Score.find(:params[:id]). This params[:id] is also coming from the view with the submit. Change the counter here, and save.
def update
#score = Score.find(:params[:id])
#score.counter = params[:my_counter]
#score.save
redirect_to :action => :index # or wherever
end
Good luck.

Rails how to populate dropdown from DB. Herlp or ivar?

My rails app has a number of forms that use select tags. To date, to populate these selects I've been initialising an ivar(s).
But it is becoming bit of a maintenance issue. It's easy to remember to populate an ivar when you are in a new or edit action. It's the create and update actions that are more problematic. Those ivars required by the form only need to be populated when there is an validation (or error) issue with the model.
Considering that the ivars are not needed when responding to xml or json requests, I'm questioning is there a better way.
Is this a case where I should be using helpers within my _form partial? To date I've always considered database access within a helper to be a code smell - but now I'm not so sure.
Just in case my post isn't clear:
def create
#user = User.new(params[:user])
# this is what i do now - but is there a better way
unless #user.save
load_form_required_ivars # here I'm unnecessarily
# hitting the db for json/xml requests
end
respond_with(#user)
end
You can use attr_accessor and attr_accessible to hold these values of the drop down you are populating. as you validate in the create and then pass their values back if there are validation issues.
I don't get the part about xml and json being different however, but this should work for that too.

In ruby on rails what code would ask whether a form passed all validation or not?

I want to have my form forward to recaptcha but only after form has passed all validation. How would I achieve this before users details are saved to DB?
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
#to recaptcha, but before save and only after sign up form passes validation
else
format.html { render :new }
format.js { render :form_errors }
end
end
end
Have a good look at The definitive guide to form-based website authentication and ask yourself if you really need a captcha.
Besides that, you can use Callbacks :after_validation, before_save, around_save, after_save, before_create, around_create, after_create, before_update, around_update, after_update to handle stuff still inside your transaction.
The way to call one of these callbacks is to simply declare them in your model
If you need to use a captcha however, I would do this with javascript and ajax, to append it to your form before the user sends it.
You should not do this in the controller after recieving a post of the form, since you will have to:
Store the filled form values in the session after validation (dont save)
Redirect the user to a captcha page (which will make any user confused)
Check the captcha multiple times before it passes (they are quite unreadable)
Get the model out of the session (which you have no idea of which one it is)
Call save on the model to actually write it to your DB.
So basically you avoid starting a transaction before the captcha is passed.
Validation lives in the model, you could simply do this in the controller:
#user.valid?
and then do your recaptcha stuff.
Another solution is to use callbacks such as: before_save or before_create but only if recaptcha could be accessed in model (which I doubt).
This Railscast has all you need to know about multistep forms. The episode covers validation and moving back and forth between steps. http://railscasts.com/episodes/217-multistep-forms
It sounds like your form has two steps, the first being where they enter in all their information, and the second being just a captcha entry.
Now, in my opinion you should just roll the captcha into the main user entry form and keep it all to a single page rather than having a two step process, I've done both before and having the captcha be part of the same form is much, much easier and less complex. Having everything in a single form allows you to have all of your logic consolidated (mostly) into a single controller action. There may be logic you can abstract out of the controller into a helper method, like the verification of the captcha, which will make your controller action that much less complicated. The last thing you want to do is over-complicate your action logic.

Resources