I am trying to validate an email address.. whether it is present or not.. and whether it meets a regex criteria (/xyz/). I really need to do this on the controller or view level in rails as I am going to be dumping ActiveRecord for my app as I am planning on using a nosql database later.
Any hints? Suggestions?
In the model, it would be the following code.. but I'm trying to do this in controller or view:
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
class UsersController < ApplicationController
def create
user = User.new(params[:user])
if valid_email?(user.email)
user.save
redirect_to root_url, :notice => 'Good email'
else
flash[:error] = 'Bad email!'
render :new
end
end
private
def valid_email?(email)
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
email.present? &&
(email =~ VALID_EMAIL_REGEX) &&
User.find_by(email: email).empty?
end
end
However, I would say that validation still belongs in the model, even if it isn't an ActiveRecord-based one. Please take a look at how to use ActiveModel::Validations:
http://api.rubyonrails.org/classes/ActiveModel/Validations.html
http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
http://asciicasts.com/episodes/219-active-model
You can leave it in the model even if you use nosql later on. Just use ActiveModel instead of ActiveRecord. For example, do not inherit from ActiveRecord.
class ModelName
include ActiveModel::Validations
attr_accessor :email
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
Related
I have a Sessions controller that requires authentication for creating a session using .authenticate method of has_secure_password defined in the User model, as per below:
Edit:
class User < ApplicationRecord
before_save { self.email = email.downcase }
# Relationships
has_secure_password
has_many :pedidos
# Validations
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :nome, presence: true
validates :empresa, presence: true
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
validates :cpf, presence: true, length: { minimum: 11, maximum: 14 }
validates :password, presence: true, length: { minimum: 6 }
def admin?
self.admin
end
end
SessionsController
def create
user = User.find_by(email: params[:sessions][:email])
if user && user.authenticate(params[:sessions][:password])
flash[:success] = "Seja bem vindo(a), #{user.nome}!"
session[:user_id] = user.id
redirect_to user
else
flash.now[:danger] = 'Não foi possível realizar o login, '
flash.now[:danger] += 'combinação usuário/senha inválido'
render 'new'
end
end
Now I created an admin/Users controller that I would like to perform database operations without having to enter password and password_confirmation fields. In the current scenario I'm getting error messages saying "password and password_confirmation can't be blank"
How should I proceed? Thank you.
If presumably your problem is that you'd like to be able to save new Users to your db without needing a password, one way might be to just add a dummy password for admins as explained here
Another might be to skip validations when saving an admin to the db. (I haven't tried this with has_secure_password so I'm not positive that it'd work but worth a shot)
I have a edit form for a client. It is also possible to change the password, but of course you don't want to change(and/or reenter) your password every time you change on of the other settings. To avoid updating the password I tried this:
def client_update_params
if admin? == true
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country,:billing_informations)
else
if params[:client][:password].blank?
params[:client].delete("password")
params[:client].delete("password_confirmation")
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country)
else
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country,
:password,:password_confirmation)
end
end
end
So the idea is to check if the password field is set or not. If it is set, update with new password, else do not update the password. But every time I hit submit(and leave the password field empty), the form validation says the password is to short....
Is there maybe a working/more elegant solution for this problem ?
EDIT:VALIDATIONS ON MODEL:
attr_accessor :is_admin_applying_update
attr_accessor :plain_password
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :company_name,presence:true
validates :address_street,presence:true
validates :address_number,presence:true
validates :address_city,presence:true
validates :address_zip,presence:true
validates :address_country,presence:true
validates :billing_informations,presence:true
has_secure_password
validates :password, length: { minimum: 6 }, :unless => :is_admin_applying_update
def Client.new_remember_token
SecureRandom.urlsafe_base64
end
def Client.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = Client.encrypt(Client.new_remember_token)
end
Remember that you don't really have a password attribute in your model. The password is stored encrypted in a field named password_digest.
You should only be validating the password attribute when a password and a password_confirmation is given. You can do this in your model validation rather than deleting things from the params hash.
Also, you should validate the existence of a password_confirmation if password is present.
Add a virtual attribute skip_password like the devise uses
In model
attr_accessible :skip_password
validates :password, length: { minimum: 6 }, :unless => :skip_password
def skip_password=(value)
#skip = value
end
def skip_password
#skip
end
In controller
#client.skip_password = true
I have a problem with the test of "User when email address is already taken", here is what it shows when I run the test
1) User when email address is already taken
Failure/Error: user_with_same_email = #user.dup
NoMethodError:
private method `initialize_dup' called for #<User:0x007f9710c7c528>
# ./spec/models/user_spec.rb:78:in `block (3 levels) in <top (required)>'
I don't realize what I am defining as private and I can't call.
Here is the test
describe "when email address is already taken" do
before do
user_with_same_email = #user.dup
user_with_same_email.email = #user.email.upcase
user_with_same_email.save
end
it { should_not be_valid }
end
and here the user model
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
has_many :microposts, dependent: :destroy
before_save { self.email.downcase! }
before_save :create_remember_token
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
validates :name, presence: true, length: { maximum: 50}
validates :password, length: { minimum: 6 }
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
Thank you
It was a bug in Rails 3.2.12 with ruby 2. Take a look on this https://github.com/rails/rails/issues/9417. Switching to Rails 3.2.15 should solve your problem.
This is really a comment to "it seems to be back in 4.0.2 – jackr Jan 10 at 22:58":
What I've done is force it to be public:
#my_object.errors.class_eval do
def initialize_dup(other) # :nodoc:
#messages = other.messages.dup
super
end
end
#my_object.errors.initialize_dup(another_object.errors)
I'd appreciate some up votes so that I can get enough points so that stackoverflow will allow me to comment :)
According to the accepted answer in my similar ticket, Rails 4 Validating email uniqueness without case_sensitive, this seems to be an irreconcilable trade-off between performance and case-sensitive validation for MySQL. There are links there to two GitHub issues that show some flip-flopping on the implementation, but there doesn't appear to be any fully satisfactory solution possible. Several work-arounds are suggested there.
I'm trying to do a custom authentication to better understand what's going on under the hood. It took a while to get it to accept password and password_confirmation, but now it just wont work and I'm all out of ideas
class UsersController < ApplicationController
def new
#user = User.new
end
def create
par = params[:user]
#user = User.new(params[:user])
if #user.verify_password_confirmation(par[:password], par[:password_confirmation]) && #user.save
sign_in #user
redirect_to user_url(#user)
else
render 'new'
end
end
end
Is there any danger to having password and password_confirmation in attr_accessor?
require 'bcrypt'
class User < ActiveRecord::Base
attr_accessor :password_confirmation, :password
attr_accessible :name, :email, :password, :password_confirmation
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d]\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }
validates :password_confirmation, presence: true
def password=(password)
self.password_digest = BCrypt::Password.create(password)
end
def verify_password(password) #for the sessions, which obviously I can't check yet
BCrypt::Password.new(self.password_digest) == password
end
def verify_password_confirmation(pass, pass_con) #couldn't see how else to confirm it
pass_con == pass
end
def reset_session_token!
self.session_token = SecureRandom::base64(32)
self.save!
self.session_token
end
end
EDIT: Specifically the problem is that it's failing at either the #user.save or the verify_password_confirmation and re-rendering
Look at secure_password.rb on github. has_secure_password is much more simple, clear and easy to understand than other third party authentication gems or plugins.
Also note that has_secure_password uses bcrypt-ruby gem for generating password_digest. So if you want to roll your own authenticating system then you will want to think about its security as well.
I just came across omniauth-identity which enables users to sign in and register without using a Facebook, Twitter, etc.
There is a step were you have to create a Identity model (I'm following this Railscast):
class Identity < OmniAuth::Identity::Models::ActiveRecord
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
end
Now, I already have an User model and a login and registration system (created by following the Ruby on Rails Tutorial):
user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
So I'm a bit confused. Should remove the lines that have to do with authentication in the User model (e.g. validation, attr_accesible, create_remember_token etc. along with the name and email fields in the users table)?
And remove sessions_helper.rb too?
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def current_user?(user)
user == current_user
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url
end
end
Because, correct me if I'm wrong, but I I think omniauth-identity handles that too (except for the current_user part.
Creating the Identity model is useful mostly to authenticate with multiple providers. Here's a good description of how to go about doing that:
https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers
It answers your question what should be in the User model and what in the Identity model.