Rails - adding attributes on a user before it's created - ruby-on-rails

When a user registers with twitter I'm trying to add his name, location, etc. to his user record. I think I want to do something like user.build
Here is the controller. This is what happens:
user = User.new
user.apply_omniauth(omni)
if user.save
flash[:notice] = "Logged In!"
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omni.except('extra')
redirect_to new_user_registration_path
end
When a user doesn't exist with twitter, the user is redirected to the registration path where they finish registering. I want to add the extra stuff from twitter to their yet to be saved user account. I can't do it in the user.apply_omniauth(omni) method because that saves to the authentications table.
Any ideas?
Thanks!

You can create a flag in apply_omniauth method to make a decision to save or not.
app/models/user.rb
# def apply_omniauth(omniauth) => def apply_omniauth(omniauth, save_it)
# apply_omniauth with save it flag
def apply_omniauth(omniauth, save_it = false)
case omniauth['provider']
when 'facebook'
self.apply_facebook(omniauth)
end
self.email = omniauth['user_info']['email']
if email.blank ? build_authentications(omniauth, save_it)
end
#build authentications
def build_authentications(omniauth, save_it = false)
auth_params = {: provider = > omniauth['provider'],
: uid = > omniauth['uid'],
: token = > (omniauth['credentials']['token'] rescue nil)
}
if save_it authentications.create!(auth_params)
else authentications.build(auth_params)
end
end
#force to save
def apply_omniauth!(omniauth)
apply_omniauth(omniauth, true)
end

Related

Rails - sending invitations to users that already exist

I have an invite method that sends users emails to be part of a team. I have a modal that displays some users that already have an account and a textfield to input more emails and sends an invite for the person to join the platform.
But if I type an email that already exists in the database I get:
Validation failed: Email has already been taken
But want to send an email even if the person already has an account.
Here's what I have:
def invite
invite = params.dig(:invite) || {}
candidate_ids = invite.dig(:candidate_ids) || []
extra_emails = invite.dig(:extra_emails) || ""
emails = extra_emails
.split(/,|;/)
.map(&:strip)
new_users = emails.map { |email| team_email_invite(email) }
candidate_ids.concat new_users.map(&:id)
invite_data = candidate_ids.map { |uid| {
:user_id => uid,
:team_id => #team.id,
} }
TeamInvite.create(invite_data)
.map!(&:email)
respond_to do |format|
format.html { redirect_to overviews_url, notice: 'Invites sent.' }
format.json { head :no_content }
end
end
def team_email_invite(email)
user = User.new({
:email => email,
:password => SecureRandom.hex,
:role => :shadow,
})
user.add_role :recruiter
user.skip_confirmation_notification!
user.save!
end
candidate_ids - It's the users that I display on the list(all good)
extra_emails - emails in the textfield
So when I write a user that already has an account in the textfield, team_email_invite tries to create a new user and crashes.
I don't want to do something like
begin
new_users = emails.map { |email| team_email_invite(email) }
rescue
new_users=[]
end
because then it doesn't send the invite.
Any idea how to solve this?
You could use first_or_initialize. The block only gets run if the User does not already exist. Here's an example...
user = User.where(email: email).first_or_initialize do |usr|
usr.email = email
usr.password = SecureRandom.hex
usr.role = :shadow
usr.skip_confirmation_notification!
end
user.add_role :recruiter
user.save!

How to find user by email and if not, redirect him to register page

I use Omniauth and Omniauth-facebook gems and I want to click on button, if user is found, then sign him in. If not, redirect him to register page.
So far I use just classic login/register:
user.rb:
def self.from_omniauth(auth, role)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.info.email).first
if registered_user
registered_user.provider = auth.provider
return registered_user
else
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
if (role == "1")
user.add_role :sportsman
elsif (role == "2")
user.add_role :donor
end
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end
end
end
omniauth_callbacks_controller.rb:
def facebook
role = cookies[:role]
# signin = cookies[:signin]
user = User.from_omniauth(request.env["omniauth.auth"], role)
if user.persisted?
flash.notice = "Signed by Facebooku"
sign_in_and_redirect user
else
flash.notice = "Error, try again."
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
This code works, but if user is not registered, it will register him. But I sign roles when user register himself.
Thanks for help.
Because you are already saving the user attributes into the session with
session["devise.user_attributes"] = user.attributes
Then the following should already work:
def self.from_omniauth(auth, role)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
# NOTE: because you're searching "email" and not both "email" + "provider", then I assume
# that you want users to have only one shared account between all providers (i.e. if they also sign-in in Google with the exact email as his email in Facebook)
registered_user = User.where(:email => auth.info.email).first
if registered_user
# NOTE: this line doesn't do anything because registered_user is not being "saved"
registered_user.provider = auth.provider
return registered_user
else
# build a new User object (don't save yet!)
return User.new.tap do |u|
u.provider = auth.provider
u.email = auth.info.email
u.uid = uid: auth.uid
u.password = Devise.friendly_token[0,20]
# because I assume you're using "rolify" gem, don't add the roles yet here, because I don't think you can store the "roles" into the session
# add the roles logic in whatever controller you have for user registration
end
end
end
end
Then override new_with_session in your user model. new_with_session is automatically called by Devise in registrations#new. We need to set the
user attributes that we previously stored in session inside omniauth_callbacks#facebook
class User < ApplicationRecord
def self.new_with_session(params, session)
super.tap do |user|
if user_attributes = session['devise.user_attributes']
user.assign(user_attributes)
end
end
end
end
So I used simple params to get where the user come from:
<script type="text/javascript">
document.cookie = "login=0"
</script>
This code is in views/devise/registrations/new.html.erb (and in views/devise/sessions/new.html.erb) and it is a JavaScript. It tells me if user went from login page (sessions folder - login=1) or from registration page (registrations folder - login=0). Then I use this code to determinate if user is from login page AND if he is not registered yet. If both conditions are true, then he is redirected to register page. It is so simple that I am embarrassed...
def facebook
hash = request.env["omniauth.auth"]
info = hash.info
email = info["email"]
user = User.find_by_email(email)
login = cookies[:login]
if (user == nil && login == "1")
redirect_to new_user_registration_path
else
role = cookies[:role]
user = User.from_omniauth(request.env["omniauth.auth"], role)
if user.persisted?
flash.notice = "Logged in by Facebook"
sign_in_and_redirect user
else
flash.notice = "Error, try again."
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
end

Rails update one to one and one to many - create child object if not exist

I have three models: Employee, User, and Role. Relationship between these classes are employee --- one_to_one -- user and user -- one_to_many -- role.
My create action is working fine with following strong params methods
def employee_params
params.require(:employee).permit(:first_name, :middle_name, :last_name, :email, user_attributes: [:id, role_ids:[]])
end
For update if employee record has no user object, I am instantiating new user, mapping it to employee and calling update by passing string params. However, the update is failing with message:
Failed to save the new associated user
My update method code is
def update
#employee = Employee.find(params[:id])
if params[:employee][:user_attributes] != nil && params[:employee][:user_attributes][:role_ids] != nil && ! params[:employee][:user_attributes][:role_ids].empty?
if #employee.user == nil
#employee.user = User.new
temp_password = Devise.friendly_token.first(8)
#employee.user.is_temp_password = true
#employee.user.password = Devise.friendly_token.first(8)
#employee.user.email = #employee.email
#employee.user.email = params[:employee][:email] if params[:employee][:email]
end
end
respond_to do |format|
if #employee.update(employee_params)
format.json { render json: #employee.as_json}
else
format.json {render :json => #employee.errors.as_json, :status => 422}
end
end
end
I think as the above users suggested, you need to save the new User Object but also I think you should have the User creation code inside of the Employee create since you you would need to auto create it anyways in the update
Not sure if you also aware of helpers blank?, present? but I rewrote your code with that
def update
#employee = Employee.find(params[:id])
if params[:employee][:user_attributes].present? && params[:employee][:user_attributes][:role_ids].present? && params[:employee][:user_attributes][:role_ids].present?
unless #employee.user
user = User.new
#employee.user = user
temp_password = Devise.friendly_token.first(8)
user.is_temp_password = true
user.password = Devise.friendly_token.first(8)
user.email = #employee.email
user.email = params[:employee][:email] if params[:employee][:email]
user.save!
end
end

can not persist the new object between requests - RoR

In my application, I have a twitter and facebook login, however I need to prompt a password and email after they first register with twitter or facebook. I am using omniauth gems and my controller/user model looks like this:
//socials_controller.rb
def create
#render text: request.env['omniauth.auth'].to_yaml and return
#user = User.from_omniauth(request.env['omniauth.auth'])
if(#user.email == nil)
redirect_to patient_login_entry_url(#user)
elsif #user.confirmed
log_in #user
redirect_to #user
else
flash[:danger] = "Bir hata olustu."
redirect_to root_url
end
end
def login_entry
#patient = Patient.find(params[:id])
end
def update_social
#patient = Patient.find(params[:id])
if #patient.update_attributes(user_params)
SendVerificationEmailJob.perform_later #patient
flash[:success] = "Aktivasyon için #{#patient.email} adresinizi kontrol ediniz."
redirect_to root_url
else
flash[:danger] = "Bilgilerinizi kontrol edip tekrar deneyiniz."
redirect_to patient_login_entry_url(#patient)
end
end
and my from_omniauth method is:
//user.rb
has_secure_password
class << self
def from_omniauth(auth_hash)
if exists?(uid: auth_hash['uid'])
user = find_by(uid: auth_hash['uid'])
else
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'], type: 'Patient')
user.location = get_social_location_for user.provider, auth_hash['info']['location']
if auth_hash.provider == 'facebook'
user.avatar = User.process_uri(auth_hash['info']['image'])
user.name = auth_hash['extra']['raw_info']['first_name']
user.surname = auth_hash['extra']['raw_info']['last_name']
user.email = auth_hash['extra']['raw_info']['email']
user.gender = auth_hash['extra']['raw_info']['gender']
elsif auth_hash.provider == 'twitter'
user.avatar = auth_hash['info']['image']
user.name = auth_hash['info']['name']
end
user.url = get_social_url_for user.provider, auth_hash['info']['urls']
user.save!
end
user
end
At the login_entry page, I simply prompt the email and password, and POSTing them to the update_social method.
However, as expected, my app throws the error "Password can't be blank", because has_secure_password validates its presence by default. So, I need to persist it between the requests since I can not save it without a password. How can I achieve this?
I tried to store the created object in session by using to_json method, and turning it into a hash between requests, however this time the profile picture I got from twitter/facebook did not persist (I'm using AWS S3 + Paperclip, the URL persists but there is no such image when I check it from the S3 console) so I think that solution was not good.

How to check if the user should be assigned the role admin

The goal of this method is to check when a user signs up, if they are on the list of users that should be admins.
I am new to Ruby and think it is just a syntax error:
def set_role
if self[:email] == ("email#gmail.com" || "sample#gmail.com" || "test#gmail.com")
self[:role] = "admin"
else
self[:role] = "customer"
end
end
This would be a good time to use a case statement:
def set_role
self[:role] = case self[:email]
when "email#gmail.com", "sample#gmail.com", "test#gmail.com"
'admin'
else
'customer'
end
end
It's easy to add new values to the when test.
You want to check if an array of emails includes the current email like this:
def set_role
if ["email#gmail.com", "sample#gmail.com", "test#gmail.com"].include?(self[:email])
self[:role] = "admin"
else
self[:role] = "customer"
end
end
This code could also be improved:
def set_role
admin_emails = ["email#gmail.com", "sample#gmail.com", "test#gmail.com"]
self[:role] = if admin_emails.include?(self[:email])
"admin"
else
"customer"
end
end

Resources