validation snippets

Dynamic RSpec tests using plain old Ruby

Tagged rspec, ruby, validation, trick, tip  Languages ruby

Just happened to stumble upon the following article at caboo.se: Handy dynamic rspec tip.

I immediately found a way of simplifying a test case that involves testing that an ever increasing number of videos can be transcoded:

require File.dirname(__FILE__) + '/helper'
require File.dirname(__FILE__) + '/../lib/transcoder.rb'

context "Transcoder" do
  
  Dir.glob('videos/*').each do |video|
    it "should support #{video}" do
      Transcoder.convert(video, "site/#{video}.flv")[:file_size].should > 0
    end
  end
end

You can also use it to test model validations:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')


describe "Product" do
  @@valid_product_attributes = { 
      :name         => 'WTF',
      :description  => 'LOL',
      :price        => '10.0',
      :tax          => '22.0'  }

  
  before(:each) do

  end

  it "should create a new instance given valid attributes" do
    Product.create!(@@valid_product_attributes)
  end
  
  @@valid_product_attributes.each do |name, value|
    it "should not allow blank #{name}" do
      lambda do
        Product.create!(@@valid_product_attributes.except(name))
      end.should raise_error(ActiveRecord::RecordInvalid)
    end
  end
end

How to add and remove validation rules (jQuery validate plugin)

Tagged remove, validation, plugin, add, jquery  Languages javascript

This snippet shows how to change the jQuery validation plugin's validation rules dynamically:

$(document).ready(function() { 
  // Setup the default validation rules
  var validator = $('form').validate({ 
    rules: { 
      username: { 
        required: true, 
        minlength: 2
      }
    }
  };

  // Drop-down selection changes validation rules
  $('#account-type').change(function () {
    // Get the jQuery validation plugin's settings
    var settings = $('form').validate().settings;

    // Modify validation settings
    $.extend(true, settings, {
       rules: {
           // Add validation of year
           "year": {
               required: true,
               min: 2010,
               max: 2020,
               number: true
           }, 
           // Remove validation of username
           "username": {} 
       }
    });
  });
});

You can also use CSS classes to achieve the same:

$('#channel-title').addClass("{required:true,messages:{required:'required field'}}")
$('#channel-title').removeClass("{required:true,messages:{required:'required field'}}")

Validating URLs in Rails

Tagged url, rails, validation, active_record  Languages ruby

Minimalist implementation

A naïve implementation:

validates_format_of :website, :with => URI::regexp(%w(http https))

via http://mbleigh.com/2009/02/18/quick-tip-rails-url-validation.html

Full-featured and reusable implementation

A more full-featured and reusable example using the Addressable gem (app/validators/uri_validator.rb):

require 'addressable/uri'

#
# Validates URLs.
#
# @param options[:allow_uri] If true, validates only URI part of URL and skips host and scheme.
# @param options[:message] Error message.
# @param options[:allowed_protocols] Allowed protocols, e.g. [:http, :https].
#
class UriValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    uri = parse_uri(value)
    if !uri
      record.errors[attribute] << generic_failure_message
    elsif !allowed_protocols.include?(uri.scheme)
      record.errors[attribute] << "must begin with #{allowed_protocols_humanized}"
    end
  end

private

  def generic_failure_message
    options[:message] || "is an invalid URL"
  end

  def allowed_protocols_humanized
    allowed_protocols.to_sentence(:two_words_connector => ' or ')
  end

  def allowed_protocols
    @allowed_protocols ||= options.fetch(:allowed_protocols, ['http', 'https'])
  end

  def parse_uri(value)
    uri = Addressable::URI.parse(value)
    uri.scheme && uri.host && uri.to_s == value && uri
  rescue URI::InvalidURIError, Addressable::URI::InvalidURIError, TypeError
  end
end

Tests:

require 'spec_helper'

describe UriValidator do
  let(:subject) do
    Class.new do
      include ActiveModel::Validations
      attr_accessor :url
      validates :url, uri: true
    end.new
  end

  VALID_URL = [ 'http://www.google.com',
                'http://.com', # NOTE: Addressable thinks this is valid
                'http://google' ]
  VALID_URL.each do |valid_url|
    it "accepts a valid URL (#{valid_url})" do
      subject.url = valid_url
      assert subject.valid?
      subject.errors.full_messages.should == []
    end
  end

  INVALID_URL = ['http://ftp://ftp.google.com',
                 'http://ssh://google.com',
                 '//www.google.com',
                 'www.google.com',
                 'google.com',
                 'http:/www.google.com',
                 '<>hi' ]
  INVALID_URL.each do |invalid_url|
    it "#{invalid_url} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      binding.pry if subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  INVALID_PROTOCOL = [ 'ftp://ftp.google.com',
                       'ssh://google.com' ]
  INVALID_PROTOCOL.each do |invalid_url|
    it "#{invalid_url} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("must begin with http or https")
    end
  end
end

Code based on http://gist.github.com/bf4/5320847.