Created
March 3, 2011 21:51
-
-
Save trshafer/853680 to your computer and use it in GitHub Desktop.
initializers/formtastic.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'formtastic/validations' | |
module Formtastic | |
class SemanticFormBuilder < ActionView::Helpers::FormBuilder | |
@@validate_by_default = false | |
cattr_accessor :validate_by_default | |
end | |
end | |
Formtastic::SemanticFormBuilder.validate_by_default = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Validations = { | |
validates_presence_of: function(value, options) { | |
return (/[^\s]/).test(value) && value; | |
}, | |
//we've got three types of length | |
// within which is a range {first: 12, last: 200, excludeEnd: true} | |
// minimum and maximum are obvi | |
validates_length_of: function(value, options) { | |
if (options.within != null) { | |
// you're welcome aaron: I didn't use a ternary | |
if (options.within.excludeEnd) { | |
return (value.length >= options.within.first) && (value.length < | |
options.within.last); | |
} else { | |
//console.log('vlo within include end: '+((value.length >= options.within.first) && (value.length <= options.within.last))) | |
return ((value.length >= options.within.first) && (value.length | |
<= options.within.last)); | |
} | |
} else if (options.maximum != null) { | |
return value.length <= options.maximum; | |
} else if (options.minimum != null) { | |
return value.length >= options.minimum; | |
} else { | |
return false; | |
} | |
}, | |
validates_numericality_of: function(value, options) { | |
if (! (/^\d+(\.\d+)?$/).test(value)) { | |
return false; | |
} else if (options.greater_than != null) { | |
return parseInt(value, 10) > options.greater_than; | |
} else { | |
return true; | |
} | |
}, | |
validates_size_of: function(value, options) { | |
return Validations.validates_length_of(value, options); | |
}, | |
validates_format_of: function(value, options) { | |
reg = new RegExp(options['with'].regexp, options['with'].options); | |
return reg.test(value); | |
}, | |
validates_inclusion_of: function(value, options) { | |
return (options['in'].indexOf(value) != -1); | |
}, | |
validates_exclusion_of: function(value, options) { | |
return ! Validations.validates_inclusion_of(value, options); | |
}, | |
validates_confirmation_of: function(value, options) { | |
if (console) { | |
console.log("Validates confirmation of does nothing"); | |
} | |
return true; | |
}, | |
validates_acceptance_of: function(value, options) { | |
console.log("Validates acceptance of does nothing"); | |
return false; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usage: | |
# semantic_form_for @patient, :validate => true | |
# this will add data-validate on all inputs that have simple ActiveRecord validations | |
# it adds a 'validate' class on the form | |
# using semantic_form_for @patient, :validate => :no_class | |
# will add the data-validate, but not the validate class on the form | |
# if you specify :if or :except in the conditions, it'll add :data-conditional-validation => true on the input | |
# you can set Formtastic::SemanticFormBuilder.validate_by_default = true | |
# to default validate so you don't have to pass :validate => true on all | |
# for the input you can specify :skip_validation => true | |
# This will still add the validations to the dom, but the javascript skips | |
# validations on that field | |
# | |
module ActiveRecord | |
class Base | |
def self.validation_name_to_i18n(validation_name) | |
{ | |
:validates_acceptance_of => 'accepted', | |
:validates_associated => 'invalid', | |
:validates_confirmation_of => 'confirmation', | |
:validates_exclusion_of => 'exclusion', | |
:validates_format_of => 'invalid', | |
:validates_inclusion_of => 'inclusion', | |
:validates_length_of => nil, | |
:validates_numericality_of => 'not_a_number', | |
:validates_presence_of => 'blank', | |
:validates_uniqueness_of => 'taken' | |
}[validation_name] | |
end | |
#when you get back, do vlo cuz you've gotta check the validtion options | |
def self.default_message_for(validation_name, options) | |
return I18n.t("activerecord.errors.messages.#{validation_name_to_i18n(validation_name)}") unless validation_name == :validates_length_of | |
keys = options.keys | |
if keys.include?(:within) | |
"must be between #{options[:within].first} and #{options[:within].last} characters" | |
elsif keys.include?(:maximum) || keys.include?("maximum") | |
I18n.t("activerecord.errors.messages.too_long") | |
elsif keys.include?(:minimum) | |
I18n.t("activerecord.errors.messages.too_short") | |
end | |
end | |
end | |
end | |
module Formtastic #:nodoc: | |
class SemanticFormBuilder < ActionView::Helpers::FormBuilder | |
# these are defined in the formbuilder.rb initializer cuz Aaron doesn't like load order | |
# @@validate_by_default = false | |
# cattr_accessor :validate_by_default | |
alias_method :original_input, :input | |
def input(method, options = {}) | |
return original_input(method, options) unless @options[:validate].present? | |
options[:input_html] ||= {} | |
validation_options = field_validations(method, options, false) | |
if options[:as].to_s.eql?('text_with_counter') | |
config_text_counter_validations(validation_options[:'data-validate'] || {}, options[:input_html][:'count-max']) | |
end | |
validation_options[:'data-validate'] = validation_options[:'data-validate'].to_json | |
options[:input_html].merge!(validation_options) | |
original_input(method, options) | |
end | |
def field_validations(method, options={}, as_json=true) | |
refl = (@object.is_a?(Array) ? @object.first : @object).class.reflect_on_validations_for(method) | |
if refl.present? | |
conditional = false | |
validations = refl.inject({}) do |hsh, reflection| | |
# Rails.logger.info("\n\n#{reflection.inspect}") | |
# Rails.logger.info("\n\n#{reflection.options.inspect}") | |
conditional = true if reflection.options[:if].present? or reflection.options[:except].present? | |
# all of this because the field name kept repeating | |
message = reflection.options[:message] || ActiveRecord::Base.default_message_for(reflection.macro, reflection.options) | |
field_name = options[:label].present? ? options[:label].gsub(self.required_or_optional_string(true), '') : method.to_s.titleize | |
message = "#{field_name} #{message}" unless message =~ /#{field_name}/i | |
reflection_options = reflection.options.dup.merge(:message => message) | |
reflection_options.merge!(:allow_blank => true) if options[:force_allow_blank] | |
# end calculating the message | |
hsh.merge(reflection.macro => reflection_options) | |
end | |
merging_options = {:'data-validate' => (as_json ? validations.to_json : validations) } | |
# We do it like this because we don't even want the data-condition-validation | |
merging_options.merge!(:'data-conditional-validation' => conditional) if conditional | |
if(skip_validation = options.delete(:skip_validation)) | |
merging_options.merge(:'data-skip-validation' => skip_validation) | |
end | |
return merging_options | |
else | |
return {} | |
end | |
end | |
#used to pass in the validate options into the fields_for | |
alias_method :old_semantic_fields_for, :semantic_fields_for | |
def semantic_fields_for(record_or_name_or_array, *args, &proc) | |
options = args.extract_options! | |
options.merge!(:validate => @options[:validate]) if @options[:validate].present? | |
old_semantic_fields_for(record_or_name_or_array, *(args << options), &proc) | |
end | |
# Override: This now returns true if you've set a different validation | |
# other than validates_presence_of. If you've set allow_blank to true | |
# then it will return false, otherwise it returs true. | |
# note I made some fat changes here :/ | |
def method_required?(attribute) | |
return false if @options[:validate] == false | |
if @object && @object.class.respond_to?(:reflect_on_validations_for) | |
attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym | |
@object.class.reflect_on_validations_for(attribute_sym).any? do |validation| | |
if validation.options[:allow_blank].present? | |
!validation.options[:allow_blank] | |
elsif validation.macro == :validates_presence_of | |
true | |
elsif validation.macro == :validates_length_of | |
validation.options.keys.detect { |validation_option| [:minimum, :is, :within].include?(validation_option) }.present? | |
else | |
true | |
end | |
end | |
else | |
@@all_fields_required_by_default | |
end | |
end | |
private | |
# if input is a text_with_counter, configure data validation; | |
# take smaller of client-side 'count-max' or server-side 'validates_lenght_of' | |
def config_text_counter_validations(validations, count_max) | |
validation_len = validations[:validates_length_of].present? ? validations[:validates_length_of][:maximum] : nil | |
input_max = if (count_max && validation_len) | |
(count_max < validation_len) ? count_max : validation_len | |
elsif (count_max || validation_len) | |
count_max || validation_len | |
else | |
nil | |
end | |
if input_max | |
validations[:validates_length_of] ||= {} | |
validations[:validates_length_of][:maximum] = input_max | |
end | |
end | |
end | |
module SemanticFormHelper | |
#used to add the 'validate' class into the form unless it's no_class | |
alias_method :old_semantic_form_for, :semantic_form_for | |
def semantic_form_for(record_or_name_or_array, *args, &proc) | |
options = add_validation_option(args.extract_options!) | |
options.deep_merge!(fix_nested_resources(record_or_name_or_array, options)) | |
options.deep_merge!({:html => {:'data-model' => calculate_data_model(record_or_name_or_array) }}) | |
old_semantic_form_for(record_or_name_or_array, *(args << options), &proc) | |
end | |
alias_method :old_semantic_remote_form_for, :semantic_remote_form_for | |
def semantic_remote_form_for(record_or_name_or_array, *args, &proc) | |
options = add_validation_option(args.extract_options!) | |
options.deep_merge!(fix_nested_resources(record_or_name_or_array, options)) | |
old_semantic_remote_form_for(record_or_name_or_array, *(args << options), &proc) | |
end | |
private | |
def calculate_data_model(record_or_name_or_array) | |
record_name = record_or_name_or_array.is_a?(Array) ? record_or_name_or_array.last : record_or_name_or_array | |
record_name.is_a?(Symbol) ? record_name.to_s : record_name.class.to_s.underscore | |
end | |
#:url => (@allergy.new_record? ? patient_allergies_path(@patient) : patient_allergy_path(@patient, @allergy)), :html => {:method => (@allergy.new_record? ? 'post' : 'put')} | |
def fix_nested_resources(record_or_name_or_array, options) | |
return {} if !record_or_name_or_array.is_a?(Array) or options.has_key?(:url) | |
{:url => real_url_for_nested_resources(record_or_name_or_array), :html => {:method => restful_action_for_nested_resources(record_or_name_or_array)}} | |
end | |
def real_url_for_nested_resources(array_record) | |
obj = array_record.last | |
prefix = array_record.first.class.name.underscore | |
core = obj.class.name.underscore | |
if obj.new_record? | |
send("#{prefix}_#{core.pluralize}_path", array_record.first) | |
else | |
send("#{prefix}_#{core}_path", array_record.first, obj) | |
end | |
end | |
def restful_action_for_nested_resources(array_record) | |
array_record.last.new_record? ? 'post' : 'put' | |
end | |
def add_validation_option(options) | |
options[:validate] = SemanticFormBuilder.validate_by_default if options[:validate].nil? | |
if options[:validate].present? && options[:validate] != :no_class | |
options[:html] ||= {} | |
options[:html][:class] = options[:html][:class] ? "#{options[:html][:class]} validate" : 'validate' | |
end | |
options | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
don't forget to add:
gem 'validation_reflection'
to the Gemfile