I generated the default devise views with:
rails generate devise:views
Then I added a username field to the views/devise/registrations/new.html.erb form.
Currently, only email and password validation occurs. How do I validate presence and uniqueness of the username field? Do I need to add something to the User model?
I used both the of the tutorials mentioned in the other answers, Railscast #210 and the Devise Wiki. However, so far as I could tell they do not explicitly say how to validate the presence and/or uniqueness of the username field.
If you added username with a simple migration -
rails generate migration addUsernameToUser username:string
Then devise doesn't do anything special with that field, so you need to add checks for validation and uniqueness yourself in the User model.
class User < ActiveRecord::Base
...
validates_presence_of :username
validates_uniqueness_of :username
However, If you look at the RailsCast #209 there is an example of the migration used to create the User model.
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.database_authenticatable :null => false
# t.confirmable
t.recoverable
t.rememberable
t.trackable
# t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
t.timestamps
end
add_index :users, :email, :unique => true
# add_index :users, :confirmation_token, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
def self.down
drop_table :users
end
end
Notice here that the users email is defined as being unique. Perhaps if username was added using this same syntax then devise magic would take care of presence and uniqueness.
Rails 4 and Strong Parameters
On top of the above I had to generate the views with:
$ rails g devise:views
then in devise.rb add:
config.scoped_views = true
and finally configure the permitted parameters as below for sign_up as below:
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit :username, :email, :password, :password_confirmation
end
end
end
This is described in Devise Doc
Also, my validation for username is the following:
validates :username, presence: true
validates :username, uniqueness: true, if: -> { self.username.present? }
I use two lines, so if username is blank I get only one error.
If anybody is wondering how to get the login to check for username if its blank
or to make sure to users cannot have the same username. I spent quite a few
hours trying to figure this out and in the ned I only had to add :
validates_uniqueness_of :username, case_sensitive: false
validates_presence_of :username
to your user.rb file in app/models/
Here are the docs...
https://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
This now throws the errors I need.
I feel like an idiot because I found uniquness_of first , then went back to and spent hours trying to figure out how to check for a blank field then found it is in the same documentation as the other...I am a noob.
Now onto figure out how to change the error messages since they are not in the devise.en.yml
Just add a username field to your User model and on the Devise wiki:
http://github.com/plataformatec/devise/wiki/Sign-in-using-login-or-mail
Hope it helps.
Related
I am testing my user input validation in my application and I am getting two errors in regards to my password presence.
This is what I have written for my model.
class User < ActiveRecord::Base
include Slugifiable
extend Slugifiable::Find
has_secure_password
has_many :posts
validates :email, uniqueness: true, presence: true
validates :username, uniqueness: true, presence: true
validates :password, presence: true
end
Below is my migration table:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :username
t.string :password
t.string :password_digest
end
end
end
Each time I run my application with no input it should give me three error messages: "Password can't be blank", "Email can't be blank", "Username can't be blank". Instead I get an extra "Password can't be blank" error. I am using a password_digest variable which is a salted hash of the users password once the data persists in the database.
has_secure_password comes with its own presence validation on the create action. Therefore, validating the presence of password is redundant and is causing you to get two "Password can't be blank" error messages.
Simply remove validates :password, presence: true or add a condition to the validation for a specific controller action/other context...ie
validates :password, presence: true, on: :some_action
I have a Ruby on Rails JSON API with Knock for JWT Authentification.
The User model looks like this:
class User < ApplicationRecord
attr_accessor :email, :password, :password_digest
has_secure_password
end
And the Migration:
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :email
t.string :password
t.string :password_digest
t.timestamps
end
end
end
There is also a User-Controller with scaffolded CRUD-Methods. To Generate JWT, I use the following Controller, which is inherited from the Knock::AuthTokenController:
class UserTokenController < Knock::AuthTokenController
end
However, when I generate a User with the console like this...
User.create(:email => 'test.test#test.com', :password => 'test', :password_digest => 'test')
... I get a database object with empty values:
You don't need attr_accessor since you are using ActiveRecord.
Remove this line
attr_accessor :email, :password, :password_digest
I have been following Michael Hartl's Ruby on Rails tutorial book to try and add users to my application. Reading chapter 6, I have added what I believe to be the necessary fields for my user, specifically password and password confirmation via "has_secure_password".
I thought that adding "has_secure_password" to my user model would include the attributes "password" and "password_confirmation" provided I add a "password_digest" to the model. I have done that as the book instructed me to. However, when I run a test, Rails gives me the following error:
Error:
UserTest#test_should_be_valid:
ActiveModel::UnknownAttributeError: unknown attribute 'password' for User.
test/models/user_test.rb:8:in `setup'
I tried this solution and it still gave me the same error, not recognizing the attributes "password" or "password_confirmation". I installed bcrypt using "gem install bcrypt" and included the following in my gem file:
gem 'bcrypt-ruby', :require => 'bcrypt'
I am using Rails 5 and it seems like "has_secure_password" is not supplying the password attributes that I need. Can anyone see what I missed or did wrong that caused "has_secure_password" to not work as intended? Thanks
User Model:
class User < ApplicationRecord
has_many :activities
class User < ActiveRecord::Base
attr_accessor :name, :email, :password, :password_confirmation
has_secure_password
validates :first_name, presence: true, length: {minimum: 1}
validates :last_name, presence: true, length: {minimum: 1}
validates :email, presence: true, uniqueness: true, length: {minimum: 5}
validates :username, presence: true, uniqueness: true, length: {minimum: 1}
validates :password_digest, length: {minimum: 6}
validates :password, :confirmation => true, length: {minimum: 4}
validates :password_confirmation, presence: true
#-----------------------New Stuff ---------------------------------------
acts_as_authentic do |c|
c.crypto_provider = Authlogic::CryptoProviders::Sha512
end
#------------------------------------------------------------------------
#---------------Unsure if working--------------
#validates_presence_of :password, :on => :create
#validates_presence_of :email
#validates_uniqueness_of :email
#----------------------------------------------
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
end
Apologies for the messy code on the model as I am still learning Rails.
User Controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = 'Account created'
else
flash[:notice] ='ERROR: Account was not created'
redirect_to 'users/new'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
end
User Table:
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "username"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "persistence_token"
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
User Test:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
def setup
#user = User.new(first_name: 'test', last_name: 'tester', password: '1234',email: 'test1#mail.com',
password: 'foobar', password_confirmation: 'foobar')
end
test 'should be valid' do
assert #user.valid?
end
end
Update:
I have tested this out and it works. So hope will work for you as well :) Looks like MiniTest doesn't work well with BCrypt. I received the same error - undefined password, but later implemented my change and it went further well.
Original answer:
As of your founded solution it made me think that this makes no sence - adding getter and especially setter methods for :password and :password_confirmation. Because has_secure_password creates those virtually that runs through BCrypt. So doesn't it goes around crypting / encrypting? If so it is not safe. So only option left for testing I see take the BYcript into the testing suite. I think something like this might do the trck:
In User Test:
require 'bcrypt'
def setup
#user = User.new(first_name: 'test', last_name: 'tester', password: BCrypt::Password.create("my password") ,email: 'test1#mail.com', password_confirmation: 'my password')
end
test 'should be valid' do
assert #user.valid?
end
Note that I removed duplicated password: 'foobar. Since with that particular test you are testing if User can be created, so shouldn't pass a different password or even duplicated attribute... Make another test for this (also checkout fixtures, they are great for creating test objects, as well as factories for more complicated cases).
And of course, remove the atr_accessor :password, :password_confirmation form your User model.
p.s. and please fix you code snippet for User class. Or is it really defined twice like this?:
class User < ApplicationRecord
has_many :activities
class User < ActiveRecord::Base
I would like to NOT require email for signing only mobile number to register user and login in using devise gem. I removed email from config/initializers/devise.rb:
This is the link to the validate.rb file of devise. You can see a method email_required? in the model. So I guess
def email_required?
false
end
You need to put above method in your model.rb file.
You'll also need to make a slight modification to your users table. By default, Devise does not allow the email field to be null. Create and run change a migration that allows email to be null
# in console
rails g migration AddChangeColumnNullToUserEmail
# migration file
class AddChangeColumnNullToUserEmail < ActiveRecord::Migration
def self.up
change_column :users, :email, :string, :null => true
end
def self.down
change_column :users, :email, :string, :null => false
end
end
You can use this guide, except use mobile instead of username. e.g.
In devise.rb:
config.authentication_keys = [:mobile]
In your controller:
.permit(:mobile)
Change the email field to your mobile field in app/views/devise/sessions/new.html.erb and app/views/devise/registrations/new.html.erb
In devise.en.yml:
invalid: 'Invalid mobile number or password.'
not_found_in_database: 'Invalid mobile number or password.'
As a complement of #divyang's answers. You should also remove de email's index on users table...
remove_index "users", name: "index_users_on_email"
I'm currently working on a system where the email is only required if the user is not a student and username is required if the user is a student.
So here is what I did in my model :
class User < ActiveRecord::Base
validates :email, presence: true, unless: :student?
validates :username, presence: true, if: :student?
end
This works fine on username attributes, but for the email, I'm still getting Email cannot be blank error. I guess Devise has it's own email validation rule.
How can I make this works, I mean overriding Devise validate presence rule on email?
Thanks
Devise has an email_required? method that can be overrided with some custom logic.
class User < ActiveRecord::Base
validates :username, presence: true, if: :student?
protected
def email_required?
true unless student?
end
end
I think Devise uses email as a key in its model.
add_index :users, :email, unique: true
If you used the generated devise migrations you could make the user_name the key with a migration.
add_index :users, :user_name, unique: true
remove_index(users, column: users)
change_column :users, :email, :string, :null => true