Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Specifying the order of validations

I have a validator class that i am writing that has three validations, that are run when calling MyVariableName.valid?

validates_length_of :id_number, :is => 13, :message => "A SA ID has to be 13 digits long"
validates_format_of :id_number, :with => /^[0-9]+$/, :message => "A SA ID cannot have any symbols or letters"
validate :sa_id_validator

The third one is a custom validator. The thing is that my validator sa_id_validator requires that the data that is passed in is a 13 digit number, or I will get errors. How can I make sure that the validate :sa_id_validator is only considered after the first two have run?

Sorry if this is a very simple question I have tried figuring this out all of yesterday afternoon.

Note: this validator has to run over a couple thousand entries and is also run on a spreadsheet upload so I need it to be fast..

I saw a way of doing this but it potentially runs the validations twice, which in my case would be bad.

EDIT:

my custom validator looks like this

def sa_id_validator
  #note this is specific to South African id's
  id_makeup = /(\d{6})(\d{4})(\d{1})(\d{1})(\d{1})/.match(@id_number)
  birthdate = /(\d{2})(\d{2})(\d{2})/.match(id_makeup[1])
  citizenship = id_makeup[3]
  variable = id_makeup[4]
  validator_key = id_makeup[5]

  birthdate_validator(birthdate) && citizenship_validator(citizenship) && variable_validator(variable) && id_algorithm(id_makeup[0], validator_key)
end

private

def birthdate_validator(birthdate)
  Date.valid_date?(birthdate[1].to_i,birthdate[2].to_i,birthdate[3].to_i)
end

def citizenship_validator(citizenship)
  /[0]|[1]/.match(citizenship)
end

def variable_validator(variable)
  /[8]|[9]/.match(variable)
end

def id_algorithm(id_num, validator_key)
  odd_numbers = digits_at_odd_positions
  even_numbers = digits_at_even_positions
  # step1: the sum off all the digits in odd positions excluding the last digit.
  odd_numbers.pop
  a = odd_numbers.inject {|sum, x| sum + x}
  # step2: concate all the digits in the even positions.
  b = even_numbers.join.to_i
  # step3: multiply step2 by 2 then add all the numbers in the result together
  b_multiplied = (b*2)
  b_multiplied_array = b_multiplied.to_s.split('')
  int_array = b_multiplied_array.collect{|i| i.to_i}
  c = int_array.inject {|sum, x| sum + x}
  # step4: add the result from step 1 and 3 together 
  d = a + c
  # step5: the last digit of the id must equal the result of step 4 mod 10, subtracted from 10
  return false unless 
    validator_key == 10 - (d % 10)
  end

  def digits_at_odd_positions
    id_num_as_array.values_at(*id_num_as_array.each_index.select(&:even?))
  end

  def digits_at_even_positions
    id_num_as_array.values_at(*id_num_as_array.each_index.select(&:odd?))
  end

  def id_num_as_array
    id_number.split('').map(&:to_i)
  end
end

if i add the :calculations_ok => true attribute to my validation, and then pass in a 12 digit number instead i get this error:

i.valid?
NoMethodError: undefined method `[]' for nil:NilClass
from /home/ruberto/work/toolkit_3/toolkit/lib/id_validator.rb:17:in `sa_id_validator'

so you can see its getting to the custom validation even though it should have failed the validates_length_of :id_number??

like image 824
TheLegend Avatar asked Feb 26 '13 07:02

TheLegend


1 Answers

I am not quite sure but i have read at some blog that Rails always runs all validations even if the first one is invalid.

What you can do is to make your custom method in such a way that it would become flexible or bouncy in such a way that i would handle all the cases.

This answer would definitely help you.

Hope it would answer your question

like image 71
Muhamamd Awais Avatar answered Sep 20 '22 04:09

Muhamamd Awais