Authlogic run validations on login prior to create action - ruby-on-rails

I need to run the built-in validations on the login field prior to actually creating the user record, is there a way to do this in Authlogic? The reason for is when a user types in a new login, AJAX is invoked to check and see that the login in unique, valid, etc. Once that is done, the user can enter his email to claim the login, it's a 2 step process.

The User model uses ActiveRecord validations, so this isn't specific to Authlogic. If you want to run the validations on a model you can call user.valid?. This will return true or false depending on if the entire model is valid. However it also fills up the user.errors object so you can then check if a given attribute is valid.
Here is some code that uses RJS to do the AJAX. But you can use anything and organize it however you want.
user = User.new(params[:user])
user.valid? # we aren't interested in the output of this.
error = user.errors.on(:login)
if error
page.insert_html :before, "user_login", content_tag(:span, error, :class => "error_message")
end
You may be interested in my Mastering Rails Forms screencast series where I cover this topic in the 2nd episode.

Related

Ruby on Rails: How to validate if on specific page?

In my Ruby on Rails application I am trying to add in validations that will ensure the user has entered a value in a text box. In my system I have a table called Account which stores users' email account information, when they go onto the views/accounts/_form.html.erb page to add a new email account I want to validate the presence of a port number. I can do this through the following code:
validates :port, presence: true
This successfully ensures that users enter their port number, but when a user signs up through the views/users/_new.html.erb page they have to enter only an email address (e.g example#example.com) and the users_controller will then create a record in the Account table for this email address. My problem is that on the views/accounts/_form.html.erb page the port number is required but on the views/users/_new.html.erb it is not.
Is there a way of validating that the user enters the port number if they are on the views/accounts/_form.html.erb page or invoking the create method in the accounts_controller?
I am aware that I could do this through the HTML required validation like so: <%= f.text_field :port, :required => true %> but I need to add in further validation as well as presence, so this is not suitable.
You can create an attr_accessor field that determines if the validation should occur...
class Account < ActiveRecord:Base
attr_accessor :port_needs_validation
validates :port, presence: true, if: -> {port_needs_validation}
Then just set the accessor in your create method...
def create
#account = Account.new
#account.assign_attributes(account_params)
#account.port_needs_validation = true
if #account.save
...
Extract that part of the logic into a form object, check out the legendary 2012 blog entry from CodeClimate. Things have changed since then, the author uses Virtus to build form objects, more popular & up-to-date gems these days are:
reform
dry-rb
active type
but really you can make anything behave like an ActiveModel object
if it's a one-off thing, just do what Steve said in the other answer but that is a sure way to hell, safe-hate and divorce (at least from personal experience) in any slightly teeny weeny bigger project (i.e. you mean to spend some hours more working on it, it's not like you just finished everything and want to go home).
Actually, just use form classes everywhere and avoid model validations & other callbacks at all. You don't want things sending account activation mails or validating your password complexity when you're writing tests and just need a "post" that belongs to "user".
My own favorite personal fuckup due to model callbacks is sending 240.000 "your account has been upgraded/downgraded" emails because of an innocent spelling change update in an account_type attribute migration just because account_type_changed? was true.
So.. Form classes for ever, model callbacks never.
I would not recommend you have model aware of views. In #SteveTurczyn 's solution, an abstract field is introduced into model to identified the which page it come from, which is an good solution.
As from Ruby on Rail MVC, both View and Model talk to the controller, another solution will be have controller handle validation of params before passing the value to create account.

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 - ping user without authenticating?

So I'm writing a Facebook clone for a school project using Rails and I need some way to keep track of which users are logged in. At the moment, I'm a bit time-pressed, so I decided just to update the User model every time they visit a page with a last_seen attribute.
Problem is, the user model requires revalidation to successfully update_attributes. So I'm wondering two things:
Is there a better way to do this that I'm missing?
If not (or if it would take too long) is there a way to bypass the validation?
to 1.: I cant give you an exact answer but I think itwould be better to deal with this problem using a javascript on the clientside with a timer that sends an ajax request all xxx secounds and an action that receives this requests and saves it in a seperate table associated with the User.
to 2.: Yes there are some ways to bypass validations The most pragmatic way is to bypass the :validate => false option when saving the object but then you can use update_attributes:
object.save(:validate => false)
So there is also the possibility to use conditional validations that are only used when a specific condition is complyed. There is a railscast about that => http://railscasts.com/episodes/41-conditional-validations .

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.

Is there a way in Rails to say "run all the validates EXCEPT :password"?

I am using Devise for my authentication. If a hashed_password isn't set, Rails/Devise's validations will require a password to be set, as well as the password_confirmation.
When I invite new users, I obviously don't want to set their password, so when I create the invitation in my system, it fails because user.password is blank.
I can set a temporary hashed_password on the user, but when they enter their own password, the validation checks for :password and :password_confirmation will not happen because hashed_password is set, which is a real problem.
Is there any way to tell Rails that I want to run all the validations except for the ones associated with :password?
I know Rails has :if conditions, which might fix my problem, but Devise declares the :password validation on my behalf, so that essentially is hidden.
How can I get the desired result here?, hopefully in a way that is not a hack.
My current hypothetical solution that is somewhat messy: The only thing I can think of is to create a new Invitation model that is not the User model, and use the Invitation model for the form. When the invitation is submitted I can validate that Invitation and copy over all the values to the new User model. I can save that User without any validations at all.
That's the best solution I dreamed up.
It seems like my solution will be a lot more work than saying something simple like:
user.save(validations => {:except => :password})
EDIT: I have found one part of the solution, but I am still having problems. In our user model, we can override a Devise method to prevent the validation of the password for invitations with this bit of code:
#protected
def password_required?
!is_invited && super
end
The is_invited attribute is just a column I added to the users table/model.
However, there is one gotcha here. When a user accepts an invitation and they arrive to the form where they need to set their password/password_confirmation, valid? will always return true.
This one has me deeply perplexed. I don't see how requires_password? and valid? can be true at the same time. If it requires the password, it should do a validation check and cause the validations to fail.
I'm starting to hate Devise - or just the idea of using gems to build parts of your application in a blackbox. I think the real solution probably is to rip out Devise and just do it all from scratch. That way your app has total control of how all of this works :(
I recently started using this great devise add-on: devise_invitable
It's commonly used so users (or any model) can invite other users to join.
But I adapt it for manually (via an admin panel) invite new potential users to my app.
Hope this helps!

Resources