Skip to content

Commit

Permalink
Allow blank
Browse files Browse the repository at this point in the history
  • Loading branch information
bcardarella committed Mar 6, 2011
1 parent 6b9add4 commit cabaf08
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 76 deletions.
6 changes: 4 additions & 2 deletions lib/client_side_validations/active_model.rb
Expand Up @@ -4,7 +4,7 @@ module ClientSideValidations::ActiveModel
module Validator

def client_side_hash(model, attribute)
extra_options = options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:on])
extra_options = options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:on, :allow_blank])
{ :message => model.errors.generate_message(attribute, message_type, extra_options) }.merge(extra_options)
end

Expand All @@ -23,11 +23,13 @@ def client_side_validation_hash
client_side_hash = validator.client_side_hash(self, attr[0])
if (client_side_hash[:on] == self.validation_context || client_side_hash[:on].nil?)
kind_hash.merge!(validator.kind => client_side_hash.except(:on))
else
kind_hash.merge!({})
end
end

attr_hash.merge!(attr[0] => validator_hash)
end.delete_if { |key, value| value.nil? }
end.delete_if { |key, value| value.blank? }
end
end
end
Expand Down
6 changes: 2 additions & 4 deletions lib/client_side_validations/active_model/length.rb
@@ -1,13 +1,11 @@
module ClientSideValidations::ActiveModel
module Length

# This needs to handle the :tokenizer option. Currently the client side script will just assume
# the default of value.split(//)
def client_side_hash(model, attribute)
extra_options = options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS).except(:tokenizer, :too_long, :too_short, :wrong_length)
extra_options = options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:allow_blank, :on]).except(:tokenizer, :too_long, :too_short, :wrong_length)

errors_options = options.except(*self.class::RESERVED_OPTIONS)
messages = extra_options.except(:js_tokenizer).keys.inject({}) do |hash, key|
messages = extra_options.except(:js_tokenizer, :allow_blank, :on).keys.inject({}) do |hash, key|
errors_options[:count] = extra_options[key]
count = extra_options[key]
default_message = options[self.class::MESSAGES[key]]
Expand Down
4 changes: 2 additions & 2 deletions lib/client_side_validations/active_model/numericality.rb
Expand Up @@ -9,8 +9,8 @@ def self.included(base)
end

def client_side_hash(model, attribute)
extra_options = options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS, :message).reject { |key, value| key == :only_integer && !value }
keys = [:numericality] | (extra_options.keys - [:message])
extra_options = options.except(*::ActiveModel::Errors::CALLBACKS_OPTIONS - [:on], :message).reject { |key, value| key == :only_integer && !value }
keys = [:numericality] | (extra_options.keys - [:message, :on])
filtered_options = options.except(*self.class::RESERVED_OPTIONS)
messages = keys.inject({}) do |hash, key|
count = extra_options[key]
Expand Down
86 changes: 58 additions & 28 deletions lib/generators/templates/client-side-validations.js
Expand Up @@ -121,10 +121,16 @@ var clientSideValidations = new function() {
}
},
format: function(validator, selector) {
if (!validator['with'].test(selector.val())) {
return validator.message;
} else if (validator['without'].test(selector.val())) {
return validator.message;
if (message = this.presence(validator, selector) && validator.allow_blank == true) {
return;
} else if (message) {
return message;
} else {
if (validator['with'] && !validator['with'].test(selector.val())) {
return validator.message;
} else if (validator['without'] && validator['without'].test(selector.val())) {
return validator.message;
}
}
},
presence: function(validator, selector) {
Expand Down Expand Up @@ -159,13 +165,19 @@ var clientSideValidations = new function() {
}
},
length: function(validator, selector) {
var CHECKS = { is: '==', minimum: '>=', maximum: '<=' }
var tokenizer = validator.js_tokenizer || "split('')";
var tokenized_length = eval("(selector.val()." + tokenizer + " || '').length");

for (var check in CHECKS) {
if (validator[check] && !eval(tokenized_length + CHECKS[check] + validator[check])) {
return validator.messages[check];
if (message = this.presence(validator, selector) && validator.allow_blank == true) {
return;
} else if (message) {
return message;
} else {
var CHECKS = { is: '==', minimum: '>=', maximum: '<=' }
var tokenizer = validator.js_tokenizer || "split('')";
var tokenized_length = eval("(selector.val()." + tokenizer + " || '').length");

for (var check in CHECKS) {
if (validator[check] && !eval(tokenized_length + CHECKS[check] + validator[check])) {
return validator.messages[check];
}
}
}
},
Expand All @@ -175,31 +187,49 @@ var clientSideValidations = new function() {
}
},
exclusion: function(validator, selector) {
for (var i = 0; i < validator['in'].length; i++) {
if (validator['in'][i] == selector.val()) {
return validator.message;
if (message = this.presence(validator, selector) && validator.allow_blank == true) {
return;
} else if (message) {
return message;
} else {
for (var i = 0; i < validator['in'].length; i++) {
if (validator['in'][i] == selector.val()) {
return validator.message;
}
}
}
},
inclusion: function(validator, selector) {
for (var i = 0; i < validator['in'].length; i++) {
if (validator['in'][i] == selector.val()) {
return;
if (message = this.presence(validator, selector) && validator.allow_blank == true) {
return;
} else if (message) {
return message;
} else {
for (var i = 0; i < validator['in'].length; i++) {
if (validator['in'][i] == selector.val()) {
return;
}
}
return validator.message;
}
return validator.message;
},
uniqueness: function(validator, selector) {
var data = {};
data['case_sensitive'] = !!validator.case_sensitive;
data[selector.attr('name')] = selector.val();
var response = $.ajax({
url: '/validators/uniqueness.json',
data: data,
async: false
}).responseText;
if (!$.parseJSON(response)) {
return validator.message;
if (message = this.presence(validator, selector) && validator.allow_blank == true) {
return;
} else if (message) {
return message;
} else {
var data = {};
data['case_sensitive'] = !!validator.case_sensitive;
data[selector.attr('name')] = selector.val();
var response = $.ajax({
url: '/validators/uniqueness.json',
data: data,
async: false
}).responseText;
if (!$.parseJSON(response)) {
return validator.message;
}
}
}
}
Expand Down
121 changes: 81 additions & 40 deletions test/active_model/cases/test_validations.rb
Expand Up @@ -2,82 +2,123 @@

class ActiveModel::ValidationsTest < ClientSideValidations::ActiveModelTestBase

class Person
include ::ActiveModel::Validations
attr_accessor :first_name, :last_name, :age, :weight

def self.name
"Person"
end
end

def new_person
person = Class.new(Person)
yield(person)
person.new
end

def test_validations_to_client_side_hash
person = Person.new
person = new_person do |p|
p.validates_presence_of :first_name
end
expected_hash = {
:first_name => {
:presence => {
:message => "can't be blank"
}
},
:email => {
}
}
assert_equal expected_hash, person.client_side_validation_hash
end

def test_validations_to_client_side_hash_with_validations_allow_blank
person = new_person do |p|
p.validates_length_of :first_name, :is => 10, :allow_blank => true
p.validates_format_of :first_name, :with => //, :allow_blank => true
end
expected_hash = {
:first_name => {
:length => {
:messages => { :is => 'is the wrong length (should be 10 characters)'},
:is => 10,
:allow_blank => true
},
:format => {
:message => "is invalid",
:with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
:message => 'is invalid',
:with => //,
:allow_blank => true
}
}
}
assert_equal expected_hash, person.client_side_validation_hash
end

def test_validations_to_client_side_hash_with_validations_on_create
person_class = Person.dup
person_class.class_eval do
validates_presence_of :last_name, :on => :create
validates_presence_of :age, :on => :update

def validation_context
:create
person = new_person do |p|
p.validates_length_of :first_name, :is => 10, :on => :create
p.validates_length_of :last_name, :is => 10, :on => :update
p.validates_format_of :first_name, :with => //, :on => :update
p.validates_format_of :last_name, :with => //, :on => :create
p.validates_numericality_of :age, :on => :create
p.validates_numericality_of :weight, :on => :update
p.class_eval do
def validation_context
:create
end
end
end
person = person_class.new
expected_hash = {
:first_name => {
:presence => {
:message => "can't be blank"
}
:length => {
:messages => { :is => 'is the wrong length (should be 10 characters)'},
:is => 10,
},
},
:last_name => {
:presence => {
:message => "can't be blank"
:format => {
:message => 'is invalid',
:with => //,
}
},
:email => {
:format => {
:message => "is invalid",
:with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
:age => {
:numericality => {
:messages => { :numericality => 'is not a number' },
}
}
}
assert_equal expected_hash, person.client_side_validation_hash
end

def test_validations_to_client_side_hash_with_validations_on_update
person_class = Person.dup
person_class.class_eval do
validates_presence_of :last_name, :on => :create
validates_presence_of :age, :on => :update

def validation_context
:update
person = new_person do |p|
p.validates_length_of :first_name, :is => 10, :on => :update
p.validates_length_of :last_name, :is => 10, :on => :create
p.validates_format_of :first_name, :with => //, :on => :create
p.validates_format_of :last_name, :with => //, :on => :update
p.validates_numericality_of :age, :on => :update
p.validates_numericality_of :weight, :on => :create
p.class_eval do
def validation_context
:update
end
end
end
person = person_class.new
expected_hash = {
:first_name => {
:presence => {
:message => "can't be blank"
}
:length => {
:messages => { :is => 'is the wrong length (should be 10 characters)'},
:is => 10,
},
},
:age => {
:presence => {
:message => "can't be blank"
:last_name => {
:format => {
:message => 'is invalid',
:with => //,
}
},
:email => {
:format => {
:message => "is invalid",
:with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
:age => {
:numericality => {
:messages => { :numericality => 'is not a number' },
}
}
}
Expand Down

0 comments on commit cabaf08

Please sign in to comment.