etandrib-livevalidation 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +67 -0
- data/Rakefile +22 -0
- data/assets/javascripts/live_validation.js +4 -0
- data/assets/stylesheets/live_validation.css +28 -0
- data/init.rb +2 -0
- data/install.rb +1 -0
- data/lib/form_helpers.rb +49 -0
- data/lib/live_validations.rb +73 -0
- data/livevalidation.gemspec +25 -0
- data/tasks/live_validation_tasks.rake +17 -0
- data/test/form_helpers_test.rb +214 -0
- data/test/live_validations_test.rb +124 -0
- data/test/resource.rb +17 -0
- data/uninstall.rb +1 -0
- metadata +67 -0
data/README
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
LiveValidation is a plugin which allows automatic integration of your Rails application with Javascript library LiveValidation[http://www.livevalidation.com/]. This library implements client-side form validation and you can see a demo in its site.
|
2
|
+
|
3
|
+
This plugin generates the needed Javascript code to run client-side the validations you have already defined in your models (with Rails validation helpers) to run them server-side, and redefines form helpers to automaticaly include that Javascript code. You have not to write your validations twice so you can keep DRY. And if you have 1) used Rails validation helpers to validate your models, and 2) used Rails form helpers to create your forms, you only have to install this plugin to get your forms validated both server and client-side!
|
4
|
+
|
5
|
+
|
6
|
+
== A little HOWTO
|
7
|
+
|
8
|
+
1. Install the plugin or the gem dependency:
|
9
|
+
|
10
|
+
script/plugin install git://github.com/etandrib/livevalidation.git
|
11
|
+
OR
|
12
|
+
# In environment.rb
|
13
|
+
config.gem "livevalidation", :lib => "etandrib/livevalidation", :version => ">= 1.3.0", :source => "http://gems.github.com"
|
14
|
+
|
15
|
+
|
16
|
+
2. Use the rake task to install the Javascript and CSS files in the public folder
|
17
|
+
|
18
|
+
rake livevalidation:install
|
19
|
+
|
20
|
+
3. Modify your application header to include at least the Prototype and LiveValidation Javascript libraries. Also add the LiveValidation stylesheet.
|
21
|
+
|
22
|
+
<%= javascript_include_tag 'prototype', 'live_validation' %>
|
23
|
+
<%= stylesheet_link_tag 'live_validation' %>
|
24
|
+
|
25
|
+
4. Enjoy! You can know use the default Rails form helpers and validation methods which will automatically call on the LiveValidation scripts and styles.
|
26
|
+
|
27
|
+
You can disable live validation for a given field using <tt>:live => false</tt>:
|
28
|
+
|
29
|
+
<%= f.text_field :name, :live => false %>
|
30
|
+
|
31
|
+
You can also reverse this schema so the default is <b>no validation</b> by including the following in your <tt>config/environment.rb</tt>:
|
32
|
+
|
33
|
+
ActionView::live_validations = false
|
34
|
+
|
35
|
+
and enabling it only for some fields:
|
36
|
+
|
37
|
+
<%= f.text_field :name, :live => true %>
|
38
|
+
|
39
|
+
== Supported validations
|
40
|
+
|
41
|
+
* <tt>validates_presence_of</tt>
|
42
|
+
* <tt>validates_numericallity_of</tt>
|
43
|
+
* <tt>validates_format_of</tt>
|
44
|
+
* <tt>validates_length_of</tt>
|
45
|
+
* <tt>validates_confirmation_of</tt>
|
46
|
+
|
47
|
+
== Supported form helpers
|
48
|
+
|
49
|
+
* <tt>text_field</tt>
|
50
|
+
* <tt>text_area</tt>
|
51
|
+
* <tt>password_field</tt>
|
52
|
+
|
53
|
+
== Author
|
54
|
+
|
55
|
+
Gemification by {Nate Bird}[http://etandrib.github.com]. Plugin written by {Sergio Gil}[http://www.lacoctelera.com/porras]. The LiveValidation library is maintained by {Alec Hill}[http://www.livevalidation.com].
|
56
|
+
|
57
|
+
== License
|
58
|
+
|
59
|
+
LiveValidation is licensed under the terms of the {MIT License}[http://www.opensource.org/licenses/mit-license.php].
|
60
|
+
|
61
|
+
Copyright (c) 2007 Sergio Gil
|
62
|
+
|
63
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
64
|
+
|
65
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
66
|
+
|
67
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the live_validation plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the live_validation plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'LiveValidation'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
// LiveValidation 1.3 (prototype.js version)
|
2
|
+
// Copyright (c) 2007-2008 Alec Hill (www.livevalidation.com)
|
3
|
+
// LiveValidation is licensed under the terms of the MIT License
|
4
|
+
var LiveValidation=Class.create();Object.extend(LiveValidation,{VERSION:"1.3 prototype",TEXTAREA:1,TEXT:2,PASSWORD:3,CHECKBOX:4,SELECT:5,FILE:6,massValidate:function(C){var D=true;for(var B=0,A=C.length;B<A;++B){var E=C[B].validate();if(D){D=E;}}return D;}});LiveValidation.prototype={validClass:"LV_valid",invalidClass:"LV_invalid",messageClass:"LV_validation_message",validFieldClass:"LV_valid_field",invalidFieldClass:"LV_invalid_field",initialize:function(B,A){if(!B){throw new Error("LiveValidation::initialize - No element reference or element id has been provided!");}this.element=$(B);if(!this.element){throw new Error("LiveValidation::initialize - No element with reference or id of '"+B+"' exists!");}this.elementType=this.getElementType();this.validations=[];this.form=this.element.form;this.options=Object.extend({validMessage:"Thankyou!",onValid:function(){this.insertMessage(this.createMessageSpan());this.addFieldClass();},onInvalid:function(){this.insertMessage(this.createMessageSpan());this.addFieldClass();},insertAfterWhatNode:this.element,onlyOnBlur:false,wait:0,onlyOnSubmit:false},A||{});var C=this.options.insertAfterWhatNode||this.element;this.options.insertAfterWhatNode=$(C);Object.extend(this,this.options);if(this.form){this.formObj=LiveValidationForm.getInstance(this.form);this.formObj.addField(this);}this.boundFocus=this.doOnFocus.bindAsEventListener(this);Event.observe(this.element,"focus",this.boundFocus);if(!this.onlyOnSubmit){switch(this.elementType){case LiveValidation.CHECKBOX:this.boundClick=this.validate.bindAsEventListener(this);Event.observe(this.element,"click",this.boundClick);case LiveValidation.SELECT:case LiveValidation.FILE:this.boundChange=this.validate.bindAsEventListener(this);Event.observe(this.element,"change",this.boundChange);break;default:if(!this.onlyOnBlur){this.boundKeyup=this.deferValidation.bindAsEventListener(this);Event.observe(this.element,"keyup",this.boundKeyup);}this.boundBlur=this.validate.bindAsEventListener(this);Event.observe(this.element,"blur",this.boundBlur);}}},destroy:function(){if(this.formObj){this.formObj.removeField(this);this.formObj.destroy();}Event.stopObserving(this.element,"focus",this.boundFocus);if(!this.onlyOnSubmit){switch(this.elementType){case LiveValidation.CHECKBOX:Event.stopObserving(this.element,"click",this.boundClick);case LiveValidation.SELECT:case LiveValidation.FILE:Event.stopObserving(this.element,"change",this.boundChange);break;default:if(!this.onlyOnBlur){Event.stopObserving(this.element,"keyup",this.boundKeyup);}Event.stopObserving(this.element,"blur",this.boundBlur);}}this.validations=[];this.removeMessageAndFieldClass();},add:function(A,B){this.validations.push({type:A,params:B||{}});return this;},remove:function(A,B){this.validations=this.validations.reject(function(C){return(C.type==A&&C.params==B);});return this;},deferValidation:function(A){if(this.wait>=300){this.removeMessageAndFieldClass();}if(this.timeout){clearTimeout(this.timeout);}this.timeout=setTimeout(this.validate.bind(this),this.wait);},doOnBlur:function(){this.focused=false;this.validate();},doOnFocus:function(){this.focused=true;this.removeMessageAndFieldClass();},getElementType:function(){switch(true){case (this.element.nodeName.toUpperCase()=="TEXTAREA"):return LiveValidation.TEXTAREA;case (this.element.nodeName.toUpperCase()=="INPUT"&&this.element.type.toUpperCase()=="TEXT"):return LiveValidation.TEXT;case (this.element.nodeName.toUpperCase()=="INPUT"&&this.element.type.toUpperCase()=="PASSWORD"):return LiveValidation.PASSWORD;case (this.element.nodeName.toUpperCase()=="INPUT"&&this.element.type.toUpperCase()=="CHECKBOX"):return LiveValidation.CHECKBOX;case (this.element.nodeName.toUpperCase()=="INPUT"&&this.element.type.toUpperCase()=="FILE"):return LiveValidation.FILE;case (this.element.nodeName.toUpperCase()=="SELECT"):return LiveValidation.SELECT;case (this.element.nodeName.toUpperCase()=="INPUT"):throw new Error("LiveValidation::getElementType - Cannot use LiveValidation on an "+this.element.type+" input!");default:throw new Error("LiveValidation::getElementType - Element must be an input, select, or textarea!");}},doValidations:function(){this.validationFailed=false;for(var C=0,A=this.validations.length;C<A;++C){var B=this.validations[C];switch(B.type){case Validate.Presence:case Validate.Confirmation:case Validate.Acceptance:this.displayMessageWhenEmpty=true;this.validationFailed=!this.validateElement(B.type,B.params);break;default:this.validationFailed=!this.validateElement(B.type,B.params);break;}if(this.validationFailed){return false;}}this.message=this.validMessage;return true;},validateElement:function(A,C){var D=(this.elementType==LiveValidation.SELECT)?this.element.options[this.element.selectedIndex].value:this.element.value;if(A==Validate.Acceptance){if(this.elementType!=LiveValidation.CHECKBOX){throw new Error("LiveValidation::validateElement - Element to validate acceptance must be a checkbox!");}D=this.element.checked;}var E=true;try{A(D,C);}catch(B){if(B instanceof Validate.Error){if(D!==""||(D===""&&this.displayMessageWhenEmpty)){this.validationFailed=true;this.message=B.message;E=false;}}else{throw B;}}finally{return E;}},validate:function(){if(!this.element.disabled){var A=this.doValidations();if(A){this.onValid();return true;}else{this.onInvalid();return false;}}else{return true;}},enable:function(){this.element.disabled=false;return this;},disable:function(){this.element.disabled=true;this.removeMessageAndFieldClass();return this;},createMessageSpan:function(){var A=document.createElement("span");var B=document.createTextNode(this.message);A.appendChild(B);return A;},insertMessage:function(B){this.removeMessage();var A=this.validationFailed?this.invalidClass:this.validClass;if((this.displayMessageWhenEmpty&&(this.elementType==LiveValidation.CHECKBOX||this.element.value==""))||this.element.value!=""){$(B).addClassName(this.messageClass+(" "+A));if(nxtSibling=this.insertAfterWhatNode.nextSibling){this.insertAfterWhatNode.parentNode.insertBefore(B,nxtSibling);}else{this.insertAfterWhatNode.parentNode.appendChild(B);}}},addFieldClass:function(){this.removeFieldClass();if(!this.validationFailed){if(this.displayMessageWhenEmpty||this.element.value!=""){if(!this.element.hasClassName(this.validFieldClass)){this.element.addClassName(this.validFieldClass);}}}else{if(!this.element.hasClassName(this.invalidFieldClass)){this.element.addClassName(this.invalidFieldClass);}}},removeMessage:function(){if(nxtEl=this.insertAfterWhatNode.next("."+this.messageClass)){nxtEl.remove();}},removeFieldClass:function(){this.element.removeClassName(this.invalidFieldClass);this.element.removeClassName(this.validFieldClass);},removeMessageAndFieldClass:function(){this.removeMessage();this.removeFieldClass();}};var LiveValidationForm=Class.create();Object.extend(LiveValidationForm,{instances:{},getInstance:function(A){var B=Math.random()*Math.random();if(!A.id){A.id="formId_"+B.toString().replace(/\./,"")+new Date().valueOf();}if(!LiveValidationForm.instances[A.id]){LiveValidationForm.instances[A.id]=new LiveValidationForm(A);}return LiveValidationForm.instances[A.id];}});LiveValidationForm.prototype={initialize:function(A){this.element=$(A);this.fields=[];this.oldOnSubmit=this.element.onsubmit||function(){};this.element.onsubmit=function(C){var B=(LiveValidation.massValidate(this.fields))?this.oldOnSubmit.call(this.element,C)!==false:false;if(!B){Event.stop(C);}}.bindAsEventListener(this);},addField:function(A){this.fields.push(A);},removeField:function(A){this.fields=this.fields.without(A);},destroy:function(A){if(this.fields.length!=0&&!A){return false;}this.element.onsubmit=this.oldOnSubmit;LiveValidationForm.instances[this.element.id]=null;return true;}};var Validate={Presence:function(A,B){var C=Object.extend({failureMessage:"Can't be empty!"},B||{});if(A===""||A===null||A===undefined){Validate.fail(C.failureMessage);}return true;},Numericality:function(B,C){var A=B;var B=Number(B);var C=C||{};var D={notANumberMessage:C.notANumberMessage||"Must be a number!",notAnIntegerMessage:C.notAnIntegerMessage||"Must be an integer!",wrongNumberMessage:C.wrongNumberMessage||"Must be "+C.is+"!",tooLowMessage:C.tooLowMessage||"Must not be less than "+C.minimum+"!",tooHighMessage:C.tooHighMessage||"Must not be more than "+C.maximum+"!",is:((C.is)||(C.is==0))?C.is:null,minimum:((C.minimum)||(C.minimum==0))?C.minimum:null,maximum:((C.maximum)||(C.maximum==0))?C.maximum:null,onlyInteger:C.onlyInteger||false};if(!isFinite(B)){Validate.fail(D.notANumberMessage);}if(D.onlyInteger&&((/\.0+$|\.$/.test(String(A)))||(B!=parseInt(B)))){Validate.fail(D.notAnIntegerMessage);}switch(true){case (D.is!==null):if(B!=Number(D.is)){Validate.fail(D.wrongNumberMessage);}break;case (D.minimum!==null&&D.maximum!==null):Validate.Numericality(B,{tooLowMessage:D.tooLowMessage,minimum:D.minimum});Validate.Numericality(B,{tooHighMessage:D.tooHighMessage,maximum:D.maximum});break;case (D.minimum!==null):if(B<Number(D.minimum)){Validate.fail(D.tooLowMessage);}break;case (D.maximum!==null):if(B>Number(D.maximum)){Validate.fail(D.tooHighMessage);}break;}return true;},Format:function(A,B){var A=String(A);var C=Object.extend({failureMessage:"Not valid!",pattern:/./,negate:false},B||{});if(!C.negate&&!C.pattern.test(A)){Validate.fail(C.failureMessage);}if(C.negate&&C.pattern.test(A)){Validate.fail(C.failureMessage);}return true;},Email:function(A,B){var C=Object.extend({failureMessage:"Must be a valid email address!"},B||{});Validate.Format(A,{failureMessage:C.failureMessage,pattern:/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i});return true;},Length:function(A,B){var A=String(A);var B=B||{};var C={wrongLengthMessage:B.wrongLengthMessage||"Must be "+B.is+" characters long!",tooShortMessage:B.tooShortMessage||"Must not be less than "+B.minimum+" characters long!",tooLongMessage:B.tooLongMessage||"Must not be more than "+B.maximum+" characters long!",is:((B.is)||(B.is==0))?B.is:null,minimum:((B.minimum)||(B.minimum==0))?B.minimum:null,maximum:((B.maximum)||(B.maximum==0))?B.maximum:null};switch(true){case (C.is!==null):if(A.length!=Number(C.is)){Validate.fail(C.wrongLengthMessage);}break;case (C.minimum!==null&&C.maximum!==null):Validate.Length(A,{tooShortMessage:C.tooShortMessage,minimum:C.minimum});Validate.Length(A,{tooLongMessage:C.tooLongMessage,maximum:C.maximum});break;case (C.minimum!==null):if(A.length<Number(C.minimum)){Validate.fail(C.tooShortMessage);}break;case (C.maximum!==null):if(A.length>Number(C.maximum)){Validate.fail(C.tooLongMessage);}break;default:throw new Error("Validate::Length - Length(s) to validate against must be provided!");}return true;},Inclusion:function(C,D){var E=Object.extend({failureMessage:"Must be included in the list!",within:[],allowNull:false,partialMatch:false,caseSensitive:true,negate:false},D||{});if(E.allowNull&&C==null){return true;}if(!E.allowNull&&C==null){Validate.fail(E.failureMessage);}if(!E.caseSensitive){var A=[];E.within.each(function(F){if(typeof F=="string"){F=F.toLowerCase();}A.push(F);});E.within=A;if(typeof C=="string"){C=C.toLowerCase();}}var B=(E.within.indexOf(C)==-1)?false:true;if(E.partialMatch){B=false;E.within.each(function(F){if(C.indexOf(F)!=-1){B=true;}});}if((!E.negate&&!B)||(E.negate&&B)){Validate.fail(E.failureMessage);}return true;},Exclusion:function(A,B){var C=Object.extend({failureMessage:"Must not be included in the list!",within:[],allowNull:false,partialMatch:false,caseSensitive:true},B||{});C.negate=true;Validate.Inclusion(A,C);return true;},Confirmation:function(A,B){if(!B.match){throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided!");}var C=Object.extend({failureMessage:"Does not match!",match:null},B||{});C.match=$(B.match);if(!C.match){throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '"+C.match+"'!");}if(A!=C.match.value){Validate.fail(C.failureMessage);}return true;},Acceptance:function(A,B){var C=Object.extend({failureMessage:"Must be accepted!"},B||{});if(!A){Validate.fail(C.failureMessage);}return true;},Custom:function(A,B){var C=Object.extend({against:function(){return true;},args:{},failureMessage:"Not valid!"},B||{});if(!C.against(A,C.args)){Validate.fail(C.failureMessage);}return true;},now:function(A,D,C){if(!A){throw new Error("Validate::now - Validation function must be provided!");}var E=true;try{A(D,C||{});}catch(B){if(B instanceof Validate.Error){E=false;}else{throw B;}}finally{return E;}},Error:function(A){this.message=A;this.name="ValidationError";},fail:function(A){throw new Validate.Error(A);}};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
.LV_validation_message{
|
2
|
+
font-weight:bold;
|
3
|
+
margin:0 0 0 5px;
|
4
|
+
}
|
5
|
+
|
6
|
+
.LV_valid {
|
7
|
+
color:#00CC00;
|
8
|
+
}
|
9
|
+
|
10
|
+
.LV_invalid {
|
11
|
+
color:#CC0000;
|
12
|
+
}
|
13
|
+
|
14
|
+
.LV_valid_field,
|
15
|
+
input.LV_valid_field:hover,
|
16
|
+
input.LV_valid_field:active,
|
17
|
+
textarea.LV_valid_field:hover,
|
18
|
+
textarea.LV_valid_field:active {
|
19
|
+
border: 1px solid #00CC00;
|
20
|
+
}
|
21
|
+
|
22
|
+
.LV_invalid_field,
|
23
|
+
input.LV_invalid_field:hover,
|
24
|
+
input.LV_invalid_field:active,
|
25
|
+
textarea.LV_invalid_field:hover,
|
26
|
+
textarea.LV_invalid_field:active {
|
27
|
+
border: 1px solid #CC0000;
|
28
|
+
}
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/form_helpers.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActionView
|
2
|
+
|
3
|
+
mattr_accessor :live_validations
|
4
|
+
ActionView::live_validations = true
|
5
|
+
|
6
|
+
module Helpers
|
7
|
+
module FormHelper
|
8
|
+
[ :text_field, :text_area, :password_field ].each do |field_type|
|
9
|
+
define_method "#{field_type}_with_live_validations" do |object_name, method, options|
|
10
|
+
live = options.delete(:live)
|
11
|
+
live = ActionView::live_validations if live.nil?
|
12
|
+
send("#{field_type}_without_live_validations", object_name, method, options) +
|
13
|
+
( live ? live_validations_for(object_name, method) : '' )
|
14
|
+
end
|
15
|
+
alias_method_chain field_type, :live_validations
|
16
|
+
end
|
17
|
+
|
18
|
+
def live_validations_for(object_name, method)
|
19
|
+
script_tags(live_validation(object_name, method))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def live_validation(object_name, method)
|
25
|
+
if validations = self.instance_variable_get("@#{object_name.to_s}").class.live_validations[method.to_sym] rescue false
|
26
|
+
field_name = "#{object_name}_#{method}"
|
27
|
+
initialize_validator(field_name) +
|
28
|
+
validations.map do |type, configuration|
|
29
|
+
live_validation_code(field_name, type, configuration)
|
30
|
+
end.join(';')
|
31
|
+
else
|
32
|
+
''
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize_validator(field_name)
|
37
|
+
"var #{field_name} = new LiveValidation('#{field_name}');"
|
38
|
+
end
|
39
|
+
|
40
|
+
def live_validation_code(field_name, type, configuration)
|
41
|
+
"#{field_name}.add(#{ActiveRecord::Validations::VALIDATION_METHODS[type]}" + ( configuration ? ", #{configuration.to_json}" : '') + ')'
|
42
|
+
end
|
43
|
+
|
44
|
+
def script_tags(js_code = '')
|
45
|
+
( js_code.blank? ? '' : "<script>#{js_code}</script>" )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Validations
|
3
|
+
LIVE_VALIDATIONS_OPTIONS = {
|
4
|
+
:failureMessage => :message,
|
5
|
+
:pattern => :with,
|
6
|
+
:onlyInteger => :only_integer
|
7
|
+
}
|
8
|
+
# more complicated mappings in map_configuration method
|
9
|
+
|
10
|
+
VALIDATION_METHODS = {
|
11
|
+
:presence => "Validate.Presence",
|
12
|
+
:numericality => "Validate.Numericality",
|
13
|
+
:format => "Validate.Format",
|
14
|
+
:length => "Validate.Length",
|
15
|
+
:acceptance => "Validate.Acceptance",
|
16
|
+
:confirmation => "Validate.Confirmation"
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
|
22
|
+
VALIDATION_METHODS.keys.each do |type|
|
23
|
+
define_method "validates_#{type}_of_with_live_validations".to_sym do |*attr_names|
|
24
|
+
send "validates_#{type}_of_without_live_validations".to_sym, *attr_names
|
25
|
+
define_validations(type, attr_names)
|
26
|
+
end
|
27
|
+
alias_method_chain "validates_#{type}_of".to_sym, :live_validations
|
28
|
+
end
|
29
|
+
|
30
|
+
def live_validations
|
31
|
+
@live_validations ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def define_validations(validation_type, attr_names)
|
37
|
+
conf = (attr_names.last.is_a?(Hash) ? attr_names.pop : {})
|
38
|
+
attr_names.each do |attr_name|
|
39
|
+
configuration = map_configuration(conf.dup, validation_type, attr_name)
|
40
|
+
add_live_validation(attr_name, validation_type, configuration)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_live_validation(attr_name, type, configuration = {})
|
45
|
+
@live_validations ||= {}
|
46
|
+
@live_validations[attr_name] ||= {}
|
47
|
+
@live_validations[attr_name][type] = configuration
|
48
|
+
end
|
49
|
+
|
50
|
+
def map_configuration(configuration, type = nil, attr_name = '')
|
51
|
+
LIVE_VALIDATIONS_OPTIONS.each do |live, rails|
|
52
|
+
configuration[live] = configuration.delete(rails)
|
53
|
+
end
|
54
|
+
if type == :numericality
|
55
|
+
if configuration[:onlyInteger]
|
56
|
+
configuration[:notAnIntegerMessage] = configuration.delete(:failureMessage)
|
57
|
+
else
|
58
|
+
configuration[:notANumberMessage] = configuration.delete(:failureMessage)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if type == :length and range = ( configuration.delete(:in) || configuration.delete(:within) )
|
62
|
+
configuration[:minimum] = range.begin
|
63
|
+
configuration[:maximum] = range.end
|
64
|
+
end
|
65
|
+
if type == :confirmation
|
66
|
+
configuration[:match] = self.to_s.underscore + '_' + attr_name.to_s + '_confirmation'
|
67
|
+
end
|
68
|
+
configuration[:validMessage] ||= ''
|
69
|
+
configuration.reject {|k, v| v.nil? }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "livevalidation"
|
3
|
+
s.version = "1.3.0"
|
4
|
+
s.date = "2008-11-07"
|
5
|
+
s.summary = "Gem for integrating LiveValidation in Rails"
|
6
|
+
s.email = "nate@lucidi.us"
|
7
|
+
s.homepage = "http://github.com/etandrib/livevalidation"
|
8
|
+
s.description = "LiveValidation integration with default Rails form helpers and validation methods."
|
9
|
+
s.has_rdoc = false
|
10
|
+
s.authors = ["Alec Hill", "Sergio Gil", "Nate Bird"]
|
11
|
+
s.files = ["README",
|
12
|
+
"Rakefile",
|
13
|
+
"livevalidation.gemspec",
|
14
|
+
"init.rb",
|
15
|
+
"install.rb",
|
16
|
+
"uninstall.rb",
|
17
|
+
"assets/javascripts/live_validation.js",
|
18
|
+
"assets/stylesheets/live_validation.css",
|
19
|
+
"lib/form_helpers.rb",
|
20
|
+
"lib/live_validations.rb",
|
21
|
+
"tasks/live_validation_tasks.rake"]
|
22
|
+
s.test_files = ["test/form_helpers_test.rb",
|
23
|
+
"test/live_validations_test.rb",
|
24
|
+
"test/resource.rb"]
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
namespace :livevalidation do
|
2
|
+
|
3
|
+
PLUGIN_ROOT = File.dirname(__FILE__) + '/../'
|
4
|
+
|
5
|
+
desc 'Installs required javascript and stylesheet files to the public/ directory.'
|
6
|
+
task :install do
|
7
|
+
FileUtils.cp Dir[PLUGIN_ROOT + '/assets/javascripts/*.js'], RAILS_ROOT + '/public/javascripts'
|
8
|
+
FileUtils.cp Dir[PLUGIN_ROOT + '/assets/stylesheets/*.css'], RAILS_ROOT + '/public/stylesheets'
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Removes the javascript and stylesheet for the plugin.'
|
12
|
+
task :remove do
|
13
|
+
FileUtils.rm %{live_validation.js}.collect { |f| RAILS_ROOT + "/public/javascripts/" + f }
|
14
|
+
FileUtils.rm %{live_validation.css}.collect { |f| RAILS_ROOT + "/public/stylesheets/" + f }
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
require 'action_controller'
|
5
|
+
require 'action_controller/test_process'
|
6
|
+
require 'action_view'
|
7
|
+
require File.dirname(__FILE__) + '/../lib/live_validations'
|
8
|
+
require File.dirname(__FILE__) + '/../lib/form_helpers'
|
9
|
+
require File.dirname(__FILE__) + '/../test/resource'
|
10
|
+
|
11
|
+
class ResourcesController < ActionController::Base
|
12
|
+
def without_instance_var
|
13
|
+
render_form(:text, :name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def without_live
|
17
|
+
@resource = Resource.new
|
18
|
+
render :inline => "<% form_for(:resource, :url => resources_path) do |f| %><%= f.text_field :name, :live => false %><% end %>"
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_live
|
22
|
+
@resource = Resource.new
|
23
|
+
render :inline => "<% form_for(:resource, :url => resources_path) do |f| %><%= f.text_field :name, :live => true %><% end %>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_string
|
27
|
+
@resource = Resource.new
|
28
|
+
render :inline => "<% form_for(:resource, :url => resources_path) do |f| %><%= f.text_field 'name' %><% end %>"
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_text_area
|
32
|
+
@resource = Resource.new
|
33
|
+
render :inline => "<% form_for(:resource, :url => resources_path) do |f| %><%= f.text_area :name %><% end %>"
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
@resource = Resource.new
|
38
|
+
render_form(:text, :name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def amount
|
42
|
+
@resource = Resource.new
|
43
|
+
render_form(:text, :amount)
|
44
|
+
end
|
45
|
+
|
46
|
+
def password
|
47
|
+
@resource = Resource.new
|
48
|
+
render :inline => "<% form_for(:resource, :url => resources_path) do |f| %><%= f.password_field :password %><%= f.password_field :password_confirmation %><% end %>"
|
49
|
+
end
|
50
|
+
|
51
|
+
def rescue_action(e)
|
52
|
+
raise e
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def render_form(type, method)
|
58
|
+
render :inline => "<% form_for(:resource, :url => resources_path) do |f| %><%= f.#{type}_field :#{method} %><% end %>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
ActionController::Routing::Routes.draw do |map|
|
63
|
+
map.resources :resources
|
64
|
+
map.connect ':controller/:action/:id'
|
65
|
+
end
|
66
|
+
|
67
|
+
class FormHelpersTest < Test::Unit::TestCase
|
68
|
+
|
69
|
+
def setup
|
70
|
+
@controller = ResourcesController.new
|
71
|
+
@request = ActionController::TestRequest.new
|
72
|
+
@response = ActionController::TestResponse.new
|
73
|
+
Resource.class_eval do # reset live validations
|
74
|
+
@live_validations = {}
|
75
|
+
end
|
76
|
+
ActionView::live_validations = true # reset default behaviour
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_without_instance_var
|
80
|
+
get :without_instance_var
|
81
|
+
check_form_item :type => 'text', :name => 'name'
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_without_live
|
85
|
+
Resource.class_eval do
|
86
|
+
validates_presence_of :name
|
87
|
+
end
|
88
|
+
get :without_live
|
89
|
+
check_form_item :type => 'text', :name => 'name'
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_without_live_with_false_default
|
93
|
+
ActionView::live_validations = false
|
94
|
+
Resource.class_eval do
|
95
|
+
validates_presence_of :name
|
96
|
+
end
|
97
|
+
get :name
|
98
|
+
check_form_item :type => 'text', :name => 'name'
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_with_live_with_false_default
|
102
|
+
ActionView::live_validations = false
|
103
|
+
Resource.class_eval do
|
104
|
+
validates_presence_of :name
|
105
|
+
end
|
106
|
+
get :with_live
|
107
|
+
check_form_item :type => 'text', :name => 'name' do |script|
|
108
|
+
assert_matches script, "var resource_name = new LiveValidation('resource_name');resource_name.add(Validate.Presence, {\"validMessage\": \"\"})"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_with_string
|
113
|
+
Resource.class_eval do
|
114
|
+
validates_presence_of :name
|
115
|
+
end
|
116
|
+
get :with_string
|
117
|
+
check_form_item :type => 'text', :name => 'name' do |script|
|
118
|
+
assert_matches script, "var resource_name = new LiveValidation('resource_name');resource_name.add(Validate.Presence, {\"validMessage\": \"\"})"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_with_text_area
|
123
|
+
Resource.class_eval do
|
124
|
+
validates_presence_of :name
|
125
|
+
end
|
126
|
+
get :with_text_area
|
127
|
+
assert_response :ok
|
128
|
+
assert_select 'form[action="/resources"]' do
|
129
|
+
assert_select "textarea[id='resource_name']"
|
130
|
+
assert_select 'script', "var resource_name = new LiveValidation('resource_name');resource_name.add(Validate.Presence, {\"validMessage\": \"\"})"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_presence
|
135
|
+
Resource.class_eval do
|
136
|
+
validates_presence_of :name
|
137
|
+
end
|
138
|
+
get :name
|
139
|
+
check_form_item :type => 'text', :name => 'name' do |script|
|
140
|
+
assert_matches script, "var resource_name = new LiveValidation('resource_name');resource_name.add(Validate.Presence, {\"validMessage\": \"\"})"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_presence_with_message
|
145
|
+
Resource.class_eval do
|
146
|
+
validates_presence_of :name, :message => 'is required'
|
147
|
+
end
|
148
|
+
get :name
|
149
|
+
check_form_item :type => 'text', :name => 'name' do |script|
|
150
|
+
assert_matches script, /var resource_name = new LiveValidation\('resource_name'\);resource_name.add\(Validate.Presence, \{(.+)\}\)/
|
151
|
+
assert_matches script, "\"validMessage\": \"\""
|
152
|
+
assert_matches script, "\"failureMessage\": \"is required\""
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_numericality
|
157
|
+
Resource.class_eval do
|
158
|
+
validates_numericality_of :amount
|
159
|
+
end
|
160
|
+
get :amount
|
161
|
+
check_form_item :type => 'text', :name => 'amount' do |script|
|
162
|
+
assert_matches script, "var resource_amount = new LiveValidation('resource_amount');resource_amount.add(Validate.Numericality, {\"validMessage\": \"\"})"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_numericality_only_integer
|
167
|
+
Resource.class_eval do
|
168
|
+
validates_numericality_of :amount, :only_integer => true
|
169
|
+
end
|
170
|
+
get :amount
|
171
|
+
check_form_item :type => 'text', :name => 'amount' do |script|
|
172
|
+
assert_matches script, /var resource_amount = new LiveValidation\('resource_amount'\);resource_amount.add\(Validate.Numericality, \{(.*)\}\)/
|
173
|
+
assert_matches script, "\"onlyInteger\": true"
|
174
|
+
assert_matches script, "\"validMessage\": \"\""
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_confirmation
|
179
|
+
Resource.class_eval do
|
180
|
+
validates_confirmation_of :password
|
181
|
+
end
|
182
|
+
get :password
|
183
|
+
check_form_item :type => 'password', :name => 'password' do |script|
|
184
|
+
assert_matches script, /var resource_password = new LiveValidation\('resource_password'\);resource_password.add\(Validate.Confirmation, \{(.*)\}\)/
|
185
|
+
assert_matches script, "\"match\": \"resource_password_confirmation\""
|
186
|
+
assert_matches script, "\"validMessage\": \"\""
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def check_form_item(options = {}, &blk)
|
193
|
+
assert_response :ok
|
194
|
+
assert_select 'form[action="/resources"]' do
|
195
|
+
assert_select "input[type='#{options[:type]}'][id='resource_#{options[:name]}']"
|
196
|
+
if block_given?
|
197
|
+
assert_select 'script' do |element|
|
198
|
+
yield(element.to_s)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
assert_select 'script', 0
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def assert_matches(string, regexp)
|
207
|
+
if regexp.is_a?(Regexp)
|
208
|
+
assert string =~ regexp, "#{string} doesn't match #{regexp}"
|
209
|
+
else
|
210
|
+
assert string[regexp], "#{string} doesn't match #{regexp}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/live_validations'
|
5
|
+
require File.dirname(__FILE__) + '/../test/resource'
|
6
|
+
|
7
|
+
class LiveValidationTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
Resource.class_eval do # reset live validations
|
11
|
+
@live_validations = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_live_validations_accessor
|
16
|
+
assert_kind_of(Hash, Resource.live_validations)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_without_validations
|
20
|
+
assert_equal({}, Resource.live_validations)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_without_ok_message
|
24
|
+
Resource.class_eval do
|
25
|
+
validates_presence_of :name, :message => "can't be blank"
|
26
|
+
end
|
27
|
+
assert_equal("", Resource.live_validations[:name][:presence][:validMessage])
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_with_ok_message
|
31
|
+
Resource.class_eval do
|
32
|
+
validates_presence_of :name, :message => "can't be blank", :validMessage => 'thank you!'
|
33
|
+
end
|
34
|
+
assert_equal("thank you!", Resource.live_validations[:name][:presence][:validMessage])
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_presence
|
38
|
+
Resource.class_eval do
|
39
|
+
validates_presence_of :name, :message => "can't be blank"
|
40
|
+
end
|
41
|
+
assert_equal("can't be blank", Resource.live_validations[:name][:presence][:failureMessage])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_presence_more_than_one_attribute
|
45
|
+
Resource.class_eval do
|
46
|
+
validates_presence_of :name, :amount, :message => "can't be blank"
|
47
|
+
end
|
48
|
+
assert_equal("can't be blank", Resource.live_validations[:name][:presence][:failureMessage])
|
49
|
+
assert_equal("can't be blank", Resource.live_validations[:amount][:presence][:failureMessage])
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_numericality
|
53
|
+
Resource.class_eval do
|
54
|
+
validates_numericality_of :amount, :message => "isn't a valid number"
|
55
|
+
end
|
56
|
+
assert_equal("isn't a valid number", Resource.live_validations[:amount][:numericality][:notANumberMessage])
|
57
|
+
assert(!Resource.live_validations[:amount][:numericality][:onlyInteger])
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_numericality_only_integer
|
61
|
+
Resource.class_eval do
|
62
|
+
validates_numericality_of :amount, :only_integer => true, :message => "isn't an integer number"
|
63
|
+
end
|
64
|
+
assert_equal("isn't an integer number", Resource.live_validations[:amount][:numericality][:notAnIntegerMessage])
|
65
|
+
assert(Resource.live_validations[:amount][:numericality][:onlyInteger])
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_format
|
69
|
+
Resource.class_eval do
|
70
|
+
validates_format_of :name, :with => /^\w+$/, :message => "only letters are accepted"
|
71
|
+
end
|
72
|
+
assert_equal("only letters are accepted", Resource.live_validations[:name][:format][:failureMessage])
|
73
|
+
assert_equal(/^\w+$/, Resource.live_validations[:name][:format][:pattern])
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_length_max
|
77
|
+
Resource.class_eval do
|
78
|
+
validates_length_of :name, :maximum => 10, :message => "must be under 10 characters long"
|
79
|
+
end
|
80
|
+
assert_equal("must be under 10 characters long", Resource.live_validations[:name][:length][:failureMessage])
|
81
|
+
assert_equal(10, Resource.live_validations[:name][:length][:maximum])
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_length_min
|
85
|
+
Resource.class_eval do
|
86
|
+
validates_length_of :name, :minimum => 4, :message => "must be more than 4 characters long"
|
87
|
+
end
|
88
|
+
assert_equal("must be more than 4 characters long", Resource.live_validations[:name][:length][:failureMessage])
|
89
|
+
assert_equal(4, Resource.live_validations[:name][:length][:minimum])
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_length_range
|
93
|
+
Resource.class_eval do
|
94
|
+
validates_length_of :name, :in => 4..10, :message => "must be between 4 and 10 characters long"
|
95
|
+
end
|
96
|
+
assert_equal("must be between 4 and 10 characters long", Resource.live_validations[:name][:length][:failureMessage])
|
97
|
+
assert_equal(4, Resource.live_validations[:name][:length][:minimum])
|
98
|
+
assert_equal(10, Resource.live_validations[:name][:length][:maximum])
|
99
|
+
assert_nil(Resource.live_validations[:name][:length][:in])
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_length_exact
|
103
|
+
Resource.class_eval do
|
104
|
+
validates_length_of :name, :is => 5, :message => "must be 5 characters long"
|
105
|
+
end
|
106
|
+
assert_equal("must be 5 characters long", Resource.live_validations[:name][:length][:failureMessage])
|
107
|
+
assert_equal(5, Resource.live_validations[:name][:length][:is])
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_acceptance
|
111
|
+
Resource.class_eval do
|
112
|
+
validates_acceptance_of :conditions, :message => "you must accept conditions"
|
113
|
+
end
|
114
|
+
assert_equal("you must accept conditions", Resource.live_validations[:conditions][:acceptance][:failureMessage])
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_confirmation
|
118
|
+
Resource.class_eval do
|
119
|
+
validates_confirmation_of :name, :message => "doesn't match"
|
120
|
+
end
|
121
|
+
assert_equal("doesn't match", Resource.live_validations[:name][:confirmation][:failureMessage])
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/test/resource.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
ActiveRecord::Base.class_eval do
|
2
|
+
alias_method :save, :valid?
|
3
|
+
def self.columns() @columns ||= []; end
|
4
|
+
|
5
|
+
def self.column(name, sql_type = nil, default = nil, null = true)
|
6
|
+
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Resource < ActiveRecord::Base
|
11
|
+
column :id, :integer
|
12
|
+
column :name, :string
|
13
|
+
column :amount, :integer
|
14
|
+
column :conditions, :boolean
|
15
|
+
|
16
|
+
attr_accessor :password
|
17
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: etandrib-livevalidation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alec Hill
|
8
|
+
- Sergio Gil
|
9
|
+
- Nate Bird
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2008-11-07 00:00:00 -08:00
|
15
|
+
default_executable:
|
16
|
+
dependencies: []
|
17
|
+
|
18
|
+
description: LiveValidation integration with default Rails form helpers and validation methods.
|
19
|
+
email: nate@lucidi.us
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- README
|
28
|
+
- Rakefile
|
29
|
+
- livevalidation.gemspec
|
30
|
+
- init.rb
|
31
|
+
- install.rb
|
32
|
+
- uninstall.rb
|
33
|
+
- assets/javascripts/live_validation.js
|
34
|
+
- assets/stylesheets/live_validation.css
|
35
|
+
- lib/form_helpers.rb
|
36
|
+
- lib/live_validations.rb
|
37
|
+
- tasks/live_validation_tasks.rake
|
38
|
+
has_rdoc: false
|
39
|
+
homepage: http://github.com/etandrib/livevalidation
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Gem for integrating LiveValidation in Rails
|
64
|
+
test_files:
|
65
|
+
- test/form_helpers_test.rb
|
66
|
+
- test/live_validations_test.rb
|
67
|
+
- test/resource.rb
|