I really need your help, because I can't find a solution on my own since 2 hours. I tried many different approaches, but I just don't get how I can validate an attribute of a variable by using another attribute as a condition.
This was my last try:
class User < ApplicationRecord
validates :admin, absence: true,
if: ["student?", :email]
def student?(user)
user.email.include? 'stud'
end
end
I want to validate, that a user with the attribute "admin" can't have an email address, which includes 'stud'. Even better would be a validation, that 'stud' users can't be admin.
When I run db:seed I get this error, which stays if I put the 'stud' in brackets:
rails aborted!
ArgumentError: wrong number of arguments (given 0, expected 1)
/home/studi/Bachelorarbeitsapp/sample_app/app/models/user.rb:22:in `student?'
Please have a look at this issue!
I am willing to share any other code, which is necessary to solve the problem. Thanks!
Here is the full helper code:
class User < ApplicationRecord
has_many :preferences, dependent: :destroy
accepts_nested_attributes_for :preferences
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
has_secure_password
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#(stud.uni-hannover.de|wiwi.uni-hannover.de)+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
validates :mat_number, presence: true, length: { minimum: 7, maximum: 8 }, numericality: { only_integer: true }
validates :admin, absence: true,
if: ["student?", :email]
def student?(user)
user.email.include? 'stud'
end
validates :ects, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 170, only_integer: true }, allow_nil: true
validates :grade_avg, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 4 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Activates an account.
def activate
update_columns(activated: true, activated_at: Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Based on the Active Record Validations Guide section 6.2 Custom Methods, it seems like you should be able to do something like:
class User < ApplicationRecord
validate :admin_cant_be_student
def admin_cant_be_student
if admin and email.include?('stud')
errors.add(:admin, "can't have a student email address")
end
end
end
You don’t need a custom validation:
Could use validates_exclusion_of
validates_exclusion_of :email, in: %w( stud ), if: -> { |user| user.email.include? “admin” }, message: “admins can’t have stud in email”
validates_exclusion_of :email, in: %w( admin ), if: -> { |user|user.email.include? “stud” }, message: “students can’t have admin in email”
Related
I am using devise-auth-token and want to sign in with field another than email.
So I used phone_number instead and everything is working properly but I can't remove validation on email, here are my codes
user.rb
class User < ActiveRecord::Base
include DeviseTokenAuth::Concerns::User
devise :database_authenticatable, :confirmable, authentication_keys: [:phone_number]
mount_uploader :avatar, AvatarUploader
validates :first_name, presence: true,format: {with: /[[:word:]]/}
validates :last_name , presence: true,format: {with: /[[:word:]]/}
validates :email , format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }, uniqueness: true
validates :phone_number, presence: true, uniqueness: true
validate :birthdate_in_the_future_invalid
validates :country_code, presence: true
validates :birthdate, presence: true
validates :gender, presence: true
# validates :file, presence: true
validates :phone_number, phone: { possible: true, types: [:mobile], country_specifier: -> phone { phone.country_code.try(:upcase) } }
validate :e164_phone_number
after_create :set_provider_uid
def email_required?
false
end
def email_changed?
false
end
def password_required?
super if confirmed?
end
def password_match(password, password_confirmation)
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
private
def birthdate_in_the_future_invalid
begin
if Date.strptime(birthdate, '%d-%m-%Y') > Date.today
errors.add(:birthdate, "in_the_future")
end
rescue
errors.add(:birthdate, "invalid format")
end
end
def e164_phone_number
if phone_number[0] != '+'
errors.add(:phone_number, "invalid format")
end
end
def set_provider_uid
self.update_column(:uid, phone_number)
self.update_column(:provider, "phone_number")
end
end
and this is my initializers/devise.rb
Devise.setup do |config|
config.mailer_sender = 'aliabdelrahmanweka74#gmail.com'
require 'devise/orm/active_record'
config.authentication_keys = [:phone_number]
config.case_insensitive_keys = [:phone_number]
config.strip_whitespace_keys = [:phone_number]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 11
config.reconfirmable = false
config.expire_all_remember_me_on_sign_out = true
config.password_length = 6..128
config.email_regexp = /\A[^#\s]+#[^#\s]+\z/
config.reset_password_within = 6.hours
config.sign_out_via = :delete
end
I used funciton email_required? and make it return false but nothing happens
I call localhost:3000/users , Method Post
params:
{
"user": {
"first_name": "ali",
"last_name": "weka",
"gender": "male",
"birthdate": "25-06-2016",
"email": "",
"phone_number": "+201118574649",
"country_code": "EG",
"uid": "+201118574649",
"provider": "phone_number"
}
}
and here's the response
{"status":"error","data":{"id":null,"first_name":"ali","last_name":"ali","country_code":"EG","phone_number":"+201119574649","gender":"male","birthdate":"25-06-2016","created_at":null,"updated_at":null,"provider":"email","uid":"+201119574649","avatar":{"url":null},"email":""},"errors":{"email":["can't be blank","is invalid"],"full_messages":["Email can't be blank","Email is invalid"]}}
so any help, please?
I had the same problem. I wanted some users with email, and some without.
Solution
You should first remove the null: false restriction from users table with a migration. Should you want to remove the default "", do it to. Don't remove the index, so it still validates that emails are unique (in case you still use emails sometimes). Null values won't have a problem with the unique constraint.
If you are using devise validatable, add this method in your User model:
def email_required?
false
end
After doing the previous part, you should do one of the following:
Option 1:
Going deep into devise_token_auth gem, at least in my case, I realized that this concern was being included.
It's included by default unless you modify the DeviseTokenAuth initializer with
config.default_callbacks = false
But maybe you don't want that.
Option 2:
If you check the code in the concern I linked, you can see that it validates email in case the provider of the user is 'email'.
The fix in my case was to change the provider field to something else for the users that didn't have email.
With any of those 2 changes, your email won't be validated anymore.
For somebody who needs email sometimes:
Of course, do what Devise wiki says
apart from the other field include email in case_insensitive_keys, strip_whitespace_keys
and you can have a method in the User model with:
def email_required?
true unless phone_number?
end
My intent is to implement STI with two types: Staff and Clinician. My previous implementation was using roles with enums, and after doing my best to follow answers to similar questions, take out all references in tests etc. to enum roles and replace with references to types , I am getting many versions of the following error when I run my testing suite:
ERROR["test_valid_signup_information_with_account_activation", UsersSignupTest, 1.01794000000001]
test_valid_signup_information_with_account_activation#UsersSignupTest (1.02s)
ActiveRecord::SubclassNotFound: ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'Staff'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite User.inheritance_column to use another column for that information.
app/controllers/users_controller.rb:19:in `create'
test/integration/users_signup_test.rb:27:in `block (2 levels) in <class:UsersSignupTest>'
test/integration/users_signup_test.rb:26:in `block in <class:UsersSignupTest>'
Here are a couple areas where I am confused that could potentially be hiding issues:
In my user model user.rb, I think I am defining the sub Classes correctly (Staff and Clinician), but I'm unsure if I'm wrapping everything correctly. Does all the other code have to be contained in one of these classes? Am I misusing "end"?
class User < ApplicationRecord
end
class Staff < User
end
class Clinician < User
end
belongs_to :university
has_many :referral_requests
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :type, presence: true
validates :university_id, presence: true, if: lambda { self.type == 'Staff' }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
def feed
ReferralRequest.where("user_id = ?", id)
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Here's the specific test code that is failing (one of many in the test suite that are failing - all the user parameters are defined similarly though). Am I passing the staff parameter appropriately?
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user#example.com",
university_id: 1 ,
type: "Staff",
password: "password",
password_confirmation: "password" } }
Here is my users table schema:
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.string "reset_digest"
t.datetime "reset_sent_at"
t.integer "university_id"
t.integer "role"
t.string "type"
t.index ["email"], name: "index_users_on_email", unique: true
end
Thanks very much for any ideas! I ask a lot of questions on here but it's only after trying to work through similar answers for quite a while.
Assuming that the code sample above is accurate, you are seeing this error because the user.rb file is invalid Ruby and failing to parse. You should also be seeing an interpreter error about that.
class User < ApplicationRecord
belongs_to :university
has_many :referral_requests
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :type, presence: true
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# etc...
end
class Staff < User
validates :university_id, presence: true
end
class Clinician < User
end
Standard class-inheritance practices apply, so if there is code in there that is only appropriate for a specific subclass it should move there (e.g. university_id validation moving to Staff).
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
These should be written as
def self.digest(string)
# ...
end
def self.new_token
# ...
end
or, alternatively,
class << self
def digest(string)
# ...
end
def new_token
# ...
end
end
So my problem is more or less as follows. I have a rails applications with the following classes:
Users
Skills
Each user can have multiple skills and they are added from the users profile screen which is the show action for the users controller. Currently I have the Skills set up and able to be created but I cannot add a pre-built skill onto a users profile. When I try to submit I get the following error Empty list of attributes to change
The following is my add skill to user action:
def add_skill_to_user
user = User.find(params[:id])
user.skills.create(skill_params) #skill name, level...
#skills_options = Skill.all.map{|s| [ s.name, s.id] }
#whatever happens when this is is done, redirect, json answer etc...
if user.skills.update_all(skill_params)
flash[:success] = "Skill Added"
else
render 'add_skill_to_user'
end
end
private
# Set skills params whitelist
def skill_params
params.permit(:name, :user_id)
end
and the following to routes
post 'users/:id/add_skill_to_user' => 'users#add_skill_to_user'
And this is my form
<%= form_tag({controller: "users", action: "add_skill_to_user"}, method: "put") do %>
<%= collection_select(:skill, :name, Skill.all, :id, :name) %>
<%= submit_tag 'Submit' %>
<%end%>
Let me know any additional information you need.
---------Added Models Code------------------
User.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
belongs_to :group
has_many :ranks
has_many :skills
has_many :mission_notes
has_and_belongs_to_many :training_events
accepts_nested_attributes_for :skills
validates :username, presence: true
validates :email, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :group_id, presence: true
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver
end
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
end
Skill.rb
class Skill < ActiveRecord::Base
belongs_to :user
end
Your collection_select will generate something like
<select name="skill[name]">
<option value="1">First Skill</option>
...
</select>
where "skill[name]" doesn't reflect what's in the option values (skill ids).
Furthermore, your params whitelist doesn't handle the fact that the POST data generated by the select is an array.
I'd suggest
<%= collection_select(:skill, :id, Skill.all, :id, :name) %>
with this whitelist filter in the controller:
params.require(:skill).permit(:id)
But your add_skill_to_user method is confusing. If all you want to do is assign pre-existing skills to a user, there is a RailsCast for that.
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 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.