In my User model I have a before_validation method called normalize_params that uses downcase.
class User < ApplicationRecord
before_validation :normalize_params, on: [:create, :update]
validates :email, :presence => true
validates :email, :uniqueness => true
validates :username, :presence => true
validates :username, :uniqueness => true
validates_confirmation_of :password
scope :all_users_except, -> (current_user) {
where.not(id: current_user)
}
has_attached_file :avatar, styles: { medium: "300x300>", thumb:
"100x100>" }, default_url: "/images/missing.png"
validates_attachment_content_type :avatar, content_type:
/\Aimage\/.*\z/
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
private
def normalize_params
self.name = name.downcase.titleize
self.email = email.downcase
self.home = home.downcase.titleize
end
end
All of that works perfectly in my app but my tests break when they hit the downcase with this error...
NoMethodError:
undefined method `downcase' for nil:NilClass
Here are my tests...
require 'rails_helper'
describe User, 'validation' do
it { should validate_presence_of :email }
it { should validate_presence_of :username }
it { should validate_presence_of :password }
end
If I take before_validation and normalize_params out then my tests pass.
As per the documentation exemplifies, you could use attribute_present? before:
class User < ApplicationRecord
before_validation :normalize_params, on: %i[create update]
validates :email, presence: true, uniqueness: true
validates :username, presence: true, uniqueness: true
private
def normalize_params
titleize_name
downcase_email
# You can any other here.
end
def titleize_name
self.name = name.downcase.titleize if attribute_present? 'name'
end
def downcase_email
self.email = email.downcase if attribute_present? 'email'
end
end
Note you can:
Use %i[] to create an array of symbols.
Validates presence and uniqueness by separating them by comma.
Prefer the is_expected.to than should syntax (it { is_expected.to validate_presence_of :attribute })
Separate each of your attribute modifications to easily work and include them.
Avoid using hash rocket when not needed. See ruby-style-guide#hash-literals.
Either one of name, email or home may be nil. I'd recommend using the safe navigation operator:
def normalize_params
self.name = name&.downcase&.titleize
self.email = email&.downcase
self.home = home&.downcase&.titleize
end
Related
I followed this guide to use Username and Email in devise authentication, but when I try to create a new user, ie "newusername" with email "new#username.es" I get the validation error "You have to include an # symbol in your email" but refering to username field.
I tried to use a format validation in the user model, but it doesn't solved anything
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
attr_accessor :login
validates_uniqueness_of :username
validates_presence_of :username
validates :username, length: { in: 4..20 }
validates_format_of :username, :with => /\A[-a-z]+\Z/
##### and also
validates :username,
:presence => true,
:uniqueness => { :case_sensitive => false },
:format => { with: /\A[a-zA-Z]+\z/ },
:length => { in: 4..20 }
I couldn't find in Google anyone else that had the same issue.
I'll appreciate your help.
I have created a user_controller that basically extends the devise user setup, using the following syntax:
class UsersController < ApplicationController
def index
#users = User.order('created_at DESC').all
end
def create
#user = User.create(user_params)
end
private
def user_params
params.permit :user (:avatar, :email, :password, :password_confirmation )
end
end
In my model user.rb I have:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_attached_file :avatar, styles: {
large: "600x450#",
medium: "250x250#",
small: "100x100#"
}, :default_url => "/images/:style/filler.png"
#validates_attachment_content_type :avatar, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
validates :avatar, :email, :password, presence: true
end
These all work fine but when I reload the page I get a weird error that says there is a syntax error and its strange because it was working before this. I am assuming its some sort of syntax error with a rails version.
The error is:
Error details saved to: /tmp/passenger-error-9mVpXM.html
Message from application: /home/deployer/staging/releases/20140806094001/app/controllers/users_controller.rb:11: syntax error, unexpected ',', expecting ')'
...require(:user).permit (:avatar, :email, :password, :password...
... ^
/home/deployer/staging/releases/20140806094001/app/controllers/users_controller.rb:11: syntax error, unexpected ',', expecting :: or '[' or '.'
...it (:avatar, :email, :password, :password_confirmation )
... ^ (SyntaxError)
Is there anything thats glaringly wrong with this?
Your user_params method is wrong. It should be:
params.require(:user).permit(:avatar, :email, :password, :password_confirmation)
You have syntax error in user_params
You need to do like:
params.require(:user).permit(:avatar, :email, :password, :password_confirmation)
You can read more
Before my User's can register I need to authenticate them via api first to see if their information is valid. Anyhow I have my validate_api method working as it needs to be I have tested this however, I'm not sure as to why when i try to register with a faulty api it still saves the user.
I put my method in a controller and called it with a valid api and it returned true and then with faulty api and it returned false.
So if the method is working it's either being ignored or something overrides it.
My User model
class User < ActiveRecord::Base
attr_accessor :login
before_save :validate_api
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
validates :username, :presence => true, :length => { :minimum => 6, :maximum => 255 }
validates :apiid, :presence => true, :numericality => { :only_integer => true }
validates :vcode, :presence => true, :length => { :minimum => 20, :maximum => 255 }
# Setup accessible (or protected) attributes for your model
attr_accessible :login, :username, :group, :apiid, :vcode, :email, :password, :password_confirmation, :remember_me
# Check if user is banned before login
def active_for_authentication?
super && self.banned == 0
end
# Redefine authentication procedure to allow login with username or email
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login).downcase
#where(conditions).where('$or' => [ {:username => /^#{Regexp.escape(login)}$/i}, {:email => /^#{Regexp.escape(login)}$/i} ]).first
where(conditions).where("username = '#{login}' OR email = '#{login}'").first
else
where(conditions).first
end
end
# Validate API information
private
def validate_api
require 'nokogiri'
require 'open-uri'
uri = "https://*******?keyID=#{self.apiid}&vCode=#{self.vcode}"
xml = Nokogiri::XML(open(uri))
xml.xpath("//row").each do |row|
if row['****'].downcase == '****'
return true
else
return false
end
end
end
end
Instead of using before_save :validate_api you should be using validate :check_api, and then adding an error message (eg: errors[:apiid] << "must be a valid API id.") if the api check fails.
I'm working on a Rails app with two-factor authentication. The User model in this app has an attribute, two_factor_phone_number. I have the model validating that this attribute is present before the model can be saved.
To make sure the phone number is saved in a proper format, I've created a custom attribute assignment method that looks like this:
def two_factor_phone_number=(num)
num.gsub!(/\D/, '') if num.is_a?(String)
self[:two_factor_phone_number] = num.to_i
end
I'm doing some acceptance testing, and I've discovered that if this method is in the model, the ActiveRecord validation is ignored/skipped and a new model can be created without a two_factor_phone_number set.
The model code looks like this:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable,
:lockable
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :two_factor_phone_number
validates :first_name, presence: true
validates :last_name, presence: true
validates :two_factor_phone_number, presence: true
# Removes all non-digit characters from a phone number and saves it
#
# num - the number to be saved
#
# Returns the digit-only phone number
def two_factor_phone_number=(num)
num.gsub!(/\D/, '') if num.is_a?(String)
self[:two_factor_phone_number] = num.to_i
end
end
You could add a format validation:
validates :two_factor_phone_number, :format => { :with => /[0-9]/,
:message => "Only digits allowed" }
and/or create another method to set this attribute and call it before validation
before_validation :update_phone_format
def update_phone_format
...
end
I have a rails 3 application that I am working on and have implemented devise. I have it working, and now I wish to extend it so that a user is unable to use a old password more than once. Found this functionality on github which to my suprise was good. Disallow previously passwords - Git Hub
I thought this would straight forward but it is clearly not. My code looks like the following:
create_passwrod_histories.rb
class CreatePasswordHistories < ActiveRecord::Migration
def self.up
create_table(:password_histories) do |t|
t.integer :user_id
t.string :encrypted_password
t.timestamps
end
end
def self.down
drop_table :password_histories
end
end
User.rb
class User < ActiveRecord::Base
include ActiveModel::Validations
has_many :roles_users
has_many :roles, :through => :roles_users
has_many :projects
has_many :password_histories
after_save :store_digest
# authorization include this in whichever model that will use ACL9
acts_as_authorization_subject
def has_role?(role_name, object=nil)
!! if object.nil?
self.roles.find_by_name(role_name.to_s) ||
self.roles.member?(get_role(role_name, nil))
else
method = "is_#{role_name.to_s}?".to_sym
object.respond_to?(method) && object.send(method, self)
end
end
def login(user)
post_via_redirect user_session_path, 'user[username]' => user.username, 'user[password]' => user.password
end
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable #:registerable,
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :timeoutable
acts_as_authorization_subject :association_name => :roles
attr_accessor :login
# Setup accessible (or protected) attributes for your model
attr_accessible :id, :login, :username, :full_name, :email, :password, :password_confirmation, :remember_me, :role_ids
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates_presence_of :username, :full_name
validates_format_of :username, :with => /^[-\w\._#]+$/i, :allow_blank => true, :message => "should only contain letters, numbers, or . - _ #"
validates_length_of :username, :minimum => 1, :allow_blank => true
validates_uniqueness_of :username, :email
validates :email, :presence => true,
:format => { :with => email_regex }
validates :password, :unique_password => true
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
end
private
def store_digest
if encrypted_password_changed?
PasswordHistory.create(:user => self, :encrypted_password => encrypted_password)
end
end
end
unique_password_validator.rb
require 'bcrypt'
class UniquePasswordValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.password_histories.each do |ph|
bcrypt = ::BCrypt::Password.new(ph.encrypted_password)
hashed_value = ::BCrypt::Engine.hash_secret([value, Devise.pepper].join, bcrypt.salt)
record.errors[attribute] << "has been used previously." and return if hashed_value == ph.encrypted_password
end
end
end
I then run my app and try to use the same password. It then throws up the follwoing error uninitialized constant User::PasswordHistory
The only way that I can see from your code why that would be happening is if you didn't have the PasswordHistory model object. That code from Github doesn't actually explicitly tell you to do it, but you certainly need it. So, maybe you created and ran the migration but forgot to create the model, as in:
class PasswordHistory < ActiveRecord::Base
...
end