Creating Custom Validators in Rails
Finally, a post about code! I found it odd that I started a blog intended to document my coding adventures and I have yet document anything related to code at all. So, the other night I was working on a personal rails project and I ran into a situation where the available rails validations were not quite what I needed.
###Validation Rules In my model had an attribute numbers which is an array of integers. In this array each number must be unique and must be one of the valid numbers allowed.
###Custom Methods
One way to implement these validations is using
custom methods.
This is a great way to create a custom validation by using a private method in the model class
that adds errors to the model when the attribute is invalid. To do this for our uniqueness
validation rule we simply create a private validation method and call it using validate
.
class Game < ActiveRecord::Base
serialize :numbers Array
validate :numbers_are_unique
private
def numbers_are_unique
# if there are duplicates numbers.uniq.count will be less than the actual count
unless numbers.uniq.count == numbers.count
errors.add(:numbers, 'is not a unique array')
end
end
While this is great(and what I originally did), I wanted to explore creating a custom validator I could use across my app since I had another model that had a similar array of numbers that follows the same validations.
###Custom Validators
The rails documentation has a great example of how to write a
custom validator.
This is what I followed to create two custom validators UniqueArrayValidator
and
InclusiveArrayValidator
. Both these validators are not specific to my project and could be used
with any array(even if the values are not integers). I placed them in app/validators
, but others
recommend putting them in lib/validators
. If you put them in your lib folder you’ll have to make
sure they’re loaded when rails starts.
Here’s our new UniqueArrayValidator
class UniqueArrayValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.uniq.count == value.count
record.errors[attribute] << (options[:message] || 'is not a unique array')
end
end
end
For our InclusiveArrayValidator we want to be able to pass in an array of valid values to check
against. This is easily done by passing a value in the options
hash.
Here’s our InclusiveArrayValidator
class InclusiveArrayValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
valid_values = options[:valid_values]
if value.any? { |n| !valid_values.include? n }
record.errors[attribute] << (options[:message] || 'has invalid values')
end
end
end
###Using Custom Validators
Now we alter our model by removing the custom method and calling our new validators just as we
would any built in rails validator. You can see we pass in the valid_values
just as we would
any other option needed for validations.
class Game < ActiveRecord::Base
VALID_NUMBERS = [1, 2, 3, 4, 5]
serialize :numbers, Array
validates :numbers, unique_array: true,
inclusive_array: { valid_values: VALID_NUMBERS }
end
Now our numbers array is validated for uniqueness and that it only includes the values we deem valid for our use case. I hope this was helpful for anyone looking to implement a custom validator in their rails app. I’m open to suggestions on how to improve the validators, best practices, and even naming(I’m not sure inclusive is the correct way to describe that validation). Hell, maybe I missed out on a way to do this with the built in validators! Feel free to leave a comment.