Rails 3 validates_uniquess_of virtual field with scope - ruby-on-rails-3

I am working on a Rails 3 app that is needing to run a validation on a virtual field to see if the record already exists... here is my model code:
#mass-assignment
attr_accessible :first_name, :last_name, :dob, :gender
#validations
validates_presence_of :first_name, :last_name, :gender, :dob
validates :fullname, :uniqueness => { :scope => [:dob] }
def fullname
"#{first_name} #{last_name}"
end
I am not getting any errors, its passing the validation.

I don't think you can do something like that with standard validates. Reason is, it usually is a "simple" sql look-up for uniqueness. But to check uniqueness of what's sent back from your method, it'd have to :
Get all entries of your scope
For each, execute the fullname method.
Add every result in an array while testing for uniqueness
Simple when the data set is small, but then it'd just take ages to do as soon as you reach 10K plus matches in your scope.
I'd personally do this with a standard before_save using the dob scope
before_save :check_full_name_uniqueness
def check_full_name_uniqueness
query_id = id || 0
return !Patient.where("first_name = ? and last_name = ? and dob = ? and id != ?", first_name, last_name, dob, query_id).exists?
end
Add error message (before the return):
errors.add(:fullname, 'Your error')
Back in the controller, get the error :
your_object.errors.each do |k, v| #k is the key of the hash, here fullname, and v the error message
#Do whatever you have to do
end

Related

Rails 3.2 - Validate in Model Based on Other Model Criteria

I have an AWARD model - there are two forms to create an AWARD. One is for nominating EMPLOYEES, the other is for Non-Employees. The EMPLOYEE form pulls a list of active employees to populate the Nominee selection box. The Non-Employee form has only text fields to populate the Nominee field (because I have no source to populate a selection list).
To dummy-proof the app, I want to run a validation that disallows Employees from using the Non-Employee form (because they will inevitably try to do so!). There is a hidden field on each form to set whether the form is Employee or Non: <%= f.hidden_field :employee, :value => true/false %>
So, on the Non-Employee form, if the user types in a nominee_username that exists in the Employee table, it should throw an error and direct them to the Employee form.
Here's what I've attempted:
class Award < ActiveRecord::Base
belongs_to :nominator, :class_name => 'Employee', :foreign_key => 'nominator_id'
belongs_to :nominee, :class_name => 'Employee', :foreign_key => 'nominee_id'
validate :employee_using_non_employee_form,
:on => :create, :unless => :employee_nomination?
def employee_nomination?
self.employee == true
end
def employee_using_non_employee_form
if nominee_username == employee.username ## -- this is where I'm getting errors. I get "undefined local variable or method employee for #<Award:.."
## I've also tried Employee.username, but get "undefined method username for #<Class..."
## Same error when I try nominee.username
errors.add(:nominator, "Please use Employee form.")
end
end
end
There is an association between the Award and Employee models, but I don't know how to call the Employee.username within the Award model to validate the Non-Employee form.
class Employee < ActiveRecord::Base
has_many :awards, :foreign_key => 'nominator_id'
has_many :awards, :foreign_key => 'nominee_id'
end
Try this for your validation method.
def employee_using_non_employee_form
if Employee.where(:username => nominee_username).present?
errors.add(:nominator, "Please use Employee form.")
end
end

Rails 3 Validation on Uniqueness - logic

Given the following invoice model:
validates :po_number, :invoice_number, :invoice_date, :date_received, :state_id, :division_id, :pending_state_id, :approver_username, :approver_email, :presence => true
validates :po_number, :uniqueness => {:scope => :invoice_number}
There are times when an invoice record is canceled (state_id = 4), but then needs to be re-created.
Can you help me with how to still validate uniqueness on po_number and invoice_number so the new record can be created even though the same combination exists with a different state_id if it was canceled?
Based on what you described, I believe it may be sufficient to include the :state_id in the scope and pass an :unless (or :if) option to exclude cancelled (or any other states) from the check:
validates :po_number, :uniqueness => {
:scope => [:invoice_number, :state_id],
:unless => :cancelled?
}
# assumes an instance method like
def cancelled?
state_id == 4
end
I think you'll need a custom validator. Something like this might work:
validate :unique_po_number
def unique_po_number
errors.add(:po_number, 'must be unique') if self.class.where('state != 4').where(:po_number => po_number).count > 0
end

How can I map between strings and attributes automatically?

I have a tiny logical error in my code somewhere and I can't figure out exactly what the problem is. Let's start from the beginning. I have the following extension that my order class uses.
class ActiveRecord::Base
def self.has_statuses(*status_names)
validates :status,
:presence => true,
:inclusion => { :in => status_names}
status_names.each do |status_name|
scope "all_#{status_name}", where(status: status_name)
end
status_names.each do |status_name|
define_method "#{status_name}?" do
status == status_name
end
end
end
end
This works great for the queries and initial setting of "statuses".
require "#{Rails.root}/lib/active_record_extensions"
class Order < ActiveRecord::Base
has_statuses :created, :in_progress, :approved, :rejected, :shipped
after_initialize :init
attr_accessible :store_id, :user_id, :order_reference, :sales_person
private
def init
if new_record?
self.status = :created
end
end
end
Now I set a status initially and that works great. No problems at all and I can save my new order as expected. Updating the order on the other hand is not working. I get a message saying:
"Status is not included in the list"
When I check it seems that order.status == 'created' and it's trying to match against :created. I tried setting the has_statuses 'created', 'in_progress' etc but couldn't get some of the other things to work.
Anyway to automatically map between string/attribute?
from your description, looks like you're comparing a string to a symbol. Probably need to add:
define_method "#{status_name}=" do
self.status = status_name.to_sym
end
or do a #to_s on the status_names

Rails3 - Validate a unique pair of indexes

I created an index in my migrations:
add_index "enrollments", ["student_id", "lecture_id"], :name => "index_enrollments_on_student_id_and_lecture_id", :unique => true
How is it possible to validate this pair of keys in Rails? I tried:
validates_uniqueness_of :enrollment_id, :scope => [:student_id, :lecture_id]
But it doesn't work properly.
Also, I need to find out in a view, if this key already exists, or if it is possible to create a new entry.
class Enrollment < ActiveRecord::Base
validates_uniqueness_of :student_id, :scope => :lecture_id
end
If you want to determine in the view before submitting new enrollment that this pair exists then you can use ajax request (I prefer RJS with JQuery) and check it with:
class Enrollment < ActiveRecord::Base
def self.pair_exists?(student_id, lecture_id)
Enrollment.find_all_by_student_id_and_lecture_id(student_id, lecture_id).any?
end
end
Hope this will help you.
Sorry for opening an old thread but since it's 2011 and I still couldn't find a proper validator, I created one myself:
class UniqueSetValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
setup record
record.errors[attribute] << "- collection of fields [" + #fields + "] is not unique" if record.class.count(:conditions => #conditions) > 0
end
def check_validity!
raise ArgumentError, "Must contain an array of field names' symbols" unless options[:in] && options[:in].respond_to?(:each)
end
private
def setup record
conditions = []
fields = []
options[:in].each do |field|
conditions |= [ field.to_s + " = '" + record[field].to_s + "'" ]
fields |= [ field.to_s ]
end
#conditions = conditions.join(" AND ")
#fields = fields.join(", ")
end
end
It seems to work to me. To use it paste the code into:
your_rails_app/lib/unique_set_validator.rb
and enable it in:
your_rails_app/config/application.rb
by adding this line:
config.autoload_paths += %W( #{config.root}/lib )
Then you can simply use it in your model:
validates :field, :unique_set => [ :field, :field2 ]
It will validate the uniqueness of pair [ :field, :field2 ], and any error would be returned to :field. I haven't tried but it should work for more that 2 fields.
I hope that I didn't messed up anything, and that this will help someone. :)
Try this!
validates_uniqueness_of :enrollment_id, student_id, :scope => [:student_id, :lecture_id], :lecture_id], :message => "combination of enrollment, student and lecture should be unique."
If combination of student and lecture is not unique than you will get message in your error messages.
Update:
validates_uniqueness_of :student_id, :scope => [:lecture_id], :message => "combination of student and lecture should be unique."
The way voldy define in answer that's the correct way.

Why doesn't this test in the Diaspora app fail?

From http://github.com/diaspora/diaspora/blob/master/spec/models/profile_spec.rb
describe Profile do
before do
#person = Factory.build(:person)
end
describe 'requirements' do
it "should include a first name" do
#person.profile = Factory.build(:profile,:first_name => nil)
#person.profile.valid?.should be false
#person.profile.first_name = "Bob"
#person.profile.valid?.should be true
end
end
end
But in http://github.com/diaspora/diaspora/blob/master/app/models/profile.rb is validated the presense of both, the first and last name like so validates_presence_of :first_name, :last_name
Why does the above test pass even though a last name isn't specified?
last_name is actually specified. The profile is create using the Factory.build, which returns the predefined mock of :profile, which is
Factory.define :profile do |p|
p.first_name "Robert"
p.last_name "Grimm"
end
I suspect the Factory.build(:profile, ...) call creates a profile model with a default first_name and last_name set, unless specified otherwise (by the :first_name => nil in this example).
However that's just an educated guess which I am inferring from the code above and what I see here.