Passing a defined method unless an attribute is present? - ruby-on-rails

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.

Related

Validate Attribute without saving

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

Rails validate :if with checkboxes

I am working with a form having 2 checkboxes: option_one and option_two.
I don't want to allow submission of the form if option_two is checked and option_one is not.
In other words if somebody checks option_two, they must check option_one as well.
So in my MyModel I wrote :
validates :option_one, :presence => true, :if => option_two_active?, :message => "Dummy message."
Then in the MyController, I added :
def option_two_active?
params[:option_two] == "1"
end
But it keeps giving me the following error :
NoMethodError in MyController#index
Is my approach correct ? How can I achieve this ? Thanks in advance.
You have to specify the conditional method with a symbol:
validates :option_one, :presence => true, :if => :option_two_active?, :message => "Dummy message."
Also, you since you can't use params from a model, you should assign that value to the model from the controller, either with create, update_attributes, or manually. If you want to persist the option_two field, then it should be a database column, else you can just create an attribute accessor:
attribute_accessor :option_two
The way you reference the method, it will be called directly as the class is first loaded. However, the :if parameter is expected to be used with either a proc which is then called during validation or with a symbol representing a method name. In your case, you should thus setup your validation like this:
validates :option_one, :presence => true, :if => :option_two?, :message => "Dummy message."
Notice the colon before the method name. Furthermor, the validation method needs to be defined on the model, not the controller. Fortunately, ActiveRecord already defines the proper methods for Boolean fields, as used here.

Validating a existence of a beta code before creating a User

model: User
has_one :beta_invite
before_save :beta_code_must_exist
def beta_code_must_exist
if beta_invite_id == beta_invite.find_by_name(beta_invite.id)
user
else
nil
end
end
model: BetaInvite
has_many :users
What I`m trying to do is check for the existence of a beta invite in DB, before allowing the user to be saved.
Since the User will be passing in the BetaInvite name into the field, I would like to check if it matches any existing Codes in the DB.
Hope I didn`t mix things up too much.
Would appreciate any help with this problem.
Add a text field to the form for :beta_code
Add an attr_accessor for that field: attr_accessor :beta_code
Then add the following line to the model (Assumes you only want to do this check on user creation):
validate :beta_code_must_exist, :on => :create
Change beta_code_must_exist to add an error to the form. Also be sure to properly cast :beta_code into the correct type.
Warning untested code below
def beta_code_must_exist
#invite = BetaInvite.find_by_name(beta_code)
if #invite.empty?
errors.add(:beta_code, "is not a valid invite code")
else
beta_invite_id = #invite.id
end
end
Use :inclusion with the :in option. You can supply :in with any enumerable:
validates :beta_invite, :inclusion => { :in => BetaInvite.all,
:message => "%{value} is not a valid beta invite code" }
Source: Rails Active Record Validation

Rails validating that password_confirmation is present when password is also present or has been changed

I've got the following User model:
class User < ActiveRecord::Base
# Users table has the necessary password_digest field
has_secure_password
attr_accessible :login_name, :password, :password_confirmation
validates :login_name, :presence=>true, :uniqueness=>true
# I run this validation on :create so that user
# can edit login_name without having to enter password
validates :password,:presence=>true,:length=>{:minimum=>6},:on=>:create
# this should only run if the password has changed
validates :password_confirmation,
:presence=>true, :if => :password_digest_changed?
end
These validations don't quite do what I was hoping they would. It's possible to do the following:
# rails console
u = User.new :login_name=>"stephen"
u.valid?
# => false
u.errors
# => :password_digest=>["can't be blank"],
# => :password=>["can't be blank", "please enter at least 6 characters"]}
# so far so good, let's give it a password and a valid confirmation
u.password="password"
u.password_confirmation="password"
# at this point the record is valid and does save
u.save
# => true
# but we can now submit a blank password from console
u.password=""
# => true
u.password_confirmation=""
# => true
u.save
# => true
# oh noes
So what I want is the following:
password required on create, must be 6 chars in long
password_confirmation required on create, must match password
user should not have to submit password when updating login_name
password can not be deleted on update
Something which is confusing me is that rails throws a no method error if I use password_changed? as opposed to :password_digest_changed? in my password_confirmation validation. I don't understand why.
So does anyone know what I'm doing wrong here?
password isn't a column in the database, right? Just an attribute?
So there is no password_changed? method, which would be available if password were a column. Rather you should just check to see if password is set at all.
Something like:
validates :password_confirmation, :presence => true, :if => '!password.nil?'
Although that solves the initial problem you were having, it still won't quite do what you want, as it's only checking presence, and you need it to be present and match password. Something like the following should work (in combination with the above validation).
validates :password,
# you only need presence on create
:presence => { :on => :create },
# allow_nil for length (presence will handle it on create)
:length => { :minimum => 6, :allow_nil => true },
# and use confirmation to ensure they always match
:confirmation => true
If you've never seen :confirmation before, it's a standard validation that looks for foo and foo_confirmation and makes sure they're the same.
Note that you still need to check for the presence of password_confirmation

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