Related
I want the password should be mandatory for web registration only and not for mobile app registration.
My validation code is below:
class User < ApplicationRecord
validate :password_for_web
validate :password_confirmation_for_web
def password_for_web
if !app_user && password.blank?
errors.add(:password, "can't be blank.")
end
end
def password_confirmation_for_web
if !app_user && password != self.password_confirmation=
errors.add(:password_confirmation, "doesn't match.")
end
end
end
Validation is working properly but when in case of mobile app registration it is still requiring password. Help regarding the issue would be appreciable.
You can use the if: and unless: options to toggle validations:
class User < ApplicationRecord
validates :password, presence: true, confirmation: true, unless: :app_user?
# or
validates :password, presence: true, confirmation: true, if: :web_user?
# TODO implement app_user?
end
You can pass a symbol (a method name) or a lambda.
If you are using simple rails app with responsive design.
You need to first check device is mobile or other device. This you can do in many ways.
Custom Way :
In application_helper.rb:
def mobile_device
agent = request.user_agent
return "tablet" if agent =~ /(tablet|ipad)|(android(?!.*mobile))/i
return "mobile" if agent =~ /Mobile/
return "desktop"
end
Then you can use it in your views:
<% if mobile_device == "mobile" %>
//add extra parameter to check in model
<% form_tag :mobile_device, true %>
<% end %>
In your model :
class User < ApplicationRecord
validate :password_for_web, if: :mobile_device?
validate :password_confirmation_for_web, if: :mobile_device?
def password_for_web
if !app_user && password.blank?
errors.add(:password, "can't be blank.")
end
end
def password_confirmation_for_web
if !app_user && password != self.password_confirmation=
errors.add(:password_confirmation, "doesn't match.")
end
end
def mobile_device?
mobile_device.present?
end
end
You can also use gems to check mobile device like:
https://github.com/fnando/browser
https://github.com/shenoudab/active_device
If you have separate mobile app.
Add extra parameter in your registration form for mobile application like I used in my view with name mobile_device. Use updated model code and you are done.
Calling validate :method_name causes an infinite loop SystemStackError.
Doing the validation directly inline allows the validation to pass without error.
There must be something I'm overlooking or doing wrong...
The validations worked fine when doing them directly in the model instead of within the conditional_reservation_validation.
Example code that causes SystemStackError stack level too deep
The basic process has been built following this example:
Building Partial Objects Step by Step
I know this needs to be refactored / cleaned up.
Snippet from model:
validate :conditional_reservation_validation
def conditional_reservation_validation
if created_by_admin?
validates_presence_of :vehicle_location
validates_presence_of :pickup, :dropoff, :departure_date, :departure_time, :passengers
else
if active_or_parking?
validates_presence_of :vehicle_location
# SystemStackError occurs when validate :parking_agreement_if_location fires
validate :parking_agreement_if_location
end
if active_or_pickup?
# additional validations
end
end
# ...
end
def parking_agreement_if_location
if vehicle_in_special_parking_location(vehicle_location)
if self.parking_agreement == true
# ok
else
self.errors.add :base, "Must read and understand parking instructions."
end
end
end
def vehicle_in_special_parking_location(vehicle_location)
parking_locations = Location.where(require_parking: true)
if parking_locations.include?(vehicle_location)
return true
else
return false
end
end
# methods to check the step in process
def active_or_parking?
status.include?('parking') || active?
end
Calling validate :parking_agreement_if_location triggers a SystemStackError
Example code that stops the error:
Just taking the code out of the :parking_agreement_if_location method and putting it directly inline stops the SystemStackError.
validate :conditional_reservation_validation
def conditional_reservation_validation
if created_by_admin?
validates_presence_of :vehicle_location
validates_presence_of :pickup, :dropoff, :departure_date, :departure_time, :passengers
else
if active_or_parking?
validates_presence_of :vehicle_location
if vehicle_location
locations = Location.where(require_parking: true)
if locations.include?(vehicle_location)
if parking_agreement == true
# ok
else
self.errors.add :base, "Must read and understand parking instructions."
end
# validate :parking_agreement_if_location
end
end
end
if active_or_pickup?
# These methods cause SystemStackError as well...
validate :pickup_date_in_range
validate :pickup_date_in_future
end
end
end
Controller update action:
def update
params[:reservation][:status] = step.to_s
params[:reservation][:status] = 'active' if step == steps.last
case step
when :parking
#reservation.assign_attributes(reservation_params)
when :pickup
#reservation.assign_attributes(reservation_params)
when :billing
#reservation.assign_attributes(reservation_params)
end
render_wizard #reservation
end
You are using validations wrong way. They need to be invoked on class level.
You need to use conditional validations instead:
validates_presence_of :vehicle_location, if: :created_by_admin?
validates_presence_of :pickup, :dropoff, :departure_date, :departure_time, :passengers, if: :created_by_admin?
validates_presence_of :vehicle_location, unless: :created_by_admin?, if: :active_or_parking?
validate :parking_agreement_if_location, unless: :created_by_admin?, if: :active_or_parking?
I have a Company model with attr_accessor :administrator, so when user creates company, he also need to fill some fields for administrator of this company. I'm trying to test, that he fill all fields correctly.
class Company < ActiveRecord::Base
attr_accessor :administrator
validates :name, presence: true
validates :administrator, presence: true, if: :administrator_is_valid?
private
def administrator_is_valid?
administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]
end
end
company_spec.rb is:
require 'rails_helper'
describe Company do
it 'is valid with name and administrator' do
company = Company.new(name: 'Company',
administrator: {
name: nil,
email: nil,
phone: nil,
password: 'password',
password_confirmation: ''
})
expect(company).to be_valid
end
end
So, as you see, I have a lot of mistakes in validation test, but RSpec pass it.
Thanks!
That's because you didn't construct your validation properly. See, if: administrator_is_valid? will return false for your test, telling Rails to skip this validation rule.
I suggest you drop using the presence validator in favor of using administrator_is_valid? method as a validation method, because after all, if the administrator is valid then it is present. The code should look like this
validate :administrator_is_valid?
private
def administrator_is_valid?
(administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]) or
errors.add(:administrator, 'is not valid')
end
You could clean up your code like this:
validate :administrator_is_valid?
private
def administrator_is_valid?
if administrator_cols_present? && administrator_passwords_match?
true
else
errors.add(:administrator, 'is not valid')
end
end
def administrator_cols_present?
%w(name phone email password password_confirmation).all? do |col|
administrator[col.to_sym].present? # or use %i() instead of to_sym
end
end
def administrator_passwords_match?
administrator[:password] == administrator[:password_confirmation]
end
Another improvement might be to move your administrator to a struct, then call valid? on the object.
admin = Struct.new(cols) do
def valid?
cols_present? && passwords_match?
end
def cols_present?
cols.values.all? { |col| col.present? }
end
def passwords_match?
cols[:password] == cols[:password_confirmation]
end
end
Then:
validate :administrator_is_valid?
def admin_struct
#admin_struct ||= admin.new(administrator)
end
def administrator_is_valid?
errors.add(:administrator, 'is not valid') unless admin_struct.valid?
end
Thanks for answering my previous question, but I ran into a new problem.
I'm creating a custom validator that validates whether a user typed in a clean word. This
is used on my UsersController as a validation method.
I am using the Obscenity gem but created some of my own methods to ensure quality data.
Error Message
NoMethodError: Undefined method include? for Nil:NilClass
The problem with this is that my methods work if a record already exists, but they don't work during record creation. I've tried to combat this problem by using
:on => [:create, :update]
but I still receive the same error.
Validation Methods
class MyValidator < ActiveModel::Validator
def mystery_setup
#mystery_words = # This is a mystery, I can't tell you.
#mystery_c = #mystery_words.map(&:capitalize)
#mystery_u = #mystery_words.map(&:upcase)
#mysteries = #mystery_words + #mystery_c + #mystery_u
#new_mysteries = #mysteries.map{|mystery|mystery.tr("A-Za-z", "N-ZA-Mn-za-m")}
end
def validate (user)
mystery_setup
if Obscenity.profane?(user.name) \
|| #new_mysteries.any?{|mystery|user.name.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.email.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.password.include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end
User.rb(Model)
class User < ActiveRecord::Base
include ActiveModel::Validations
validates_with MyValidator
has_many :favorites, foreign_key: "user_id", dependent: :destroy
has_many :pictures, through: :favorites
has_secure_password
before_create :create_remember_token
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates_presence_of :name, :password, :email
validates_uniqueness_of :name, :email
validates :name, length: { in: 3..20 }
validates :password, length: { minimum: 6 }
validates :email, format: { with: VALID_EMAIL_REGEX }, length: { in: 8..50 }
validates_confirmation_of :password, if: lambda { |m| m.password.present? }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
I have also tried using an unless statement
def validate (user)
mystery_setup
unless User.all.include?(user)
if (Obscenity.profane?(user.name)
|| #new_mysteries.any {|mystery|user.name.include?(mystery)}) \
|| #new_mysteries.any?{|mystery|user.email.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.password.include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end
end
I tried testing if there was a user by using the unless statement but that didn't work either.
Following advice from a similar question here, I changed my migrations file to combat this area.
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, default: 'new'
t.string :password, default: 'new'
t.string :email, default: 'new'
t.timestamps
end
end
end
Question Link
undefined method `include?' for nil:NilClass with partial validation of wizard gem
Reference for Code
http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
Changing the migration file by changing the default values didn't solve this question so I decided to ask a new question here.
This method works for updating records but not for creating new records.
Help is appreciated. Thank you in advanced.
Edit
Just received an excellent suggestion to pass in the attributes in bracket format. My code now looks like this
def validate (user)
mystery_setup
unless User.all.include?(user)
if (Obscenity.profane?(user[:name]) ||
#new_mysteries.any?{|mystery|user[:name].include?(mystery)}) \
||#new_mysteries.any?{|mystery|user[:email].include?(mystery)}
||#new_mysteries.any?{|mystery|user[:password].include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end
Right now, it only has an error with the email and password attributes. If I delete the last two ||#new_mysteries.any? lines, my method works for filtering the name.
I would like to keep this professional though, so I'd like to get it to work with the other two methods. Possibly has to do with my use of parentheses or the || symbol?
Solid progress guys, keep it up.
Edit
Also, if I would like to call these validation methods on other classes, would it be better to put this in a helper file?
New Update
Here is my Users Controller code
class UsersController < ApplicationController
before_action :signed_in_user, only: [:edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Congratulations #{#user.name}! You have successfully created an account"
redirect_to games_path
else
render 'new'
end
end
def edit
end
def update
#user = User.find(params[:id])
end
def favorites
#user = User.find(current_user)
end
def destroy
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
You could write that like this:
def validate (user)
mystery_setup
user.errors[:name] << 'Tsk! Tsk! Please select a different username' if
Obscenity.profane?(user[:name]) ||
[:name, :email, :password].product(#new_mysteries).any? { |sym, mystery|
(str = user.public_send sym) && str.include?(mystery) }
end
Thanks to #Arup for the fix.
If you wished to reduce the number of instance variables, you could change the first line to:
new_mysteries = mystery_setup
and change #new_mysteries to new_mysteries.
|| #new_mysteries.any?{|mystery|user.name.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.email.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.password.include?(mystery)}
This error means that user name, email or password is nil. To deal with it you need to change each line to:
user.name && user.name.include?(mystery)
However highly recommend andand gem, which will allow you to write the above as:
user.name.andand.include?(mystery)
try this out:
def validate (user)
mystery_setup
unless User.all.include?(user)
if user.name && user.email && user.password
if (Obscenity.profane?(user.name)
|| #new_mysteries.any {|mystery|user.name.include?(mystery)}) \
|| #new_mysteries.any?{|mystery|user.email.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.password.include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end
end
end
class MyValidator < ActiveModel::Validator
def mystery_setup
mystery_words = # This is a mystery, I can't tell you.
mystery_c = mystery_words.map(&:capitalize)
mystery_u = mystery_words.map(&:upcase)
mysteries = mystery_words + mystery_c + mystery_u
mysteries.map{ |mystery| mystery.tr("A-Za-z", "N-ZA-Mn-za-m")}
end
def validate (user)
# No need to pollute the class with instance variables, just pass it back in a return
new_mysteries = mystery_setup
if Obscenity.profane?(user.name.to_s) ||
#new_mysteries.any?{ |mystery| user.name.to_s.include?(mystery) ||
user.email.to_s.include?(mystery) ||
user.password.to_s.include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end
I have refactored the code a bit, let me know if this works:
def validate (user)
mystery_setup
if Obscenity.profane?(user.name)
user.errors[:name] << 'Error: Please select a different username'
end
%w(name email password).each do |attr|
value = user.send(attr)
if value.present? and #new_mysteries.grep(/#{value}/).present?
user.errors[attr] << "Error: Please select a different user#{attr}"
end
end
end
You have an error in this part, the first line.
#mystery_words = # This is a mystery, I can't tell you.
#mystery_c = mystery_words.map(&:capitalize)
This should be
#mystery_words = [] # This is a mystery, I can't tell you.
#mystery_c = mystery_words.map(&:capitalize)
Since nil is the representation of atomic nothingness in Ruby, it never includes anything. The include? method can be simply defined on it as:
def nil.include? arg
false
end
I have this model:
class Campaign
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
field :subdomain, :type => String
field :intro, :type => String
field :body, :type => String
field :emails, :type => Array
end
Now I want to validate that each email in the emails array is formatted correctly. I read the Mongoid and ActiveModel::Validations documentation but I didn't find how to do this.
Can you show me a pointer?
You can define custom ArrayValidator. Place following in app/validators/array_validator.rb:
class ArrayValidator < ActiveModel::EachValidator
def validate_each(record, attribute, values)
Array(values).each do |value|
options.each do |key, args|
validator_options = { attributes: attribute }
validator_options.merge!(args) if args.is_a?(Hash)
next if value.nil? && validator_options[:allow_nil]
next if value.blank? && validator_options[:allow_blank]
validator_class_name = "#{key.to_s.camelize}Validator"
validator_class = begin
validator_class_name.constantize
rescue NameError
"ActiveModel::Validations::#{validator_class_name}".constantize
end
validator = validator_class.new(validator_options)
validator.validate_each(record, attribute, value)
end
end
end
end
You can use it like this in your models:
class User
include Mongoid::Document
field :tags, Array
validates :tags, array: { presence: true, inclusion: { in: %w{ ruby rails } }
end
It will validate each element from the array against every validator specified within array hash.
Milovan's answer got an upvote from me but the implementation has a few problems:
Flattening nested arrays changes behavior and hides invalid values.
nil field values are treated as [nil], which doesn't seem right.
The provided example, with presence: true will generate a NotImplementedError error because PresenceValidator does not implement validate_each.
Instantiating a new validator instance for every value in the array on every validation is rather inefficient.
The generated error messages do not show why element of the array is invalid, which creates a poor user experience.
Here is an updated enumerable and array validator that addresses all these issues. The code is included below for convenience.
# Validates the values of an Enumerable with other validators.
# Generates error messages that include the index and value of
# invalid elements.
#
# Example:
#
# validates :values, enum: { presence: true, inclusion: { in: %w{ big small } } }
#
class EnumValidator < ActiveModel::EachValidator
def initialize(options)
super
#validators = options.map do |(key, args)|
create_validator(key, args)
end
end
def validate_each(record, attribute, values)
helper = Helper.new(#validators, record, attribute)
Array.wrap(values).each do |value|
helper.validate(value)
end
end
private
class Helper
def initialize(validators, record, attribute)
#validators = validators
#record = record
#attribute = attribute
#count = -1
end
def validate(value)
#count += 1
#validators.each do |validator|
next if value.nil? && validator.options[:allow_nil]
next if value.blank? && validator.options[:allow_blank]
validate_with(validator, value)
end
end
def validate_with(validator, value)
before_errors = error_count
run_validator(validator, value)
if error_count > before_errors
prefix = "element #{#count} (#{value}) "
(before_errors...error_count).each do |pos|
error_messages[pos] = prefix + error_messages[pos]
end
end
end
def run_validator(validator, value)
validator.validate_each(#record, #attribute, value)
rescue NotImplementedError
validator.validate(#record)
end
def error_messages
#record.errors.messages[#attribute]
end
def error_count
error_messages ? error_messages.length : 0
end
end
def create_validator(key, args)
opts = {attributes: attributes}
opts.merge!(args) if args.kind_of?(Hash)
validator_class(key).new(opts).tap do |validator|
validator.check_validity!
end
end
def validator_class(key)
validator_class_name = "#{key.to_s.camelize}Validator"
validator_class_name.constantize
rescue NameError
"ActiveModel::Validations::#{validator_class_name}".constantize
end
end
You'll probably want to define your own custom validator for the emails field.
So you'll add after your class definition,
validate :validate_emails
def validate_emails
invalid_emails = self.emails.map{ |email| email.match(/^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) }.select{ |e| e != nil }
errors.add(:emails, 'invalid email address') unless invalid_emails.empty?
end
The regex itself may not be perfect, but this is the basic idea. You can check out the rails guide as follows:
http://guides.rubyonrails.org/v2.3.8/activerecord_validations_callbacks.html#creating-custom-validation-methods
Found myself trying to solve this problem just now. I've modified Tim O's answer slightly to come up with the following, which provides cleaner output and more information to the errors object that you can then display to the user in the view.
validate :validate_emails
def validate_emails
emails.each do |email|
unless email.match(/^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
errors.add(:emails, "#{email} is not a valid email address.")
end
end
end
Here's an example that might help out of the rails api docs: http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates
The power of the validates method comes when using custom validators and default validators in one call for a given attribute e.g.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
end
end
class Person
include ActiveModel::Validations
attr_accessor :name, :email
validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
validates :email, :presence => true, :email => true
end