I'm trying to learn and use my own user model and authentication with Ruby on Rails 4.0, but most tutorials (if not all) seem to be outdated with this recent update. No method described on any of them works. I'm absolutely clueless, this is my User model:
class User
include Mongoid::Document
field :login, type: String
field :hash, type: String
field :salt, type: String
field :email, type: String
field :name, type: String
before_save :hash_password
validate :login, presence: true, uniqueness: true, length: { in: 4..24 }
validate :password, presence: true, confirmation: true, length: { in: 8..32 }
validate :email, presence: true
validate :name, presence: true
def hash_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.hash = BCrypt::Engine.hash_secret(password, salt)
end
end
end
And the controller:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
render 'new'
end
end
def new
#user = User.new
end
private
def user_params
params.require(:user).permit(:login, :password, :password_confirmation, :email, :name)
end
end
I then get an UnknownAttribute error when I save the user. What am I missing? What fields are wrong?
You need to add:
attr_accessor :password
Right now you are trying to use the password attribute but rails doesn't know anything about it. attr_accessor allows you to use the password attribute locally, but will not persist it to the database (which is good).
You can follow rail casts episode. I know its a bit old but i also used it a couple of months ago and for the rails 4 you can simply "permit" the attributes rather than using "attr_accessible" as rails 4 doesn't support attr_accessible.
I managed to make it work. I didn't figure out what was the matter with the "unknown attribute" error, but I changed the model to use the method has_secure_password, which automagically takes my :password and :password_confirmed parameters, bcrypts and save to :password_digest field.
class User
include Mongoid::Document
include ActiveModel::SecurePassword # important, imports has_secure_password
field :login, type: String
field :password_digest, type: String
has_secure_password
field :email, type: String
field :name, type: String
validate :login, presence: true, uniqueness: true, length: { in: 4..24 }
validate :password, presence: true, confirmation: true, length: { in: 8..32 }
validate :email, presence: true, uniqueness: true
validate :name, presence: true
end
This done, I got the following error:
can't activate bcrypt-ruby (~> 3.0.0), already activated bcrypt-ruby-3.1.2. Make sure all dependencies are added to Gemfile.
Even though bcrypt was correctly added to my Gemfile, apparently has_secure_password needs specifically version 3.0.x of the gem, so I forced it:
gem 'bcrypt-ruby', '~> 3.0.0'
This downloaded the version 3.0.1 (not 3.0.0) which worked as expected. I hope they fix this version incompatibility soon.
Thanks for all the answers. This project will be available open source when I finish :)
Related
In my Ruby on Rails application i've set up a user registration / login system that have some validations in the class ( for register a User).
class User < ActiveRecord::Base
# Put the email downcase before save the User
before_save {self.email = email.downcase}
# Confirmation of the password before save
validates_confirmation_of :password
has_secure_password
validates :first_name, :last_name,
presence: true,
format:{ without: /\s/ }
validates :email, uniqueness: true,
format:{
with: /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
}
def to_s
"#{first_name} #{last_name}"
end
end
In this application i also use AngularJS and the question is, can i use AngularJS for the live validation of the user during the registration?
If you want to validate live your fields you'll have to use AngularJS validators. Those ruby validators will be called when you'll submit the form.
I am trying to validate a form, that is not bound to any real Model, so I did the following:
A model
class FormRequest
include ActiveModel::Model
attr_accessor :email
validates :email, presence: true, email: true
end
and a method in a controller
# The form is shown
def index
#form_request = FormRequest.new
end
But when I load the page I get this error:
Unknown validator: 'EmailValidator'
What did I miss?
You don't have the email: true validator.
To validate an email you can use regular expression:
VALID_EMAIL = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL }
I found a simple solution for my problem with the following gem
gem 'email_validator'
It seems like (amazingly) Rails 4 does not include an email validator.
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.
I think this is a simple problem. So far I've ran
rails generate scaffold User username:string email:string password:string
to make a new scaffold for the User model. The following is my user.rb:
class User < ActiveRecord::Base
validates :username, presence: true, length: { in: 2..50 }, uniqueness: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
validates :password, presence: true, length: { in: 4..50}
#self.password = 'abcd' #I can't even change the parameter to something hard-coded!
end
I've written a few tests and that works great. My next step is to put the password parameter through a hashfunction (which I want to write myself for educational purposes) and save this newly modified string instead of the original string. I don't seem to understand how to do this? Do I create a method in user.rb which gets called from the users_controllers.rb under the create method?
I would like to test this by doing rails console --sandbox and writing some tests, too.
You can use the before_save callback
# user.rb model
before_save :hash_password
def hash_password
self.password = some_hash_function(self.password)
end
You have to be careful with this method not to hash the password multiple times. That is you must always hash the clear password and not hash the hashed version. That's why I would do it like this and call the field password_digest and only hash the password if the password attribute is set.
# user.rb model
attr_accessor :password
before_save :hash_password
def hash_password
self.password_digest = some_hash_function(self.password) unless self.password.blank?
end
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
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