Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Rails, How do I validates_uniqueness_of :field with a scope of last 6 months

First Item
I Want to validate a field to make sure it is unique (in the last 6 months) before saving it to the database.

I am thinking I should use validates_uniqueness_of :field, case_sensitive => false, Scope => ...

For my application it only has to be unique if, it was used <6 months ago.

Thinking to compare it to created_at, but don't really know how to go about it.

Second Item
I think I should somehow use .strip to remove any spaces before or after the text that the use may have put in accidentally (I know that these extra spaces are used by default in rails and if they are there can make a filed unique.)

If anyone has any hints on how this should be done correctly I really would appreciate it.

like image 736
Datatec Avatar asked Jun 08 '09 20:06

Datatec


2 Answers

validates_uniqueness_of works by checking if a record already exists with the same value of the given field within the given scope. :scope lets you define the scope (obviously) of the uniqueness; for instance, if I was creating blog software and wanted to only allow a post title to be used once per blog, I could say validates_uniqueness_of :title, :scope => :blog_id -- without the scope, I'd only be allowing each title to be used once across the entire system. :scope won't let you do a complex check, like that which you desire.

What you're probably need to do is create your own validation function to check the uniqueness of the field in question within the given timeframe (code goes within the model):

validate :field_must_be_unique_within_six_months

def field_must_be_unique_within_six_months
  return if field.blank?
  num_duplicates = self.class.count(:conditions => ["field = ? AND created_at < ?", self.field, 6.months.ago])
  if num_duplicates > 0
    errors.add(:field, :taken)
  end
end

The field_must_be_unique_within_six_months method will work similarly to validates_uniqueness_of, in that it will add an error message if there is already a record with the same given field, but with the added condition that it will also check the date. The validate :field_must_be_unique_within_six_months will add the method to the validation process when a record is saved.

To validate multiple fields at the same time without violating DRY, you could use validates_each to do something like the following:

validates_each :field1, :field2 do |record, attr, value|
  if record.class.exists?(["#{attr.to_s} = ? AND created_at < ?", value, 6.months.ago])
    errors.add(attr, :taken)
  end
end

In the above block, record is the record being validated, attr is the attribute (so field1, field2, etc.) and value is the value of that attribute.

like image 137
Daniel Vandersluis Avatar answered Nov 15 '22 03:11

Daniel Vandersluis


You can probably do something like this:

def validate
    errors.add(:field, 'blah blah') if is_used_recently && !has_unique_field? 
end

def has_unique_field?
    Model.exists?(['field = ? and created_at > ?', self.field, 6.months.ago])
end 

def is_used_recently
    self.created_at < 6.months.ago || self.new? # i don't know if created_at would be set by this point
end

Alternatively you might want to create a new validation handler, or extend the existing one to pass in a :within option if that's something you're going to be doing often.

To get rid of leading and trailing white space the method you want is 'strip'. You can run this on all your fields by doing something like:

before_validation :clean_up_whitespace

def clean_up_whitespace
    self.some_field.strip!    # this does the strip in place
end

I hope this helps, let me know if I've made any mistakes!

like image 38
jonnii Avatar answered Nov 15 '22 04:11

jonnii