client_side_validations 0.8.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +109 -0
- data/TODO +0 -1
- data/javascript/lib/client_side_validations.js +38 -28
- data/lib/client_side_validations.rb +37 -40
- data/lib/client_side_validations/adapters/action_view.rb +57 -0
- data/lib/{adapters → client_side_validations/adapters}/active_model.rb +15 -3
- data/lib/{adapters → client_side_validations/adapters}/active_record_2.rb +8 -0
- data/lib/client_side_validations/orm.rb +45 -0
- data/lib/client_side_validations/template.rb +3 -0
- data/lib/generators/client_side_validations_generator.rb +13 -0
- metadata +26 -47
- data/.document +0 -5
- data/.gitignore +0 -22
- data/README.rdoc +0 -7
- data/Rakefile +0 -46
- data/VERSION +0 -1
- data/client_side_validations.gemspec +0 -80
- data/javascript/jspec/commands/example_command.rb +0 -19
- data/javascript/jspec/rhino.js +0 -10
- data/javascript/jspec/unit/jquery.validate.spec.js +0 -146
- data/javascript/jspec/unit/spec.helper.js +0 -0
- data/javascript/vendor/jspec.js +0 -1889
- data/javascript/vendor/jspec.xhr.js +0 -210
- data/lib/adapters/action_view.rb +0 -39
- data/spec/action_view_2_spec.rb +0 -55
- data/spec/action_view_3_spec.rb +0 -55
- data/spec/active_model_3_spec.rb +0 -271
- data/spec/active_record_2_spec.rb +0 -286
- data/spec/spec.opts +0 -1
- data/spec/spec_helper.rb +0 -9
- data/spec/support/required_gems.rb +0 -2
- data/tasks/spec.rake +0 -31
data/README.markdown
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Client Side Validations
|
2
|
+
Now you can easily drop in client side validations in any Rails app. It will use validations defined in a given ActiveRecord (or ActiveModel) class for use with a Javascript form validator. (currently only [jquery.validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) is supported)
|
3
|
+
|
4
|
+
For Rails 2 and Rails 3 example apps please see [client_side_validations_example](http://github.com/dnclabs/client_side_validations_examples)
|
5
|
+
|
6
|
+
The concept is simple:
|
7
|
+
|
8
|
+
1. Include the middleware
|
9
|
+
2. Define validations in the model as you normally would
|
10
|
+
3. The validations are sent to the client in JSON
|
11
|
+
4. client_side_validations.js converts the JSON for a given validation plugin and binds the validator to the form
|
12
|
+
|
13
|
+
Currently the following validations are supported:
|
14
|
+
|
15
|
+
* validates_presence_of
|
16
|
+
* validates_format_of
|
17
|
+
* validates_numericality_of
|
18
|
+
* validates_length_of
|
19
|
+
* validates_uniqueness_of
|
20
|
+
|
21
|
+
The uniqueness validation works for both ActiveRecord and Mongoid.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
> gem install client_side_validations
|
25
|
+
|
26
|
+
### Rails 2
|
27
|
+
Add "config.gem :client_side_validations" to the "config/environment.rb" file
|
28
|
+
|
29
|
+
Then run the generator:
|
30
|
+
> script/generate client_side_validations
|
31
|
+
|
32
|
+
This will copy client_side_validations.js to "public/javascripts"
|
33
|
+
|
34
|
+
### Rails 3
|
35
|
+
Add "gem 'client_side_validations" to the Gemfile
|
36
|
+
|
37
|
+
Then run the generator:
|
38
|
+
> rails g client_side_validations
|
39
|
+
|
40
|
+
This will copy client_side_validations.js to "public/javascripts"
|
41
|
+
|
42
|
+
## Configuration
|
43
|
+
Currently only [jquery.validate](http://bassistance.de/jquery-plugins/jquery-plugin-validation/) is supported so you will need to download [jQuery](http://docs.jquery.com/Downloading_jQuery) and the jQuery Validate plugin to "public/javascripts"
|
44
|
+
|
45
|
+
### Rack
|
46
|
+
The following routes will be reserved for client side validations:
|
47
|
+
|
48
|
+
/validations.json
|
49
|
+
/validations/uniqueness.json
|
50
|
+
|
51
|
+
Add the middleware to your stack:
|
52
|
+
|
53
|
+
config/environment.rb for Rails 2.x
|
54
|
+
|
55
|
+
config/application.rb for Rails 3.x
|
56
|
+
|
57
|
+
...
|
58
|
+
config.middleware.use 'ClientSideValidations'
|
59
|
+
...
|
60
|
+
|
61
|
+
### Model
|
62
|
+
Validate your models as you normally would
|
63
|
+
|
64
|
+
class Book < ActiveRecord::Base
|
65
|
+
validates_presence_of :author
|
66
|
+
end
|
67
|
+
|
68
|
+
### Layout
|
69
|
+
You currently need both jQuery and the jQuery Validate plugin loaded before you load Client Side Validations
|
70
|
+
|
71
|
+
...
|
72
|
+
<%= javascript_include_tag 'jquery', 'jquery.validate', 'client_side_validations' %>
|
73
|
+
...
|
74
|
+
|
75
|
+
### View
|
76
|
+
Have a form ask for client side validations by passing :validate => true
|
77
|
+
|
78
|
+
...
|
79
|
+
|
80
|
+
<% form_for @book, :validations => true do |b| %>
|
81
|
+
<%= b.label :author %></br>
|
82
|
+
<%= b.text_field :author %></br>
|
83
|
+
<%= submit_tag 'Create' %>
|
84
|
+
<% end %>
|
85
|
+
|
86
|
+
...
|
87
|
+
|
88
|
+
That should be it!
|
89
|
+
|
90
|
+
## Advanced Options
|
91
|
+
|
92
|
+
### Model
|
93
|
+
If you want to define only specific fields for client side validations just override the validation_fields method on each model
|
94
|
+
|
95
|
+
class Book < ActiveRecord::Base
|
96
|
+
validatese_presence_of :author
|
97
|
+
validates_presence_of :body
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def validation_fields
|
102
|
+
[:author]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
Written by Brian Cardarella
|
108
|
+
|
109
|
+
Copyright (c) 2010 Democratic National Committee. See LICENSE for details.
|
data/TODO
CHANGED
@@ -1,33 +1,36 @@
|
|
1
|
-
/* Additional jQueryValidator methods */
|
2
|
-
|
3
1
|
if (typeof(jQuery) != "undefined") {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
2
|
+
jQuery.validator.addMethod("format", function(value, element, params) {
|
3
|
+
var pattern = new RegExp(params, "i");
|
4
|
+
return this.optional(element) || pattern.test(value);
|
5
|
+
}, jQuery.validator.format("Invalid format."));
|
6
|
+
|
7
|
+
$.extend($.fn, {
|
8
|
+
clientSideValidations: function() {
|
9
|
+
var form = this;
|
10
|
+
var object = form.attr('object-csv');
|
11
|
+
var url = '/validations.json?model=' + object;
|
12
|
+
var id = form[0].id;
|
13
|
+
var adapter = 'jquery.validate';
|
14
|
+
if (/new/.test(id)) {
|
15
|
+
id = /new_(\w+)/.exec(id)[1]
|
16
|
+
} else if (/edit/.test(id)) {
|
17
|
+
id = /edit_(\w+)_\d+/.exec(id)[1]
|
18
|
+
}
|
19
|
+
var client = new ClientSideValidations(id, adapter)
|
20
|
+
$.getJSON(url, function(json) {
|
21
|
+
var validations = client.adaptValidations(json);
|
22
|
+
form.validate({
|
23
|
+
rules: validations.rules,
|
24
|
+
messages: validations.messages
|
25
|
+
});
|
27
26
|
});
|
28
|
-
}
|
29
|
-
}
|
30
|
-
|
27
|
+
}
|
28
|
+
});
|
29
|
+
|
30
|
+
$(document).ready(function() {
|
31
|
+
$('form[object-csv]').clientSideValidations();
|
32
|
+
});
|
33
|
+
}
|
31
34
|
|
32
35
|
ClientSideValidations = function(id, adapter) {
|
33
36
|
this.id = id;
|
@@ -64,6 +67,13 @@ ClientSideValidations = function(id, adapter) {
|
|
64
67
|
value = this.validations[attr][validation]['maximum'];
|
65
68
|
}
|
66
69
|
break;
|
70
|
+
case 'uniqueness':
|
71
|
+
rule = 'remote';
|
72
|
+
value = {
|
73
|
+
url: '/validations/uniqueness.json'
|
74
|
+
}
|
75
|
+
break;
|
76
|
+
|
67
77
|
default:
|
68
78
|
}
|
69
79
|
if(rule == null) {
|
@@ -1,47 +1,44 @@
|
|
1
|
-
require 'rubygems'
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require 'cgi'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
class ClientSideValidations
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
params = CGI::parse(env['QUERY_STRING'])
|
12
|
+
case env['PATH_INFO']
|
13
|
+
when %r{^/validations.json}
|
14
|
+
body = get_validations(params['model'][0])
|
15
|
+
[200, {'Content-Type' => 'application/json', 'Content-Length' => "#{body.length}"}, body]
|
16
|
+
when %r{^/validations/uniqueness.json}
|
17
|
+
field = params.keys.first
|
18
|
+
resource, attribute = field.split(/[^\w]/)
|
19
|
+
value = params[field][0]
|
20
|
+
body = is_unique?(resource, attribute, value).to_s
|
21
|
+
[200, {'Content-Type' => 'application/json', 'Content-Length' => "#{body.length}"}, body]
|
22
|
+
else
|
23
|
+
@app.call(env)
|
16
24
|
end
|
17
25
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
if defined?(ActiveModel)
|
24
|
-
require 'adapters/active_model'
|
25
|
-
unless Object.respond_to?(:to_json)
|
26
|
-
require 'active_support/json/encoding'
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def get_validations(resource)
|
30
|
+
constantize_resource(resource).new.validations_to_json
|
27
31
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if ActiveRecord::VERSION::MAJOR == 2
|
32
|
-
require 'adapters/active_record_2'
|
33
|
-
DNCLabs::ClientSideValidations::Adapter = DNCLabs::ClientSideValidations::Adapters::ActiveRecord2
|
34
|
-
klass = ActiveRecord::Base
|
32
|
+
|
33
|
+
def is_unique?(resource, attribute, value)
|
34
|
+
constantize_resource(resource).send("find_by_#{attribute}", value) == nil
|
35
35
|
end
|
36
|
+
|
37
|
+
def constantize_resource(resource)
|
38
|
+
eval(resource.split('_').map{ |word| word.capitalize}.join)
|
39
|
+
end
|
40
|
+
|
36
41
|
end
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
# Template
|
44
|
-
|
45
|
-
if defined?(ActionView)
|
46
|
-
require 'adapters/action_view'
|
47
|
-
end
|
43
|
+
require 'client_side_validations/orm'
|
44
|
+
require 'client_side_validations/template'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module DNCLabs
|
2
|
+
module ClientSideValidations
|
3
|
+
module Adapters
|
4
|
+
module ActionView
|
5
|
+
module BaseMethods
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
form_method = base.instance_method(:form_for)
|
9
|
+
base.class_eval do
|
10
|
+
define_method(:form_for) do |record_or_name_or_array, *args, &proc|
|
11
|
+
options = args.extract_options!
|
12
|
+
options.symbolize_keys!
|
13
|
+
if validations = options.delete(:validations)
|
14
|
+
unless options.key?(:html)
|
15
|
+
options[:html] = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
unless validations == true
|
19
|
+
object = validations.to_s.underscore
|
20
|
+
else
|
21
|
+
case record_or_name_or_array
|
22
|
+
when String, Symbol
|
23
|
+
object = record_or_name_or_array.to_s
|
24
|
+
else
|
25
|
+
object = record_or_name_or_array.class.to_s.underscore
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
options[:html]['object-csv'] = object
|
30
|
+
end
|
31
|
+
args << options
|
32
|
+
form_method.bind(self).call(record_or_name_or_array, *args, &proc)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end # BaseMethods
|
38
|
+
|
39
|
+
module FormBuilderMethods
|
40
|
+
|
41
|
+
def client_side_validations(options = {})
|
42
|
+
@template.send(:client_side_validations, @object_name, objectify_options(options))
|
43
|
+
end
|
44
|
+
|
45
|
+
end # FormBuilderMethods
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ActionView::Base.class_eval do
|
52
|
+
include DNCLabs::ClientSideValidations::Adapters::ActionView::BaseMethods
|
53
|
+
end
|
54
|
+
|
55
|
+
ActionView::Helpers::FormBuilder.class_eval do
|
56
|
+
include DNCLabs::ClientSideValidations::Adapters::ActionView::FormBuilderMethods
|
57
|
+
end
|
@@ -35,6 +35,10 @@ module DNCLabs
|
|
35
35
|
validation_hash
|
36
36
|
end
|
37
37
|
|
38
|
+
def validation_fields
|
39
|
+
base._validators.keys
|
40
|
+
end
|
41
|
+
|
38
42
|
private
|
39
43
|
|
40
44
|
def get_validation_message(validation, locale)
|
@@ -45,12 +49,18 @@ module DNCLabs
|
|
45
49
|
I18n.translate('errors.messages.invalid', :locale => locale)
|
46
50
|
when :length
|
47
51
|
if count = validation.options[:minimum]
|
48
|
-
I18n.translate('errors.messages.too_short', :locale => locale).sub('{
|
52
|
+
I18n.translate('errors.messages.too_short', :locale => locale).sub('%{count}', count.to_s)
|
49
53
|
elsif count = validation.options[:maximum]
|
50
|
-
I18n.translate('errors.messages.too_long', :locale => locale).sub('{
|
54
|
+
I18n.translate('errors.messages.too_long', :locale => locale).sub('%{count}', count.to_s)
|
51
55
|
end
|
52
56
|
when :numericality
|
53
57
|
I18n.translate('errors.messages.not_a_number', :locale => locale)
|
58
|
+
when :uniqueness
|
59
|
+
if defined?(ActiveRecord) && base.kind_of?(ActiveRecord::Base)
|
60
|
+
I18n.translate('activerecord.errors.messages.taken', :locale => locale)
|
61
|
+
elsif defined?(Mongoid) && base.class.included_modules.include?(Mongoid::Document)
|
62
|
+
I18n.translate('errors.messages.taken', :locale => locale)
|
63
|
+
end
|
54
64
|
end
|
55
65
|
|
56
66
|
message = validation.options[:message]
|
@@ -65,10 +75,11 @@ module DNCLabs
|
|
65
75
|
|
66
76
|
def get_validation_options(options)
|
67
77
|
options = options.stringify_keys
|
68
|
-
options.delete('on')
|
78
|
+
options.delete('on') == :create
|
69
79
|
options.delete('tokenizer')
|
70
80
|
options.delete('only_integer')
|
71
81
|
options.delete('allow_nil')
|
82
|
+
options.delete('case_sensitive')
|
72
83
|
if options['with'].kind_of?(Regexp)
|
73
84
|
options['with'] = options['with'].inspect.to_s.sub("\\A","^").sub("\\Z","$").sub(%r{^/},"").sub(%r{/i?$}, "")
|
74
85
|
end
|
@@ -82,6 +93,7 @@ module DNCLabs
|
|
82
93
|
def remove_reserved_conditionals(*conditionals)
|
83
94
|
conditionals.flatten!
|
84
95
|
conditionals.delete_if { |conditional| conditional =~ /@_/ }
|
96
|
+
conditionals.delete_if { |conditional| conditional =~ /validation_context/ }
|
85
97
|
conditionals.first
|
86
98
|
end
|
87
99
|
end
|
@@ -35,6 +35,10 @@ module DNCLabs
|
|
35
35
|
validation_hash
|
36
36
|
end
|
37
37
|
|
38
|
+
def validation_fields
|
39
|
+
base.class.reflect_on_all_validations.map { |v| v.name }.uniq
|
40
|
+
end
|
41
|
+
|
38
42
|
private
|
39
43
|
|
40
44
|
def get_validation_message(validation, locale)
|
@@ -51,6 +55,8 @@ module DNCLabs
|
|
51
55
|
end
|
52
56
|
when 'validates_numericality_of'
|
53
57
|
I18n.translate('activerecord.errors.messages.not_a_number', :locale => locale)
|
58
|
+
when 'validates_uniqueness_of'
|
59
|
+
I18n.translate('activerecord.errors.messages.taken', :locale => locale)
|
54
60
|
end
|
55
61
|
|
56
62
|
message = validation.options[:message]
|
@@ -82,6 +88,8 @@ module DNCLabs
|
|
82
88
|
'numericality'
|
83
89
|
when 'validates_length_of'
|
84
90
|
'length'
|
91
|
+
when 'validates_uniqueness_of'
|
92
|
+
'uniqueness'
|
85
93
|
end
|
86
94
|
end
|
87
95
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module DNCLabs
|
2
|
+
module ClientSideValidations
|
3
|
+
def validations_to_json
|
4
|
+
hash = Hash.new { |h, field| h[field] = {} }
|
5
|
+
validation_fields.each do |field|
|
6
|
+
hash[field].merge!(validation_to_hash(field))
|
7
|
+
end
|
8
|
+
hash.to_json
|
9
|
+
end
|
10
|
+
|
11
|
+
def validation_to_hash(field, options = {})
|
12
|
+
dnc_csv_adapter.validation_to_hash(field, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def validation_fields
|
16
|
+
dnc_csv_adapter.validation_fields
|
17
|
+
end
|
18
|
+
|
19
|
+
def dnc_csv_adapter
|
20
|
+
unless @dnc_csv_adapter
|
21
|
+
@dnc_csv_adapter = Adapter.new(self)
|
22
|
+
end
|
23
|
+
@dnc_csv_adapter
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if defined?(ActiveModel)
|
29
|
+
require 'client_side_validations/adapters/active_model'
|
30
|
+
DNCLabs::ClientSideValidations::Adapter = DNCLabs::ClientSideValidations::Adapters::ActiveModel
|
31
|
+
klass = ActiveModel::Validations
|
32
|
+
|
33
|
+
elsif defined?(ActiveRecord)
|
34
|
+
if ActiveRecord::VERSION::MAJOR == 2
|
35
|
+
require 'client_side_validations/adapters/active_record_2'
|
36
|
+
DNCLabs::ClientSideValidations::Adapter = DNCLabs::ClientSideValidations::Adapters::ActiveRecord2
|
37
|
+
klass = ActiveRecord::Base
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if klass
|
42
|
+
klass.class_eval do
|
43
|
+
include DNCLabs::ClientSideValidations
|
44
|
+
end
|
45
|
+
end
|