Devise: Authenticate with Just Username Only Instead of Email - ruby-on-rails

everyone!
So I'm building an application that only needs a username for authentication and not an email.
In my config/initializers/devise.rb, I changed the :email to :username resulting to this
config.authentication_keys = [:username]
config.case_insensitive_keys = [:username]
config.strip_whitespace_keys = [:username]
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :stores
has_many :customers, through: :stores
has_many :visits, through: :stores
has_many :mall_managers
validates :name, presence: true
validates :username, presence: true, uniqueness: true
end
I also edited the views accordingly.
When I try to sign up I get an error:
NoMethodError in Devise::RegistrationsController#create
undefined method `email' for #<User:0x000000011bd61b38>

i guess your user table does not contain email column, but the module validatable of gem devise will validates_presence_of the email, so that error will be raised.
however module validatable allow you ignore email validation by override email_required? method
class User < ApplicationRecord
# ...
def email_required?
false
end
def will_save_change_to_email?
false
end
def email_changed?
false
end
end
update i found more 2 methods need to override (as above), and who know what else (in future versions) ? so i recommend you add email database field (if the root cause i suspect (miss email) correct).

Related

Skipping Validation for custom fields added to Devise user model

I've tried to research this but oddly no one else seems to have this problem or I am not searching right. Anyway here is my user.rb. I can create a user no problem and my custom field, 'pin, gets set. I can update the 'pin' field. But if try to change name, email or password they fail because the pin is blank per the validation in my model. How to skip the pin validation if I am not updating it? Also, if I update my email, it still sends a confirmation but when I click on the link it validates the pin again and the confirmation fails. Also, I don't want to regenerate the hash as well in the after_validation helper.
class User < ApplicationRecord
has_many :profiles, dependent: :destroy
has_many :general_settings, dependent: :destroy
has_many :profile_settings, through: :profiles
has_many :tags, dependent: :destroy
has_many :videos, through: :profiles
belongs_to :account_type
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable
after_validation :create_parent_digest
validates :pin, presence: true, length:{ is: 4 }, confirmation: true
attr_accessor :pin, :pin_confirmation
def valid_pin?(pin)
Devise::Encryptor.compare(self.class, parent_digest, pin)
end
private
def create_parent_digest
self.parent_digest = Devise::Encryptor.digest(self.class, pin)
end
end
According to the Rails Guide, you can skip validations on save by adding `validate: false as on option. For example:
#user.save(validate: false)
You should use option on: http://guides.rubyonrails.org/active_record_validations.html#on
For example:
validates :pin, presence: true, length:{ is: 4 }, confirmation: true, on: :create
or you can create a custom context for validation
# in model
validates :pin, presence: true, length:{ is: 4 }, confirmation: true, on: :validate_pin
# in controller
#user.save(context: :validate_pin)
I recommend to do not use callbacks for creating something, just make create_parent_digest a public method and call it by hand in controller(or service object)
# UsersController
if #user.save
#user.create_parent_digest
...
end
Edit: you can do like this
# in model
validates :pin, presence: true, length:{ is: 4 }, confirmation: true, on: :validate_pin
# in controller
def create
#user = User.new params
if #user.valid?(:validate_pin)
#user.create_parent_digest
#user.save
else
# do something like render :new
end
end

devise invitable do not validate model

First, excuse my poor english, I'm french... it's tricky to explain my problem !
I have a model User model in a Rails application:
class User < ActiveRecord::Base
attr_accessible :email, :gender, :lastname, :firstname
end
And a BackUser model that inherit from User:
class BackUser < User
# Class for Backoffice User
devise :database_authenticatable,
:rememberable,
:trackable,
:lockable,
:invitable,
:confirmable,
:validatable,
:validate_on_invite => true
attr_accessible :password, :password_confirmation, :remember_me, :active, :role
validates :role, presence: true,
inclusion: ["admin", "normal"]
validates :gender, presence: true
validates :firstname, presence: true
validates :lastname, presence: true
def admin?
self.role == 'admin'
end
end
This second class should validate the record before invite!
BUT, when I use the console to do the following:
u = BackUser.new
u.invite!
"u" is saved in database and an invitation is send to a blank email...
Do you know what I have to do?
Thans a lot!
I'm sure you've found a solution or workaround to your problem by now, but for any future SO users who encounter the same problem I found a pretty simple fix.
Devise Invitable's model configuration docs don't fully explain how to implement :validate_on_invite, but you have to set the configuration option to true - :validate_on_invite => true.
Here's what my devise method looks like in my User model for this to work correctly.
models/user.rb
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :validate_on_invite => true
Now when I attempt to submit an invitation it is fully validating the record with what the validations I've set up in my User model before allowing the invitation to be sent and the user record to be created. Looking at the docs, I'm guessing you can also enable this setting in the devise initializer, but I haven't tried going that route.
*Second possible option to enable validation if needed
config/initializers/devise.rb
config.validate_on_invite = true
I've never been able to get the validation to work correctly for devise invitable. You can't use RobHeaton's suggestion either because you will receive a validation error on the password. I use this little hack to get validation to work:
def create
#user = User.new(user_params)
#user.valid?
#user.errors.messages.except!(:password) #remove password from errors
if (#user.errors.any?)
render 'new'
else
#user.invite!(current_user)
redirect_to user_path(#user)
end
end
It doesn't solve the mystery of why your behaviour is occurring, but:
if u.save
u.invite!
end
will give the end result you are after.

Don't receive callback with devise_invitable

I'm using devise_invitable to allow users to invite each other. I want to set values for the user when the invite is created or when it's accepted. One approach is here
Using devise_invitable for adding Users to a Group in Ruby on Rails?
but this seems overkill. The callbacks look like a perfect solution, but they don't seem to fire during my Rspec tests.
Here is the user model:
class User < ActiveRecord::Base
belongs_to :company
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :role_ids, :as => :admin
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :company
validates :company, :presence => true
after_invitation_accepted :email_invited_by
before_invitation_accepted :email_invited_by
private
def email_invited_by
# This is NEVER executed during tests, even when an invite is successfully accepted
puts "Callback worked"
end
end
Any clues about where to look would be appreciated. I find the devise_invitable documentation a bit opaque.
Thanks!
For those still looking for an answer here is what worked for me.
The issue is not with validations since in devise_invitable there is a config to validate on invite and it defaults to false:
# Flag that force a record to be valid before being actually invited
# Default: false
# config.validate_on_invite = true
So my solution is to use the callback provided by devise_invitable:
after_invitation_accepted :create_profile
Do note that this needs to be below devise :invitable
devise_invitable callbacks will be fired if the user has been really invited (i.e. the object is persisted and there's an invitation token set) and the object (user's object) has no validation errors. That said, I see that you're validating presence of company without any conditions, which, I think, could be causing validation errors.
If that's the case, you can add a condition like this
validates :company, :presence => true, :if => Proc.new { self.invitation_token.nil? }
so it won't cause validation errors and fire the callbacks.

retrieve object id from relation in rails3

i have a model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :meetings, :dependent => :destroy do
def find_foreign
Meeting.where("user_id <> ?", id)
end
end
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
# attr_accessible :title, :body
end
and when i am trying to get user's foreign meetings like that
some_user.meetings.find_foreign
i get an error
NoMethodError (undefined method `id' for []:ActiveRecord::Relation):
because self in find_foreign is an Array. How to retrive the User.id from this method ?
you can access self here:
def find_foreign
Meeting.where("user_id <> ?", self.id)
end
But don't know why you've written this?
some_user.meetings will already filter meetings by current user id. I've even no idea if block is allowed here!
find_foreign method is in User Model and you are trying to call it on Array of the Object of the Meetings
Just use
some_user.find_foreign

alias_attribute and devise causing stack level too deep error

I am running into some slightly tricky issues with a legacy db. Everything seems to work fine if I simply change the "password" column name in the db to "encrypted_password"; however, I need to leave the db in place.
So I decided to use
alias_attribute :encrypted_password, :password
Now I get a "stack level too deep" error in the console.
My user model:
class User < ActiveRecord::Base
require "digest/sha1"
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :encryptable, :encryptor => :old_cakephp_auth
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :events
before_create :add_default_values
#alias_attribute :created_at, :created
#alias_attribute :updated_at, :updated
alias_attribute :encrypted_password, :password
def add_default_values
self.created = Time.now
self.updated = Time.now
self.image = ""
self.name = self.email.split("#").first
#make normal user
self.role_id = 2
self.username = self.email.split("#").first + rand(100000000000000).to_s
self.website = ""
end
def valid_password?(password)
return false if encrypted_password.blank?
Devise.secure_compare(Digest::SHA1.hexdigest(self.password_salt+password), self.encrypted_password)
end
end
Ideas? Thanks!!! :)
I imagine that this is due to devise reserving the word password for their own use (and it in turn calling encrypted_password. Try renaming it to pword and see if the error still occurs. If it doesn't, you'll have to find another name to call the aliased password.
I should say that this is just an assumption. Let me know if it helps.

Resources