Devise validate email property with exclusion of words and #myapp.com - ruby-on-rails

Trying to validate the email field in a rails devise app with a word exclusion.
validates :email, :exclusion => {:in => ["admin", "root",,
:message => "is reserved"}
Wich works great on the :username field but not on the email.
I suspect that Devise is "taken over" the validation of the email field and I need to super/ overrule the registrations controller of Devise.
How could I:
Prevent emails with words based on an :exclusion
Prevent emails with #myapp.com so users don't use the domain the app runs on.

You can always write custom validations to achieve what you want. Example from the guide:
The easiest way to add custom validators for validating individual
attributes is with the convenient ActiveModel::EachValidator. In this
case, the custom validator class must implement a validate_each method
which takes three arguments: record, attribute and value which
correspond to the instance, the attribute to be validated and the
value of the attribute in the passed instance.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, :presence => true, :email => true
end

After checking if it is an e-mail, I would split the two validations. One for exclusion, and other for domain. You can retrieve the current domain when using passenger.
validate :email_username_is_not_on_blacklist, :email_domain_is_allowed
EXCLUDED_USERNAMES = "admin", "root"
EXCLUDED_DOMAINS = "myapp.com"
def email_username_is_not_on_blacklist
user, domain = email.split("#")
errors.add(:email, "E-mail user is not allowed") if self.class.EXCLUDED_USERNAMES.include?(user)
end
def email_domain_is_allowed
user, domain = email.split("#")
errors.add(:email, "E-mail domain is not allowed") if self.class.EXCLUDED_DOMAINS.include?(domain)
end

Related

Use value in override error message in Rails 4 for custom Validator

I am on Rails 4.2 I have written a custom Validator which will check if a value being entered exists in another table. I have been reviewing some other posts and it seems there is either a context specific or rails version preferred way to reuse the value being validated. In the rails docs i see examples such as:
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
however, if I try to use %{value} in my custom message override it does not interpolate, but just prints "%{value}". I have seen various ways of calling "value". I also could not get %{value} to work in my Validator definition, but could get #{value} to work (New to ruby, if#{value} getting it from validate_each?).
I have also been struggling with various formats of the validation statement and putting in the custom message. Some things which look repeatable from the docs are not. If the way I am declaring my custom message is causing the error, please let me know how to correct?
class ExistingGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(:code => value).any?
record.errors[attribute] << (options[:message] || "#{value} is not a valid
group code")
end
end
end
class Example < ActiveRecord::Base
validates :group_code, presence: true
validates :group_code, :existing_group => {:message => "The code you have enterd ( **what goes here?** ) is not a valid code, please check with your teacher or group
leader for the correct code." }
end
Rails automatically puts the value at the beginning of the phrase, so you can just do this:
class ExistingGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(code: value).any?
record.errors[attribute] << (options[:message] || 'is not a valid group code')
end
end
end
class Example < ActiveRecord::Base
validates :group_code, presence: true
validates :group_code, existing_group: {message: 'is not a valid code, please check with your teacher or group leader for the correct code.' }
end
Separately, note that it is always "#{1+1}" to interpolate not %.
Rails is using Internationalization style string interpolation to add the value to the message. You can use the I18n.interpolate method to accomplish this. Something like this should do the trick:
class ExistingGroupValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless Group.where(:code => value).any?
record.errors[attribute] << (I18n.interpolate(options[:message], {value: value}) || "is not a valid group code")
end
end
end
class Example < ActiveRecord::Base
validates :group_code, presence: true
validates :group_code, :existing_group => {:message => "The code you have entered, "%{value}", is not a valid code, please check with your teacher or group leader for the correct code." }
end

rails model validattion ( not containing a substring)

Is there a simple way to validate that a fields not containing a sub string. I would like a email field not containing 'yahoo.com'. If it contains 'yahoo.com' I would lile to return an error messaage.
no need to add a custom validator just use Rails validates_format_for
validates_format_of :email, without: /yahoo\.com/i, message: "your custom message"
see documentation
http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_format_of
As the comment suggests - build a custom validator
require 'active_model'
class NotYahooEmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
if value.include? "yahoo.com"
object.errors[attribute] << (options[:message] || "is a yahoo.com email address")
end
end
end
You would use this as follows:
class User < ActiveRecord::Base
validates :email, :presence => true, :not_yahoo_email => true
end

Custom Validations Rails Model

I have a contact form and would like to show individual messages depending on what has failed.I would like to use flash messages. So from what i have read so far i can create a custom method (or i think it just overrides the one in place?)
So for example, i want to validate the presence of the name field
Class Message
attr_accessor :name
validates :name, :presence => true
def validate
if self.name < 0
errors.add(:name, "Name cannot be blank")
end
end
end
Within my controller i normally use a generic message
flash.now.alert = "Please Ensure all Fields are filled in"
Is there a way to call the particular message that failed validation?
Thanks
There is a plugin available, u can follow the below url
https://github.com/jeremydurham/custom-err-msg
Check the method validates because you can pass a message argument with the desired message.
validates :name, :presence => {:message => 'The name can't be blank.'}

rails 3, set a value on an object if it fails validation

I have an object with multiple validations.
gist of the Approval model: https://gist.github.com/1579150 (side note, I know the Email Domain Validor doesn't work...)
The point is, if these validations fail, I want the object to save, but then set a value on approval.issue = true. Approval.issue is a boolean field that defaults to false, but then if the object fails validations I want the system admin to be able to see it and then handle it appropriately.
To make it more idiot proof, it would be nice to have some validations that can force the user to make changes, but then some would be exempt and would simply trigger the .issue field to true.
For instance, if the email is of the right domain but the email doesn't exist in the system, it would save it but then set issue => true. I could then set up a simple view for Approvals where :issue => :true. then the admin could modify or delete bad Approvals.
Ideas?
Code from gist:
class Approval < ActiveRecord::Base
class ApproverEmailValidator < ActiveModel::EachValidator
def validate_each(approval, attribute, value)
approval.errors[attribute] << "must be a valid e-mail address in our system" unless is_valid_email?(value)
end
protected
def is_valid_email?(address)
User.find_by_email(address)
end
end # End Approver Validator
class EmailDomainValidator < ActiveModel::EachValidator
def email_domain_is?(domain)
unless /ravennainteractive.com$/ =~ email(domain)
errors.add(:email, "You must Use an Eddie Bauer email address")
end
end
end #End Email Domain Validator
belongs_to :recommendation
attr_accessible :approval, :email, :user_id
validates :email, :email_domain
validates :next_approver_email, :approver_email => { :if => :recently_approved? }
before_save :create_next_approval
after_create :approval_notification
attr_accessor :next_approver_email
def recently_approved?
self.approved_changed? && self.approved?
end
def create_next_approval
next_approval = self.recommendation.approvals.build(:email => self.next_approver_email, :user_id => User.find_by_email(next_approver_email))
next_approval.save if next_approver_email.present? && recently_approved?
end
def email_domain_is?
unless /ravennainteractive.com$/ =~ email
errors.add(:email, "You must Use an Eddie Bauer email address")
end
end
private
def approval_notification
ApprovalMailer.needs_approval(self).deliver
end
end
You can implement observer for Approval that will analyze you objects before saving and set issue to "true", if there is some suspicious input.
UPDATE: Here is short guide how to implement observer:
rails generate observer - after this step you`ll see _observer.rb file.
Implement needed methods. Here is simple example extracted from one of my projects (It seems like you should use "before_save" method):
class HomeworkObserver < ActiveRecord::Observer
def after_create(homework)
TeacherMailer.send_later(:student_submitted_homework, homework)
end
def after_save(homework)
if (homework.checked)
StudentMailer.send_later(:teacher_checked_homework, homework)
end
end
end
Also you need to enable observer by adding it to your config/application.rb, e.g:
config.active_record.observers = :homework_observer
Official docs: http://api.rubyonrails.org/classes/ActiveRecord/Observer.html

validate and update single attribute rails

I have the following in my user model
attr_accessible :avatar, :email
validates_presence_of :email
has_attached_file :avatar # paperclip
validates_attachment_size :avatar,
:less_than => 1.megabyte,
:message => 'Image cannot be larger than 1MB in size',
:if => Proc.new { |imports| !imports.avatar_file_name.blank? }
in one of my controllers, I ONLY want to update and validate the avatar field without updating and validating email.
How can I do this?
for example (this won't work)
if #user.update_attributes(params[:user])
# do something...
end
I also tried with update_attribute('avatar', params[:user][:avatar]), but that would skip the validations for avatar field as well.
You could validate the attribute by hand and use update_attribute, that skips validation. If you add this to your User:
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
if mock.valid?
true
else
!mock.errors.has_key?(attr)
end
end
And then update the attribute thusly:
if(!User.valid_attribute?('avatar', params[:user][:avatar])
# Complain or whatever.
end
#user.update_attribute('avatar', params[:user][:avatar])
You should get your single attribute updated while only (manually) validating that attribute.
If you look at how Milan Novota's valid_attribute? works, you'll see that it performs the validations and then checks to see if the specific attr had issues; it doesn't matter if any of the other validations failed as valid_attribute? only looks at the validation failures for the attribute that you're interested in.
If you're going to be doing a lot of this stuff then you could add a method to User:
def update_just_this_one(attr, value)
raise "Bad #{attr}" if(!User.valid_attribute?(attr, value))
self.update_attribute(attr, value)
end
and use that to update your single attribute.
A condition?
validates_presence_of :email, :if => :email_changed?
Have you tried putting a condition on the validates_presence_of :email ?
http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000083
Configuration options:
if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.
I am assuming you need this, because you have a multi-step wizard, where you first upload the avatar and the e-mail is filled in later.
To my knowledge, with your validations as they are, I see no good working solution. Either you validate all, or you update the avatar without validations. If it would be a simple attribute, you could check if the new value passes the validation seperately, and then update the model without validations (e.g. using update_attribute).
I can suggest two possible alternative approaches:
either you make sure that the e-mail is always entered first, which I believe is not a bad solution. And then, with each save, the validation is met.
otherwise, change the validation. Why would you declare a validation on a model, if there are records in the database that do not meet the validation? That is very counter-intuitive.
So I would propose something like this:
validate :presence_of_email_after_upload_avatar
def presence_of_email_after_upload_avatar
# write some test, when the email should be present
if avatar.present?
errors.add(:email, "Email is required") unless email.present?
end
end
Hope this helps.
Here is my solution.
It keeps the same behaviour than .valid? method, witch returns true or false, and add errors on the model on witch it was called.
class MyModel < ActiveRecord::Base
def valid_attributes?(attributes)
mock = self.class.new(self.attributes)
mock.valid?
mock.errors.to_hash.select { |attribute| attributes.include? attribute }.each do |error_key, error_messages|
error_messages.each do |error_message|
self.errors.add(error_key, error_message)
end
end
self.errors.to_hash.empty?
end
end
> my_model.valid_attributes? [:first_name, :email] # => returns true if first_name and email is valid, returns false if at least one is not valid
> my_modal.errors.messages # => now contain errors of the previous validation
{'first_name' => ["can't be blank"]}

Resources