Different devise models using same email id to register - ruby-on-rails

I have created two different devise models and it's working fine . But the issue i am facing now is , that both the users are able to register with the same email id . I am looking to an easy fix for it but haven't been able to find one . Any suggestions on the same would be much welcome .
VIEW CODE
<li>
<label>Email Address</label>
<%= f.email_field :email,:class => 'wh-txt-box' , :validate => { :presence => true }, :placeholder => 'Email address ' %>
</li>

This is a simple fix i suggest
class Usera
validate :unique_email
private
def unique_email
errors.add(:email, 'This Email is taken') unless Userb.where(email: self.email).blank?
end
end
class Userb
validate :unique_email
private
def unique_email
errors.add(:email, 'This Email is taken') unless Usera.where(email: self.email).blank?
end
end

You can create a custom Validation method for checking the Uniqueness of Email.
validate :unique_email
private
def unique_email
user = User.where(email: email)
# user.exists? insure you that this email is already present in
# your system. Now you need to validate that If it is a new record (new_record?)
# then show error or at the time of update (read_attribute(:email) != self.email)
# it will also validate that you are entering a unique email.
if user.exists?
if new_record? || read_attribute(:email) != self.email
errors.add(:user, 'This email has already been taken.')
end
end
end

Related

Rails 4, Rolify and Assigning Roles

I am trying to make an app with Rails 4. I use, Devise, Rolify and Simple Form.
My current problem is in trying to assign rolify roles to users.
I have the following code:
User.rb
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# p '11111'
# Create the user if needed
if user.nil?
# p 22222
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
# p 33333
user = User.new(
# at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: email,
#username: auth.info.nickname || auth.uid,
password: Devise.friendly_token[0,20])
#
# debugger
# if email_is_verified
# user.skip_confirmation!
# end
user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && TEMP_EMAIL_REGEX =~ self.email
end
def full_name
[*first_name.capitalize, last_name.capitalize].join(" ")
end
after_create :add_default_role
def add_default_role
add_role(:pending) if self.roles.blank?
end
Role.rb
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
validates :resource_type,
:inclusion => { :in => Rolify.resource_types },
:allow_nil => true
scopify
end
Users/omniauth_callbacks_controller
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
#user = User.find_for_oauth(env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
# sign_in_and_redirect_user(:user, event: :authentication)
[:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
provides_callback_for provider
end
def after_sign_in_path_for(resource)
if resource.email_verified?
super resource
else
finish_signup_path(resource)
end
end
Users controller
private
def set_user
#user = User.find(params[:id])
end
def user_params
accessible = [ :first_name, :last_name, :email, {role_ids: []}] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
accessible << [:approved] if user.admin
params.require(:user).permit(accessible)
end
Users#index
<% Users.each do |user| %>
<div class="row">
<div class="col-xs-5">
<%= link_to "#{user.full_name}", user %>
</div>
<div class="col-xs-5">
<%= link_to "#{user.email}", user %>
</div>
<div class="col-xs-2">
<%= link_to "edit", edit_user_path(user) %>
</div>
</div>
<% end %>
Users#form
<%= simple_form_for(#user) do |f| %>
<%= f.error_notification %>
<% Role.all.each do |role| %>
<div class="form-inputs">
<%= f.input "user[role_ids][]", role.id, collection: #user.role_ids.include?(role.id) %>
<%= role.name %>
</div>
<div class="form-actions">
<%= f.button :submit, "Add role", :class => 'formsubmit' %>
</div>
I have also tried:
<%= f.association :roles %>
<%= role.name %>
in the user#form
Migration to add roles to role table:
class AddRolesToRolifyTable < ActiveRecord::Migration
def change
['admin', # internal admin
'manager', # internal manager
'editor', # internal web content editor
'support', # internal user support
'educator', # lecturers
'faculty_manager', #manage the business side
'project_manager',
'pending', # new people that are not yet confirmed in a role - default role assignment
].each do |role_name|
Role.create! name: role_name
end
end
end
When I save this and try to run the local host and go to users#index, I get an error that says:
Couldn't find User with 'id'=
This method is highlighted:
private
def set_user
#user = User.find(params[:id])
end
I can't say I've properly understood how rolify works with devise. My console shows that I have two test users in the db, each of which has an id (so im not sure how to explore this error further). Does anyone see where I've gone wrong?
I have adapted this setup using advice in this post:
Defining Roles with Rolify
Rolify and Devise
I can't say I've properly understood how rolify works with devise.
Rolify and Devise do very different jobs. There is no actual integration between the two.
Devise is an authentication solution, authentication is only about determining if there is a signed in user and if that user is who he/she claims to be.
Authorization on the other hand is about who is allowed to do what in a system. Rolify is a generic roles library which is meant to be used as a smaller part of an authentication solution. Just defining your authorization with .has_role? is going to get very messy quick.
It is most often combined with something like CanCanCan or Pundit which provide facilities to define authentication rules.
https://github.com/CanCanCommunity/cancancan/wiki/Role-Based-Authorization
Collection helpers
Rails has built in helpers for creating selects and checkboxes for associations and simple_form takes it a bit further.
So lets say you have a users/:user_id/edit route where admins edit a users profile and add roles:
<%= simple_form_for(#user) do |f| %>
<fieldset>
<legend>Roles</legend>
<%= f.association :roles, as: :check_boxes, collection: Role.all %>
</fieldset>
<% f.submit %>
<% end %>
Note that you don't need a special form for editing the roles in this case - this will work both when creating and editing users.
Your params sanitation is bit off:
def user_params
accessible = [ :first_name, :last_name, :email, role_ids: []]
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
accessible << [:approved] if user.admin? # don't do this. There are better ways.
params.require(:user).permit(accessible)
end
Also:
Migrations are for altering the database schema, not for seeding the system with default info. Use rake db:seeds or create your own rake task instead.
I would also question if you really need a pending role and the approved param. I would simply check if the user has no roles - which defacto means that he is pending. When a admin or peer verifies a user you would add the approved role.

Access current_user in model (or some other workaround)

I've read several SO links on this topic. Even if you can hack it to get current_user in model, you shouldn't do it. So, what are my options in my case?
I'm using the devise_invitable gem, and one of the commands is User.invite!({:email => email}, current_user), which stores who the user is invited by (current_user). I'd like to have this information.
Currently, users are invited to join a private group, and this process is handled in my group.rb model:
# group.rb
def user_emails
end
def user_emails=(emails_string)
emails_string = emails_string.split(%r{,\s*})
emails_string.each do |email|
user = User.find_for_authentication(email: email)
if user
self.add user
GroupMailer.welcome_email(user)
else
User.invite!(email: email) # But I want this: User.invite!({:email => email}, current_user)
user = User.order('created_at ASC').last
self.add user
end
end
end
If relevant, it's just a text_area that receives these emails to process:
# groups/_form.html.erb
<%= f.text_area :user_emails, rows: 4, placeholder: 'Enter email addresses here, separated by comma', class: 'form-control' %>
Without having to re-arrange too much, how can I run User.invite!({:email => email}, current_user) in this process, so that this useful information (who is invited by whom) is stored in my database? Much thanks!
Update:
With #Mohamad's help below, I got it working.
# group.rb
def emails
end
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.group_invite user
else
add User.invite!({:email => email}, inviter)
end
end
end
# groups_controller.rb
def update
#group = Group.friendly.find(params[:id])
if #group.update_attributes(group_params)
emails = params[:group][:emails]
#group.invite_many(emails, current_user) # also put this in #create
redirect_to #group
else
flash[:error] = "Error saving group. Please try again."
render :edit
end
end
And then nothing in my User model because User.invite is defined already by devise_invitable and I didn't need to do anything else. This process is working great now!
There are some subtle issues with your code. There's a potential race condition on the else branch of your code where you try to add the last created user. I'm also unsure that you need a setter method here unless you are access emails from elsewhere in the instance of Group.
As suggested by others, pass the current user as an argument form the controller. I'm not sure how invite! is implemented, but assuming it returns a user, you can refactor your code considerably.
I would do somethng like this:
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.welcome_email user
else
add User.invite!(email, inviter)
end
end
end
# controller
#group.invite_many(emails, current_user)
# User.invite
def invite(email, inviter)
# create and return the user here, and what else is necessary
end
If you are calling user_emails() from the controller (and I'm guessing you are as that must be where you are receiving the form to pass in emails_string), you can pass in the current_user:
user_emails(emails_string, current_user)
and change user_emails to receive it:
def user_emails=(emails_string, current_user)
You can store the current_user with global scope ,like #current_user,which can be assigned in sessions controller,so in model you will just #current_user as the current user of the app.

rails autocomplete how to use exising record

I'm new to rails , and I have a problem with the nested forms and all of that.
I have a User model, and an Organization model.
When I want to create a user, I want to specify from which organization does he comes from.
Either the organization name is already in the database or if it's not, I want to create a new record and associate that record the User model.
I have hard time understanding all the relations (many-to-many etc) implications in the rails framework, but so far I've got this.
model/organization.rb
class Organization < ActiveRecord::Base
has_many :user
validates_presence_of :name
end
model/user.rb (short)
class User < ActiveRecord::Base
belongs_to :organization
accepts_nested_attributes_for :organization
#####
end
From this, in the console, I can create user and specify and organization name , and it will create a new record for the user and a new record for the organization.
The problem is that it creates a new organization each time.
I want to be able to associate an already existing organization to a new user.
I can get the list of organization with things like typeahead.js for the form, so the name will be the same when the user selects one. But I don't know how to relate the two (the newly created user and already existing organization).
I thought of putting a hidden field with the id of the organization, and check in the controller if this id exists. If it does, put this id, if it doesn't create a new one. But I don't even know how to do this. In the console, when I update the attributes of a user, for example , with an organization_id = 3 which exists :
u.update_attributes( :organization_attributes => { id: 3 } )
It rejects saying he didn't find a user with ID=... with Organization.id = 3 ...
I don't understand.
I suppose since this is a common case, that this should be easy , but it's messing with my head.
If someone is willing to explain to me, I'd be very grateful.
Thank you.
EDIT
i've just tried something in my controller but that doesn't work either.
def create
#user = User.new(user_params) # :user object built from user inputform
org = Organization.find_by(name:user_params[:organization_attributes][:name])
if org
#user.organization.id = org.id
end
if #user.save
# signin the user (token etc)
sign_in #user
flash[:success] = "Registration sucessfull !"
redirect_to #user
else
render 'new'
end
end
+user_controller (strong params)
def user_params
params.require(:user).permit(:lname,:email,:fname,:password,:password_confirmation,
:gender,:role,:display_private,:link_li,:country,:city,:phone,:hobbies,
:avatar,:org_name, :organization_attributes => [ :id, :name])
end
+form.html.erb
<%= u.fields_for :organization do |o| %>
<%= o.label "Organization" %>
<!-- PUT ORGA -->
<%= o.text_field :name, class:"form-control" %>
<% end %>
I would write a custom method for this:
#in User
def organization_name
(org = self.organization) && org.name
end
def organization_name=(name)
if org = Organization.find_by_name(name)
self.organization = org
else
self.organization = Organization.create(:name => name)
end
end
Now, because you've got a getter and setter method (ie two methods with the same name, apart from the = sign), you can treat organization_name like an attribute of User and put it in a form field like
f.input :organization_name
The input will get the current value from #user.organization_name and will call #user.organization_name= with the new value.
First take away the accepts_nested_attributes from the model.
Then in your controller you should do something like:
def create
#user = User.new(user_params) # :user object built from user inputform
org = Organization.where(name: user_params[:organization_attributes][:name]).first || Organization.create(name: user_params[:organization_attributes][:name])
#user.organization = org
if #user.save
# signin the user (token etc)
sign_in #user
flash[:success] = "Registration sucessfull !"
redirect_to #user
else
render 'new'
end
end
In your app/model/user.rb
def self.create(name, attribute1, ... ,organization)
user = User.new(:name => name, :atr_1 => attribute_1, ....:atr_n => attribute_n)
user.organization = organization
raise "user not created" if !user.save
user
end
In users_controller.rb
def create
org = Organization.find params['organization'] #expecting the param to be Organization#id
user = User.create(params['name'], ..., org)
render :json => {:message => "user created"}
end

Ruby on Rails: How to update a record's attributes except one?

In my Rails app I have an update action that users can use to update their profile.
The tricky thing I want to achieve is that if a user enters a new email address and saves it, that email address won't get saved to the email database field straightaway, but rather to a database field called new_email. The field email should remain untouched (at least until the user has confirmed that email address later on).
def update
current_email = #user.email
new_email = params[:user][:email].downcase.to_s
if #user.update_attributes(params[:user])
if new_email != current_email
#user.change_email(current_email, new_email)
flash[:success] = "Profile updated. Please confirm your new email by clicking on the link that we've sent you."
else
flash[:success] = "Profile updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
User model:
def change_email(old_email, new_email)
self.new_email = new_email.downcase
self.email = old_email
self.send_email_confirmation_link
end
The function above kind of works but is hard to test and doesn't feel right. Is there a smoother way to achieve this?
Thanks for any help.
If you change your form so that you're updating new_email, you can just put it all in a simple after_update hook.
after_update :check_new_email
private
def check_new_email
send_email_confirmation_link if new_email_changed?
end
I think you could use "virtual" attribute, called - let's say - email_input and show field for this attribute (instead of email) in view:
<%= f.text_field :email_input %>
Then in your model you should have:
class User < ActiveRecord::Base
attr_accessor :email_input
attr_accessible :email_input
before_save :set_email, :if => lambda{|p| p.email_input.present?}
# ...
def set_email
email_input.downcase!
if new_record?
self.email = email_input
else
self.new_email = email_input
send_email_confirmation_link
end
end
end

Devise Invitable : Optionally Send Email

in devise invitable, you can invite a new user by performing:
User.invite!(:email => "new_user#example.com", :name => "John Doe")
What I would like to do is (sometimes) prevent devise invitable from sending out an email. I found the following code in the library:
def invite!
if new_record? || invited?
self.skip_confirmation! if self.new_record? && self.respond_to?(:skip_confirmation!)
generate_invitation_token if self.invitation_token.nil?
self.invitation_sent_at = Time.now.utc
save(:validate => false)
::Devise.mailer.invitation_instructions(self).deliver
end
end
Any ideas on how to best update that to not send out the email on the last line? I'm not familiar with the ::
thanks
you can use:
User.invite!(:email => "new_user#example.com", :name => "John Doe") do |u|
u.skip_invitation = true
end
or
User.invite!(:email => "new_user#example.com", :name => "John Doe", :skip_invitation => true)
this will skip invitation email.
In your invitations_controller (there should already be one that inherits from Devise::InvitationsController), you can add the following
# this is called when creating invitation
# should return an instance of resource class
def invite_resource
if new_record? || invited?
self.skip_confirmation! if self.new_record? && self.respond_to?(:skip_confirmation!)
super
end
end
This will override Devise's method for inviting, and then call the original Devise method (super) only if the condition is met. Devise should then handle the token generation and send the invite. You may also want to setup what the app does if the condition is false, in my case that looks like this:
def invite_resource
if user_params[:is_free] == "true"
super
else
# skip sending emails on invite
super { |user| user.skip_invitation = true }
end
end
when params[:is_free] is set to ''true'' the invitation is sent, otherwise the resource is created, but no invitation is sent.
After some digging I found this solution here: https://github-wiki-see.page/m/thuy-econsys/rails_app/wiki/customize-DeviseInvitable

Resources