Rails 4 and Devise : Allow email blank on certain conditions - ruby-on-rails

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

Related

Getting two errors for password presence in activerecord

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

Rails NoMethodError: undefined method `password_digest=

I am new to rails and have seen all possible answers for my problem, as this is asked quite frequently by other developers, yet I'm unable to resolve it. Please have a look.
I am getting this error when I try to add a data from the console
User.create(name: "Michael Hartl", email: "mhartl#example.com", phone: "0123456789", password: "foobar", password_confirmation: "foobar")
ERROR SHOWN
undefined method `password_digest=' for #<User:0x0000000375c788>
Did you mean? password=
Controller
def create
#candidate = User.new(user_params)
if #candidate.save
flash[:notice] = "New Candidate Added Successfully"
redirect_to(users_path)
else
render('new')
end
end
private
def user_params
#Whitelisting for strng parameters
params.require(:user).permit(:name, :email, :password, :password_confirmation, :qualification, :college, :stream, :phone)
end
Migration:
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :name, null: false
t.boolean :admin_user, default: false
t.string :email, null: false
t.string :password_digest, null: false
t.string :qualification
t.string :college
t.string :stream
t.string :phone
t.timestamps
end
end
end
Model
require 'bcrypt'
class User < ApplicationRecord
include BCrypt
has_many :results, dependent: :destroy
has_many :exams, through: :results
accepts_nested_attributes_for :results
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
before_save { self.email = email.downcase }
has_secure_password
validates :email, presence: true
validates :email, presence: true, length: { maximum: 255 },format: { with: VALID_EMAIL_REGEX },uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates_confirmation_of :password
validates_presence_of :password_confirmation
validates :name, presence: true
validates :phone, numericality: {only_integer: true}, length: {is: 10 , message: "length should be 10"}
scope :visible, lambda { where(:visible => true) }
scope :invisible, lambda { where(:visible => false) }
scope :sorted, lambda { order("id ASC") }
scope :newest_first, lambda { order("created_at DESC") }
scope :search, lambda {|query| where(["name LIKE ?", "%#{query}%"]) }
end
Have you checked this https://github.com/plataformatec/devise/issues/1845
As per described in the issue, you are using ActiveModel's has_secure_password with Devise. You must not mix the two.
May be removing has_secure_password from User model will resolve your issue.
The code was completely fine,
few server restarts and don't really know how but restarting my code editor ( Visual Studio Code ) worked for me.
I am leaving the question as it is, as I did reach to this code after going through several stackoverflow threads.
NB: For newbies like me, please see how to check for errors while inserting into database from console, it helps a lot. Synatctical example is as:
rails c
user = User.new ( ... . . . . .. )
user.save
user.errors
Thankyou
This happened to me as well, and I found out that sometimes vscode doesn't actually kill the process running. You will need to kill rails from the terminal.
use ps aux | grep rails to find out what processes are running and use kill -9 [rails-pid] to stop the process.
On your next attempt, everything should work fine.
This answer helped me.

How can I update particular fields of model with validation in Ruby on Rails?

There is an AcviteRecord Model named User like this:
class User < ActiveRecord::Base
validates :name, :presence => true
validates :email, :presence => true, :uniqueness => true
validates :plain_password, :presence => true, :confirmation => true
validates :plain_password_confirmation, :presence => true
#...other codes
end
It requires that the update of name and email and the update of password are separated.
When only update name and password, using update or update_attributes will cause password validation which is not needed. But using update_attribute will save name and email without validation.
Are there any ways to update particular fields of model with validation without causing the other fields' validation?
Give it a try, might help
class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, :uniqueness => true
validates :plain_password, length: { in: 4..255, allow_nil: true }, confirmation: true
validates :plain_password_confirmation, presence: true, if: -> (user){ user.plain_password.present? }
# ......
# ......
end
Apart from this you should reconsider about saving plain_password ;)
You can adjust your validations to only run on create. Requiring confirmation ensures changes on edit are applied.
validates :plain_password,
confirmation: true,
presence: {
on: :create },
length: {
minimum: 8,
allow_blank: true }
validates :plain_password_confirmation,
presence: {
on: :create }
I am assuming you are hashing your passwords, so this would accompany code similar to:
attr_accessor :plain_password
before_save :prepare_password
def encrypted_password( bcrypt_computational_cost = Rails.env.test? ? 1 : 10)
BCrypt::Password.create plain_password, cost: bcrypt_computational_cost
end
private #===========================================================================================================
# Sets this users password hash to the encrypted password, if the password is not blank.
def prepare_password
self.password_hash = encrypted_password if plain_password.present?
end
A better way to handle this is to not include the fields in the rest of the edit form, but rather provide a link to "Change my password". This link would direct to a new form (perhaps in a modal window) which will require the new password, confirmation of the new password, and the old password, to prevent account hijacking.
In your case you can use has_secure_password The password presence is only validated on creation.

Overriding presence true in User model

I have the following in my models/user.rb:
validates :company, presence: true
validates :title, presence: true
I have a secondary view where I want to create a user but not require this user to enter a company and a title. How would I do that without modifying the main user.rb?
This is for Rails 3.2
You can do by declaring custom validations the way #BroiSatse has answered or when saving the user you can pass validate: false as argument, do this way
#user.save(:validate => false)
I usually do sth like:
class User < AR::Base
validates :company, :title, presence: true, if: :validate_company_and_title?
def validate_company_and_title?
#validate_company_and_title.nil? || #validate_company_and_title
end
def skip_company_and_title_validation!
#validate_company_and_title = false
end
end
Then in your controller create action for given view you can do:
#user.skip_company_and_title_validation!

Adding additional field and validation to Devise view/model in Rails app

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.

Resources