In Rails 3, I would like to see if a value for an attribute is valid using the model's validates options without trying to save or create.
I'm writing the back end of a AJAX API, that should check a username against the
validates :username, :length => {:minimum => 2, :maximum => 50}, :exclusion => {:in => RESERVED_USERNAMES}, :format => MY_REGEX, .etc
In the User model. This it to create a little tick or cross next to the username field in the register form, so the user doesn't have to wait to see if the username is taken or not.
I could just compare it to a regex, but to try to keep my code DRY, I thought it would be better to use the validation in the user model.
Anyone know how I could do something of the line of:
username = params[:username]
if User.not_found(:username => username) && User.validate(:username => username)
#yay!
else
#nope
end
(I already have the not_found working).
You could try checking for specific errors related to the username, in addition to running all validations (you need to in order to get the error messages).
#user = User.new(params[:user])
if #user.invalid? && #user.errors[:username].any?
# yay!
else
# nope
end
You can run that without persisting your user to the database, since none of the methods used (including #new and #valid?) actually save the object.
You can create new user with username param and then inspect errors:
#user = User.new( :username => params[:username] )
#user.valid?
if #user.errors.include? :username
# username error
end
Related
I have two registration forms: one that can register with any email address and the other with just one specific domain. How to allow that from the User.rb model?
Something like this may work:
validates_format_of :email, :with => /.io/
But because I have two registration forms and the url for that form has an id of 2:
validates_format_of :email, :with => /.io/ if params[id] == 2
I do understand that params is not available in the model but based on what I want to achieve, how to accomplish this?
Basically form with id = 1 can register with any email. Form with id = 2 can only register with a .io email address (domain).
This sounds like a good use for validation contexts. A validates* method can accept an :on option, which is the name of the "context" in which the validation will be triggered; for example:
validates_format_of :email, :with => /\.io\z/, on: :restricted_email
This validation will only be triggered if the option context: :restricted_email is passed to the save or save! method. Here's how you might use it in your controller:
def create
#user = User.new(params[:user])
if params[:id] == 2
#user.save!(context: :restricted_email)
else
#user.save!
end
end
Here's a good blog post on the topic: http://blog.arkency.com/2014/04/mastering-rails-validations-contexts/
You could add an attribute to the model that would indicate which form registration was coming from, like :registered_io, sent to your model through a hidden field in the form
#...
<%= f.hidden_field :registered_io, value="true" # or false %>
Then in your model you could just do an
validates :format_of_io_email_should if self.registered_io
validates :format_of_universal_email_should if !self.registered_io
def format_of_io_email_should
# regex ahoy or whatever
end
def format_of_universal_email_should
# same same but different
end
Don't forget to run your migrations to store the attribute, too!
$ rails migration add_column_registered_io_to_user registered_io:boolean
Also don't forget to allow :registered_io in the strong params in the controller.
Having newbie trouble getting this working. I have Stores that don't have addresses (just a website) as well so the gem (Google-Maps-for-Rails) when seeding actually doesn't create them at all but only the ones with an address.
Store.rb
validates :address,
:presence => {:unless => :website,
:message => "You must enter an address, website, or both."}
acts_as_gmappable :check_process => :prevent_geocoding,
:address => "address",
:normalized_address => "address",
:msg => "Sorry, unable to find address."
# How do I correct this block?
def prevent_geocoding
unless website.present?
address.blank? || (!latitude.blank? && !longitude.blank?)
end
end
I still want to use everything here but what's the correct way to pass this block?
Thank you.
You're on the right track. You can bypass validations by passing a method into if or unless as options on the validation. In the above code, you're passing it as an option to the presence validator and not to the validation itself. Move the unless out of the hash and pass it the name of a method or a Proc — really anything that returns true or false. Here's an example:
validates :address,
:presence => { :message => "You must enter an address, website, or both." },
:unless => Proc.new { |store| store.address.nil? && store.website.present? }
That validation will run every time except in cases where the store both doesn't have an address and does have a website. If you need more complex logic, I recommend moving that out of a Proc and into a method.
I've got a User model with three fields, :email, :display_name and :handle. Handle is created behind the scenes from the :display_name.
I'm using the following validations:
validates :display_name, :presence => :true, :uniqueness => { :message => "Sorry, another user has already chosen that name."}, :on => :update
validates :email, :presence => :true, :uniqueness => { :message => "An account with that email already exists." }
I use the handle as the to_param in the model. If the user fails the validation by submitting a :display_name that already exists, then tries to change it and resubmit the form, Rails seems to use the new handle as the validation for the email -- in other words, it assumes that the email doesn't belong to the current user and validation on the email then fails. At this point, Rails assumes that the changed display name/handle is the one to use for the look up and the update action can't complete at all, because it can't find the user based on the new handle.
Here's the update method:
def update
#user = User.find_by_handle(params[:id])
#handle = params[:user][:display_name]
#user.handle = #handle.parameterize
...
end
This problem doesn't happen when the validation first fails on a duplicate email, so I'm assuming it's something about the way I've written the update method -- maybe I should try setting the handle in the model?
maybe I should try setting the handle in the model?
^ This.
The controller isn't the place to do something like this. If it's model logic that's happening behind the scenes, beyond the user's control, why put it in controller code?
Do it instead in a before_save filter, which is guaranteed to run only after the chosen display name is determined to be available and the record is deemed valid. In this way the handle won't be changed on the cached record until it is actually committed to the db, eliminating the problem of the incorrectly generated URL.
before_save :generate_handle
...
def generate_handle
self.handle = display_name.parameterize
end
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"]}
I'm using authlogic with rails 3. I have this in my user model:
validates :last_name, :presence => true
acts_as_authentic do |c|
c.validates_length_of_password_field_options = {:minimum => 7}
end
And then I have a controller action that updates the user's name:
def update_name
if #current_user.update_attributes(params[:user])
flash[:success_name] = true
redirect_to edit_user_via_account_settings_path
else
render 'edit_user_via_account_settings'
end
end
If the user enters a blank last name and attempts to update their name with this controller action, the #current_user model correctly has errors on last name, but it also has errors on password (password must be a minimum of 7 chars). How can I only validate the password if the password is being updated?
You need to use the merge_validates_* config methods instead of the validates_* methods. The former keeps the conditionals (like ignore blank password) and the latter overwrites them. That should clear everything up. And don't use the assignment on the merge_* methods.
a.merge_validates_length_of_password_field_options :minimum => 7
I think the config you are looking for is found here
http://rdoc.info/github/binarylogic/authlogic/master/Authlogic/ActsAsAuthentic/Password/Config#ignore_blank_passwords-instance_method
Unless its a new user, if no password is supplied it does not validate the password.