Rails 3 validating multiple fields if unique field value exists - ruby-on-rails

I have an Assignment model which has the following fields: :user_id, :company_id, :role_id
Each user can have multiple roles within a company but each company can only have one user with role_id == 5.
I would like to do something like below (obviously this does not work).
validates :company_id, :uniqueness => { :scope => :role_id => {:is => 5}, :message => "Only one owner is allowed." }
Is there a way to achieve this in a similar format to the above or will I need to create a custom validation macros?
If a custom validation is required how exactly would this be done and where is the 'correct' place to store this code if a new class needs to be created that inherits from ActiveModel::EachValidator? (Do I keep it in the same file or create a new file and possibly save it in /lib?)
Thanks in advance.

validates_uniqueness_of :user_id, :scope => :company_id, :if => Proc.new{|user| user.role_id == 5 }

Ok, I've come up with a solution but not quite sure if it's the 'rails way' of doing this or if it's just a hack-y workaround. Can someone please correct me on this?
I have done the following in my Association.rb file:
validate :owner_exists
.
..
...
protected
def owner_exists
errors.add(:role_id, " of owner already exists") if (role_id == 5 && Assignment.where('company_id = ? AND role_id = 5', company_id ).exists?)
end

Related

Performing a function to check for duplicates before saving the record in the model in Rails

This is what I have in my model. I want to check for duplicate records and I cant just use a unique index for a field since the contacts are unique per job) I am unable to do this using the following code. Am I doing it wrong? Any help is appreciated. Thanks!
before_save :check_for_duplicates?
def check_for_duplicates?
JobsPeople.find(:all, :conditions => ["job_id = ? and contact_id = ?", self.job_id, self.contact_id])
end
Callbacks don't do anything more than run the code you tell them to run. If you want to prevent something from executing you have two options, use a validation or raise an error.
In your current code you could check if that query you ran returns anything, and then raise an error if it does. The error will bubble up to the transaction that is wrapping the save and trigger a rollback, then propagate the error into your code where you can handle it if you want.
before_save :check_for_duplicates?
def check_for_duplicates?
if JobsPeople.find(:all, :conditions => ["job_id = ? and contact_id = ?", self.job_id, self.contact_id]).any?
raise 'some error'
end
end
Although there is already a built-in uniqueness validator that handles this situation
validates :job_id, :uniquness => {:scope => :contact_id, :message => 'must be unique per contact}
Of course this should still be backed up with a composite unique index in the database as there is a potential race condition, the likelihood of which depends largely on the behavior of your application.
# in a migration
add_index :table_name, [:job_id, :contact_id], :unique => true, :name => 'index_job_id_and_contact_id_on_table_name'
I believe this should do what you want
class JobsPeople
validates_uniqueness_of :contact_id, scope: :job_id
end

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

Passing a defined method unless an attribute is present?

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.

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"]}

Validate only when

I need to validate a value's presence, but only AFTER the value is populated. When a User is created, it is not required to set a shortcut_url. However, once the user decides to pick a shorcut_url, they cannot remove it, it must be unique, it must exist.
If I use validates_presence_of, since the shortcut_url is not defined, the User isn't created. If I use :allowblank => true, Users can then have "" as a shortcut_url, which doesn't follow the logic of the site.
Any help would be greatly appreciated.
Here we are always making sure the shortcut_url is unique, but we only make sure it is present if the attribute shortcut_selected is set (or if it was set and now was changed)
class Account
validates_uniqueness_of :shortcut_url
with_options :if => lambda { |o| !o.new_record? or o.shortcut_changed? } do |on_required|
on_required.validates_presence_of :shortcut_url
end
end
You'll need to test to make sure this works well with new records.
Try the :allow_nil option instead of :allow_blank. That'll prevent empty strings from validating.
Edit: Is an empty string being assigned to the shortcut_url when the user is being created, then? Maybe try:
class User < ActiveRecord::Base
validates_presence_of :shortcut_url, :allow_nil => true
def shortcut_url=(value)
super(value.presence)
end
end
try conditional validations, something like:
validates_presence_of :shortcut_url, :if => :shortcut_url_already_exists?
validates_uniqueness_of :shortcut_url, :if => :shortcut_url_already_exists?
def shortcut_url_already_exists?
#shortcut_url_already_exists ||= User.find(self.id).shortcut_url.present?
end

Resources