I've a variable registered(boolean) in my table User. Because when a connected user want to use an Email, if this email is not existing into the table User, a new line is created in the Users (only with the variable email, and registered = false).
So I would like that when a user registers, he will not be blocked if(email exist && registered == false).
In this case, the password will be replaced, and the variable registered change to true.
I would like to run something like that :
def create
#user_registred = User.find(email: params[user: :email])
if #user_registred.any?
if #user_registred.registred == false
#user = User.update(params[:user])
else
notice: "Email already taked"
end
else
#user = User.new(params[:user])
end
end
I think devise already provide uniqueness on email. Apart from this instead of having another column you can put validation of uniqueness also here. Apart from this you can also set database level uniqueness constraint.
Update :
have a look at the below code.
user = User.where(email: 'test#email.com').first_or_initialize
if user.new_record?
user.save
Do your stuff.
else
user.update_attributes(attributes goes here)
and do your other stuff
end
Related
I'm currently building an app wich is working with Devise. Some actions allows the users to invite other users by entering their emails. But sometimes the users are not registered yet.
To avoid a multiplication of tables and conditions (between registered and non-registered users), I chose to create empty User (with email, id, and registred(false) variables). This allows me to always use the same user for different invitations, even if the user is not yet registered.
The idea is that when the user registers, it automatically retrieves all the information previously recorded by other users (in this case invitations)
However, Devise email is defined by default as uniq (across method :validatable).
During registration, I need to set the following condition:
if email does not exist
New User
elsif email exist && not registred
Update User with this email
else
message:'email already token'
end
I tried several solutions like :
RegistrationsController#Create :
#previous_account = User.where(email: params[:user][:email]).first
if #previous_account == nil && #previous_account.registred != true
#user = User.new(params[:user])
else
# This update the user with his password and other informations, but keeping the same id (to keep all the previous invitations)
#previous_account.update(params[:user])
end
or
User Model :
before_create :verif_exist
def verif_exist
pre_user = User.where(email: self.email).first
if pre_user != nil && pre_user.registred != true
pre_user.update(encrypted_password: self.encrypted_password, registred: true)
else
self.save
end
end
I don't know how to solve this issue, if anyone has the same specificities in his app, I would love to see your solution.
In our application we have normal users. However, we want to be able to make invitations, to invite certain people. Note that an invitation is directly coupled to a user, as we want to be able to set certain settings for these users already. (We are mitigating clients from our old software to the new).
So:
An admin should be able to create a new user and change its settings.
When someone follows a link with their invitation_token, they should see a form where they can set a password for their account.
What I am having trouble with, is how to enable the admin to create an user account, bypassing the normal password validation. It would be a horrible solution if a default password would need to be set, as this would create a severe security flaw.
How to create a new User in Devise without providing a password?
There are at least two ways to do what you want:
Method 1:
Overload Devise's password_required? method
class User < ActiveRecord::Base
attr_accessor :skip_password_validation # virtual attribute to skip password validation while saving
protected
def password_required?
return false if skip_password_validation
super
end
end
Usage:
#user.skip_password_validation = true
#user.save
Method 2:
Disable validation with validate: false option:
user.save(validate: false)
This will skip validation of all fields (not only password). In this case you should make sure that all other fields are valid.
...
But I advise you to not create users without password in your particular case. I would create some additional table (for example, invitations) and store all required information including the fields that you want to be assigned to a user after confirmation.
TL;DR:
user.define_singleton_method(:password_required?) { false }
Fiddle:
class MockDeviseUser
protected
def password_required?
true
end
end
class User < MockDeviseUser
def is_password_required?
puts password_required?
end
end
unrequired_password_user = User.new
unrequired_password_user.define_singleton_method(:password_required?) { false }
unrequired_password_user.is_password_required?
regular_user = User.new
regular_user.is_password_required?
#false
#true
You can now use the DeviseInvitable gem for this.
It allows you to do exactly what you're asking.
If you need to completely overwrite the password requirement just define the following on your model:
def password_required?
false
end
I want my users to be able to update their email addresses, if they authenticate themselves.
My code looks something like this...
#user = User.find_by_email(params[:user][:old_email].downcase)
if #user
if #user.authenticate(params[:user][:password])
#user.email = params[:user][:email]
if #user.update_attributes(email: params[:user][:email])
Something along those lines. Basically the params are the old_email, the email which the user wants to change to, and their password. We pull the record that matches their old_email (current email). Then we verify the password matches. If so, we want to change the email and the email only, IF of course the email meets the criteria in our model for the :email column.
The problem I'm having is, when I get to #user.update_attributes(em... I get the following error
"Password is too short (minimum is 6 characters)"
I don't want it to verify on password, password should not even be involved in the update whatsoever. I'm only interested in the changing the email here. I want validation only on the email field, and none of the others, and I only want to update the email field. How can I achieve this?
You can update specific column by #update_columns. It updates the attributes directly in the database issuing an UPDATE SQL statement and sets them in the receiver.
It will skip validations and callbacks.
#user.update_columns(email: params[:user][:email])
Remember to sanitise the email address before updating.
Validation belongs in the model but as you need to validate in controller, define a private action:
private
def valid_email?(email)
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
email.present? && (email =~ VALID_EMAIL_REGEX)
end
Then your condition check would be:
if #user.authenticate(params[:user][:password]) && valid_email?(params[:user][:email])
Assuming that an existing user(ID:1367) can send this url to his friends as an invitation.
http://example.com/users/sign_up?invitation=1367
Then Users table has hidden column called invitation.
and This is procedure how it works that I want.
His friend input information to sign up.
He hits submit button then hidden field 'invitation' will be also sent to form.
1367 will be set in the column called 'invitation' of the record.
He will receive confirmation mail, and when he clicks on the link, I'd like to add this transaction, and execute only once for his account.
Of course, this shouldn't be executed when the existing user tried to re-activate.
Only for the first confirmation for the new user.
code
#user = User.find_by_invitation(current_user.invitation)
#user.friends = #user.friends + 1
#user.save
I already have registration controller that helps adding extra transaction to Devise.
Now I want to know how I can implement this procedure to my app.
Should it be something like this?
registrations_controller.rb
def after_????????
if #user = User.find_by_invitation(current_user.invitation)
#user.friends = #user.friends + 1
#user.save
end
end
You can do this on the user model. Use a callback called after_create which is triggered after a user is created.
# user.rb
after_create :handle_invitation_code
private
def handle_invitation_code
# do something with invitation here
# i'm assuming that you want to credit
# whoever it is that invited this user
# assuming that invitation contains the
# id of the referrer
if referrer = User.find_by_id(invitation)
# do something with referrer
end
end
Be warned that if you return false on a callback, it will cause a rollback and the record won't be saved.
UPDATE: callback for after confirmation
instead of using after_create, use before_save with an if option
before_save :handle_invitation_code, if: :just_confirmed?
def just_confirmed?
confirmed_at_changed? && confirmed_at_was.nil?
end
def handle_invitation_code
...
end
I have an app which connects to an iphone app, which in turn authenticates it's users via http_digest.
I'm using authlogic, and in my schema users of the website are "users" and users of the phone app are "people". So, i have user_sessions and people_sessions. To handle the http_digest auth, i'm using the authenticate_or_request_with_http_digest method like this:
def digest_authenticate_person
authenticate_or_request_with_http_digest do |email, password|
#ldb is just a logging method i have
ldb "email = #{email.inspect}, password = #{password.inspect}"
person = Person.find_by_email(email)
if person
ldb "Authentication successful: Got person with id #{person.id}"
#current_person_session = PersonSession.create(person)
else
ldb "Authentication failed"
#current_person_session = nil
end
return #current_person_session
end
end
I can see in the logs that password is nil: only email is passed through to the inside of the authenticate_or_request_with_http_digest block.
Im testing this with a curl call like so:
curl --digest --user fakename#madeup.xyz:apass "http://localhost:3000/reports.xml"
I'd expect "fakename#madeup.xyz" and "apass" to get passed through to the inside of the block. Once i have the password then i can use a combination of email and password to find (or not) a user, in the normal way. Does anyone know how i can get access to the password as well?
grateful for any advice - max
EDIT - on further googling, i think i'm using this method wrong: i'm supposed to just return the password, or the crypted password. But then how do i compare that against the password passed as part of the http_digest username?
Found the answer: i had a fundamental misunderstanding of how authenticate_or_request_with_http_digest works: after reading the documentation (in the source code of the gem) i realised that the purpose of this method is not to do the authentication, its purpose is to provide the "email:realm:password" string to the browser, let the browser encrypt it, and check the result against it's own calculated (or cached) version of this.
Here's how i set it up:
def current_person
if #current_person
#current_person
else
load_current_person
end
end
#use in before_filter for methods that require an authenticated person (mobile app user)
def require_person
unless current_person
redirect_to root_path
end
end
def load_current_person
#check user agent to see if we're getting the request from the mobile app
if request.env['HTTP_USER_AGENT'] =~ /MobileAppName/
result = digest_authenticate_person
if result == 401
return 401
elsif result == true
#make authlogic session for person
#current_person_session = PersonSession.new(#person_from_digest_auth)
#current_person = #person_from_digest_auth
end
end
end
#this method returns either true or 401
def digest_authenticate_person
authenticate_or_request_with_http_digest(Person::DIGEST_REALM) do |email|
person = Person.find_by_email(email)
#result = nil
if person
#need to send back ha1_password for digest_auth, but also hang on to the person in case we *do* auth them successfully
#person_from_digest_auth = person
#result = person.ha1_password
else
#person_from_digest_auth = nil
#result = false
end
#result
end
end