Skip to content

Instantly share code, notes, and snippets.

@kurko
Created January 4, 2013 03:18
Show Gist options
  • Save kurko/4449645 to your computer and use it in GitHub Desktop.
Save kurko/4449645 to your computer and use it in GitHub Desktop.
A "gem" that converts the defined ActiveModel::Validations (validates in Rails' models) to JSON. Also, I created a JS code that invalidate fields based on this generated JSON.
// activeModelValidations
//
// This code is responsible for doing live validations in a form based on a
// JSON object serialized from the Rails' ActiveModel::Validations. This
// prototype interprets this JSON to do the validations.
//
// The entry point is:
//
// activeModelValidations.observe(JSON)
//
// This is responsible for observing any change in the fields specified in the
// JSON object and printing error messages in real time.
//
// The second useful method is:
//
// activeModelValidations.isFormValid(form, JSON)
//
// This method verifies if a given form is valid according to an
// ActiveModel::Validations serialized JSON object. Return true or false.
function activeModelValidations(){ }
var activeModelValidation = new activeModelValidations();
activeModelValidations.prototype.validators = null;
//
// observe()
//
// Observes any changes in the fields specified in the JSON object. It also
// prints error messages in real time in the invalid fields.
activeModelValidations.prototype.observe = function(validators) {
this.validators = validators;
var _this = this;
$.each(this.validators, function(field, specs){
$("input[name='"+field+"']").on("focusout", function(){
_this.validateField(field);
});
});
}
//
// isFormValid()
//
// Validates a whole `form`. It adds error messages automatically.
//
// Returns true if all input fields are valid, false otherwise.
activeModelValidations.prototype.isFormValid = function(form, validators) {
var _this = this;
var valid = true;
$.each(validators, function(field, specs){
if ($(form).find("input[name='"+field+"']").length > 0)
if (!_this.validateField(field))
valid = false;
});
return valid;
}
activeModelValidations.prototype.validationCallbacks = function(){
var jsValidators = new Object();
jsValidators["presence"] = this.validatePresence;
jsValidators["numericality"] = this.validateNumericality;
jsValidators["length"] = this.validateLength;
return jsValidators;
}
//
// validateField()
//
// Validates a particular input field by name. It also sets the error message.
//
// Returns true if field is valid, false otherwise.
//
activeModelValidations.prototype.validateField = function(field) {
var _this = this;
var specs = this.validators[field];
if (typeof specs == 'object'){
for (validator in this.validationCallbacks(this)) {
if (typeof specs[validator] != 'undefined') {
var validatorFunction = this.validationCallbacks()[validator];
if (!validatorFunction.call(this, field, validator, specs[validator]))
return false;
}
}
}
return true;
};
// Validation methods corresponding to the following ActiveModel rules:
//
// * presence
// * length
// * numericality
//
// validatePresence()
//
// Validates using ActiveModel's presence algorithm. Current working rules:
//
// - present: checks if a field is present
//
activeModelValidations.prototype.validatePresence = function(fieldName, rule, specs) {
var field = this.getFieldObject(fieldName);
if (specs == true && field.val() == "") {
this.addErrorMessage(field, "Can't be blank.");
return false;
} else
this.removeErrorMessages(field);
return true;
}
// validateLength()
//
// Validates using ActiveModel's length algorithm. Current working rules:
//
// - is: checks if a field has a specific length
//
activeModelValidations.prototype.validateLength = function(fieldName, rule, specs) {
var field = this.getFieldObject(fieldName);
this.removeErrorMessages(field);
// the 'is' options
if (typeof specs["is"] == "number" && field.val().length != specs["is"]) {
var message = "This field has to have "+specs["is"]+" characters" +
" (currently "+field.val().length+")."
this.addErrorMessage(field, message);
return false;
}
// 'minimum'
if (typeof specs["minimum"] == "number" && field.val().length < specs["minimum"]) {
var message = "This field has to have at least " + specs["minimum"] +
" characters (currently " + field.val().length + ")."
this.addErrorMessage(field, message);
return false;
}
// 'maximum'
if (typeof specs["maximum"] == "number" && field.val().length > specs["maximum"]) {
var message = "This field has to have at most " + specs["maximum"] +
" characters (currently " + field.val().length + ")."
this.addErrorMessage(field, message);
return false;
}
return true;
}
// validateNumericality()
//
// Validates ActiveModel numericality. Current working rules:
//
// - integer_only: checks if a field has only numbers
//
activeModelValidations.prototype.validateNumericality = function(fieldName, rule, specs) {
var field = this.getFieldObject(fieldName);
if (specs["only_integer"] && field.val().match(/[^0-9]/)) {
this.addErrorMessage(field, "This field must have only numbers.");
return false;
} else
this.removeErrorMessages(field);
return true;
}
//
// Internal methods
//
activeModelValidations.prototype.getFieldObject = function(fieldName) {
return $("input[name='"+fieldName+"']");
}
activeModelValidations.prototype.removeErrorMessages = function(field) {
$('div.field_with_error.field_'+field.attr("name")).remove();
}
activeModelValidations.prototype.addErrorMessage = function(field, message) {
var fieldName = field.attr("name");
this.removeErrorMessages(field);
var tag = '<div class="field_with_error field_'+fieldName+'">'+message+'</div>';
field.parent().append(tag);
}
module ActiveModel
module ValidationsToJson
# Public: serializes validations to JSON format.
#
# It's useful for client-side validations.
#
# To use this, add to your ActiveModel::Validations compliant class:
#
# extend ActiveModel::ValidationsToJson
#
# Calling validations_to_json() returns a HASH in the following format:
#
# { "field_name" => {
# "presence" => true,
# "length" => { "is" => 16 }
# },
# "another_field_name => { "presence" => true }
# "number_field => {
# "numericality" => {
# "only_integer" => true
# }
# }
# }
#
def validations_to_json(convert_to_json = true)
validations = validators.each_with_object({}) do |e, validations|
options = {}
if e.kind_of?(ActiveModel::Validations::PresenceValidator)
options[:presence] = true
else
validation_name = e.class.name.split("::").last
validation_name = validation_name.scan(/(.*)Validator/)[0][0].downcase
options[validation_name.to_sym] = e.options.dup
end
merge_options_into_attributes_hash(validations, e.attributes, options)
end
validations = ActiveSupport::JSON.encode(validations) if convert_to_json
validations
end
private
def merge_options_into_attributes_hash(validations, attributes, options)
attributes.each do |attribute|
validations[attribute] = {} unless validations[attribute]
validations[attribute] = validations[attribute].merge(options)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment