Prevent Devise from erasing entire registration form if a single field fails validation (Rails 4) - ruby-on-rails

I have the same problem as the person in this post:
rails 3 + devise: if registration form has error, how prevent from erasing data in other fields?
That is to say, Devise erases the entire registration form after a single field fails validation. I am using rails 4, however, and therefore am not sure how to implement the "virtual attributes" solution given to the poster above.
Any help would be very appreciated.

Related

Why Rails defaults creation to a POST on collection in HTML form?

When generating a scaffold, by default the new_resource_path generates a form that will submit to resources_path.
This makes total sense in a RESTful mentality.
But, given that the generated material does not uses it as a REST resource, why does it POST to the collection path?
When the resource is successfully created, Rails will redirect to the created resource path. When any error occurs, Rails will render the new template that will present the errors (generated by scaffolding).
This seems fine, except that when any errors occurs when trying to create the resource, the URL will change to the collection path. This means that if user tries to refresh the page, it will not see the creation form. If the application does not allow listing for this resource, a routing error may happen. In case the application uses any type of authorization and the current user does not has the required authorization to list stuff, it may see a forbidden.
I see Rails scaffold generator as something the community agrees to be the standard way to do basic CRUD in it. So, why this behavior?
It seems that by keeping a purist RESTful resources approach we are breaking user experience a bit.
To see an example of this, just create a new Rails application, scaffold a new entity and try to create it with some validation errors.
$ rails new example
$ cd example
$ rails generate scaffold note text
# edit app/models/note.rb
class Note < ApplicationRecord
validates :text, length: { minimum: 10 }
end
$ rails db:migrate
$ rails server
# go to localhost:3000/notes/new
# click 'Create Note'
# see the error
# hit browser's refresh button
# now you are listing notes, and not creating one
If you think "this should not harm a real application". I've come up with this when writing tests for authentication.
My application is using Devise and fails for this test:
test 'new user should not be able to register with wrong password confirmation' do
email = 'newuser#newdomain.com'
password = 'little$secret'
password_confirmation = 'big$secret'
visit new_user_registration_path
fill_in 'Email', with: email
fill_in 'Password', with: password
fill_in 'Password confirmation', with: password_confirmation
assert_no_difference ->{ User.count } do
click_on 'Sign up'
end
assert page.has_content?("Password confirmation doesn't match Password")
# FAILS:
assert_equal new_user_registration_path, current_path
end
What this means in real life: When user tries to create an account, submit an invalid form, see the error and hit refresh, it is on an invalid path as the resource does not support listing (i.e. /users).
To make that last assertion pass, I had to overwrite the default Devise view to submit the form to /users/sign_up instead of just /users and to add a new route to call create when a POST is made to this URL. Then I realized that this will happen to any controller following the RESTful Resource approach, unless developers create this new route and use a custom URL for submitting creation forms.
Also, the "purist RESTful Resource approach" doesn't seem to be so purist. When you submit your form with invalid data, the POST will result in a 200 OK rendering an HTML with errors, instead of a 400 Bad Request. So, why not submit the form to the same URL the form exists in?
My bet is that I'm missing something, but I can't figure it out. So, what am I missing?
But, given that the generated material does not uses it as a REST
resource, why does it POST to the collection path?
So, why not submit the form to the same URL the form exists in?
Because the rails conventions embrace statelessness.
The form that you see when a create fails shows the result of a POST request. It is not meant to be repeated - or shared.
You could potentially have POST /notes/create and create a GET /notes/create route so that it would show the form after a refresh - but is that a good design from a framework point of view? I would say no.
Forms that POST back to the same URL can give a bad user experience - like the "Confirm form submission" dialog when you hit the back button. This is actually worse than the scenario you are painting up as it can lead to unexpected consequences for the user.
I see Rails scaffold generator as something the community agrees to be
the standard way to do basic CRUD in it.
The rails scaffold command is a rapid prototyping tool. They are not meant as the authoritative source of the "right" way to do rails nor does the community hold them as the word of god.
Also, the "purist RESTful Resource approach" doesn't seem to be so
purist.
The Rails community is not very purist. If anything its quite pragmatic and aims towards embracing concepts like REST but with a focus on developer convenience and "should just work".
When you submit your form with invalid data, the POST will
result in a 200 OK rendering an HTML with errors, instead of a 400 Bad
Request.
This is pragmatism, back in the day Internet Explorer would do all kinds of annoying things when given 4XX response codes. 200 OK guarantees the client will render the response - although it is tecnically wrong.
This seems fine, except that when any errors occurs when trying to
create the resource, the URL will change to the collection path. This
means that if user tries to refresh the page, it will not see the
creation form.
I don't get you : If you refresh the page, it will just re-POST the same parameters and so show the same form with errors. I just re-checked that.
If the application does not allow listing for this resource, a routing
error may happen. In case the application uses any type of
authorization and the current user does not has the required
authorization to list stuff, it may see a forbidden.
So, a user would not be allowed, for example, to view a list of posts, but it would allowed to create a new one ?

rails 4 how to add custom form fields

How can we add custom form fields in rails 4.
For ex:
User (model)
username
email
hashed_password
Now in the form field for password I would like to have fields like password and password confirm.
But due to the introduction of strong parameters in rails 4, I cannot simply send those fields .
It throws error.
It used to be a piece of cake in prior versions of rails i.e in the model we would add our custom form fields to attr_accessor and we could play with them.
NOTE: I tried this thing few weeks ago and it did'nt work, and I don't remember the exact error but was something like 'no method password on user'.
I know it is very stupid of me to not provide you with the exact error messages.
My apologies, can't help I deleted the application.
I was following this tutorial (it's for rails 3) http://www.sitepoint.com/rails-userpassword-authentication-from-scratch-part-i/
Check out How is attr_accessible used in Rails 4? for using strong parameters in rails 4
Protecting attributes is now done in the Controller.
I'm using attr_accessor in model in Rails 4.
In forms I have
<%= text_field_tag 'param_name' %>
And in controller
#model.param_name = params[:param_name]
It works ok and can solve your problem, I think. BUT! I'm not sure it is secure to use it this way. If someone can explain, why it is not appropriate for security reasons and suggest a better way, I'll appreciate it (as the author, I think).

Updating attributes from retrieved objects that has required attr_acessors - Rails Tutorial - Michael Hartl's

On Chapter 7 from Michael Hartl's tutorial there is a User model <code here> that has a password attribute defined as a attr_accessor and also as attr_accessible with a presence validator.
The problem is: if I retrieve an existent User and try to update its email, ruby throws an exception claiming for its password, and I'm forced to re-set the password every time I want to update any other attribute. Example:
User.first.update_attributes!(:email => "example#mail.com")
ActiveRecord::RecordInvalid: Validation failed: Password can't be blank, Password is too short (minimum is 6 characters)
I added ":on => :create" next to the password's presence validator and it seemed to solve the problem. Is this the right solution regarding a login system?
Anyone who has completed the Michael Harlt's tutorial knows if that was an error or it was his real intention?
You want to add the validator to the password hash field so that it is the actual password stored in the database which is checked. If you make the password= function set the value of a hash column, then this method will work independently of the actual password virtual variable.

Migrating existing user model to Devise

I'm trying to migrate a legacy app to Rails 3 and change the authentication to use Devise. I've created the model and migrations and imported all the user data.
I don't plan to migrate the passwords over as the existing scheme is not one we'd like to use going forward, but I want to be able to present users with a simple experience.
Ideally I'd like to catch a login error and then check the password with the legacy field and then update the Devise password with it if it matches.
I can see that Warden gives me a callback that can trap errors so I expect I can trap a login error.
However because all the passwords (in Devise) are blank I get errors relating to the hash as the encrypted_password fields are empty.
Is there a way I can update all the user accounts with a random password?
I've seen in Devise::Models::DatabaseAuthenticatable that there is a method 'password=' but if I call that, e.g. in rails console for the app:
User.find(1).password=('new')
=> "new"
I just get the same plain text string back ('new') and saving the user record post this doesn't populate the encrypted_password field.
I've searched around but can't seem to be able to find it. Any suggestions much appreciated!
Ok just in case anyone else is as cloth headed as I have been the last 24 hours, here's how you set the password:
user = User.find(id)
user.password = 'new-password'
user.save
Simple really :)

Rails Form Validations for Multi-Model Forms

I am trying to build a Rails app with multiple models in a single form, and multiple forms on a single page. To make that work (according to my limited knowledge), I have to drop out of the scaffold code and the "form_for :model" helper and use "form_tag" instead. However when I do that, I lose the ability to automatically catch and report form validation errors in the view (with the error message in the flash[:error] and have the invalid fields highlighted.
If I have a controller method for a form that has to validate data from multiple models, how to I pass the validation errors back to the form? What do I have to do to get the invalid fields highlighted?
(For the longest time I didn't "get" Rails forms, because I thought they were useless Ruby wrappers for HTML code. Now that I am working in a non-Rails environment, I realize how much hard work they save because validation is tied to ActiveRecord validaions, and if a validation fails, the form can be reposted with the invalid fields hightlighted and a useful message in flash[:error]).
To add multiples models to a simple form, after rails 2.3 you just have to add accepts_nested_attributes_for in your model, the model that will be connected with your controllers and views, change the views to support information from another models (with field_for) and maybe build the reference objects in your controllers. Check these links:
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes.
http://github.com/alloy/complex-form-examples

Resources