judge 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/judge.gemspec +1 -1
- data/lib/generators/judge/templates/judge.js +2 -2
- data/lib/generators/judge/templates/underscore.js +121 -60
- data/lib/judge.rb +2 -1
- data/lib/judge/each_validator.rb +21 -0
- data/lib/judge/message_collection.rb +47 -56
- data/lib/judge/message_config.rb +41 -0
- data/lib/judge/validator.rb +10 -7
- data/lib/judge/validator_collection.rb +2 -4
- data/lib/judge/version.rb +1 -1
- data/spec/each_validator_spec.rb +17 -0
- data/spec/form_builder_spec.rb +1 -1
- data/spec/html_spec.rb +1 -1
- data/spec/javascripts/helpers/underscore.js +121 -60
- data/spec/message_collection_spec.rb +30 -41
- data/spec/setup.rb +6 -1
- data/spec/{factories.rb → support/factories.rb} +0 -0
- data/spec/support/locale/en.yml +18 -0
- data/spec/support/setup.rb +72 -0
- data/spec/{spec_helper.rb → support/spec_helper.rb} +2 -2
- data/spec/support/validators/city_validator.rb +9 -0
- data/spec/validator_collection_spec.rb +1 -1
- data/spec/validator_spec.rb +2 -6
- metadata +30 -20
@@ -0,0 +1,41 @@
|
|
1
|
+
module Judge
|
2
|
+
|
3
|
+
module MessageConfig
|
4
|
+
|
5
|
+
ALLOW_BLANK = [
|
6
|
+
:format,
|
7
|
+
:exclusion,
|
8
|
+
:inclusion,
|
9
|
+
:length
|
10
|
+
]
|
11
|
+
|
12
|
+
MESSAGE_MAP = {
|
13
|
+
:confirmation => { :base => :confirmation },
|
14
|
+
:acceptance => { :base => :accepted },
|
15
|
+
:presence => { :base => :blank },
|
16
|
+
:length => { :base => nil,
|
17
|
+
:options => {
|
18
|
+
:minimum => :too_short,
|
19
|
+
:maximum => :too_long,
|
20
|
+
:is => :wrong_length
|
21
|
+
}
|
22
|
+
},
|
23
|
+
:format => { :base => :invalid },
|
24
|
+
:inclusion => { :base => :inclusion },
|
25
|
+
:exclusion => { :base => :exclusion },
|
26
|
+
:numericality => { :base => :not_a_number,
|
27
|
+
:options => {
|
28
|
+
:greater_than => :greater_than,
|
29
|
+
:greater_than_or_equal_to => :greater_than_or_equal_to,
|
30
|
+
:equal_to => :equal_to,
|
31
|
+
:less_than => :less_than,
|
32
|
+
:less_than_or_equal_to => :less_than_or_equal_to,
|
33
|
+
:odd => :odd,
|
34
|
+
:even => :even
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/judge/validator.rb
CHANGED
@@ -4,16 +4,19 @@ module Judge
|
|
4
4
|
|
5
5
|
attr_reader :active_model_validator, :kind, :options, :method, :messages
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@messages = message_collection
|
7
|
+
def initialize(object, method, amv)
|
8
|
+
@kind = amv.kind
|
9
|
+
@options = amv.options.reject { |key| [:if, :on, :unless, :tokenizer].include?(key) }
|
10
|
+
@method = method
|
11
|
+
@messages = Judge::MessageCollection.new(object, method, amv)
|
13
12
|
end
|
14
13
|
|
15
14
|
def to_hash
|
16
|
-
{
|
15
|
+
{
|
16
|
+
:kind => kind,
|
17
|
+
:options => options,
|
18
|
+
:messages => messages.to_hash
|
19
|
+
}
|
17
20
|
end
|
18
21
|
|
19
22
|
end
|
@@ -7,10 +7,8 @@ module Judge
|
|
7
7
|
attr_reader :validators
|
8
8
|
|
9
9
|
def initialize(object, method)
|
10
|
-
|
11
|
-
@validators =
|
12
|
-
Judge::Validator.new(amv, method, Judge::MessageCollection.new(object, method, amv))
|
13
|
-
end
|
10
|
+
amvs = object.class.validators_on(method).reject { |amv| amv.kind == :uniqueness }
|
11
|
+
@validators = amvs.map { |amv| Judge::Validator.new(object, method, amv) }
|
14
12
|
end
|
15
13
|
|
16
14
|
def each(&block)
|
data/lib/judge/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "support/spec_helper"
|
2
|
+
|
3
|
+
describe Judge::EachValidator do
|
4
|
+
|
5
|
+
let(:amv) { User.validators_on(:city).first }
|
6
|
+
|
7
|
+
specify "custom validators include Judge::EachValidator" do
|
8
|
+
CityValidator.include?(Judge::EachValidator).should be_true
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "#messages_to_lookup method should return array of messages" do
|
12
|
+
amv.should respond_to :messages_to_lookup
|
13
|
+
amv.messages_to_lookup.should be_an Array
|
14
|
+
amv.messages_to_lookup.should include :not_valid_city
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/spec/form_builder_spec.rb
CHANGED
data/spec/html_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
// Underscore.js 1.3.2
|
2
3
|
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
3
4
|
// Underscore is freely distributable under the MIT license.
|
4
5
|
// Portions of Underscore are inspired or borrowed from Prototype,
|
@@ -62,7 +63,7 @@
|
|
62
63
|
}
|
63
64
|
|
64
65
|
// Current version.
|
65
|
-
_.VERSION = '1.3.
|
66
|
+
_.VERSION = '1.3.2';
|
66
67
|
|
67
68
|
// Collection Functions
|
68
69
|
// --------------------
|
@@ -180,7 +181,7 @@
|
|
180
181
|
each(obj, function(value, index, list) {
|
181
182
|
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
|
182
183
|
});
|
183
|
-
return result;
|
184
|
+
return !!result;
|
184
185
|
};
|
185
186
|
|
186
187
|
// Determine if at least one element in the object matches a truth test.
|
@@ -224,7 +225,7 @@
|
|
224
225
|
|
225
226
|
// Return the maximum element or (element-based computation).
|
226
227
|
_.max = function(obj, iterator, context) {
|
227
|
-
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
|
228
|
+
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
|
228
229
|
if (!iterator && _.isEmpty(obj)) return -Infinity;
|
229
230
|
var result = {computed : -Infinity};
|
230
231
|
each(obj, function(value, index, list) {
|
@@ -236,7 +237,7 @@
|
|
236
237
|
|
237
238
|
// Return the minimum element (or element-based computation).
|
238
239
|
_.min = function(obj, iterator, context) {
|
239
|
-
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
|
240
|
+
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
|
240
241
|
if (!iterator && _.isEmpty(obj)) return Infinity;
|
241
242
|
var result = {computed : Infinity};
|
242
243
|
each(obj, function(value, index, list) {
|
@@ -250,19 +251,16 @@
|
|
250
251
|
_.shuffle = function(obj) {
|
251
252
|
var shuffled = [], rand;
|
252
253
|
each(obj, function(value, index, list) {
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
rand = Math.floor(Math.random() * (index + 1));
|
257
|
-
shuffled[index] = shuffled[rand];
|
258
|
-
shuffled[rand] = value;
|
259
|
-
}
|
254
|
+
rand = Math.floor(Math.random() * (index + 1));
|
255
|
+
shuffled[index] = shuffled[rand];
|
256
|
+
shuffled[rand] = value;
|
260
257
|
});
|
261
258
|
return shuffled;
|
262
259
|
};
|
263
260
|
|
264
261
|
// Sort the object's values by a criterion produced by an iterator.
|
265
|
-
_.sortBy = function(obj,
|
262
|
+
_.sortBy = function(obj, val, context) {
|
263
|
+
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
|
266
264
|
return _.pluck(_.map(obj, function(value, index, list) {
|
267
265
|
return {
|
268
266
|
value : value,
|
@@ -270,6 +268,8 @@
|
|
270
268
|
};
|
271
269
|
}).sort(function(left, right) {
|
272
270
|
var a = left.criteria, b = right.criteria;
|
271
|
+
if (a === void 0) return 1;
|
272
|
+
if (b === void 0) return -1;
|
273
273
|
return a < b ? -1 : a > b ? 1 : 0;
|
274
274
|
}), 'value');
|
275
275
|
};
|
@@ -299,26 +299,26 @@
|
|
299
299
|
};
|
300
300
|
|
301
301
|
// Safely convert anything iterable into a real, live array.
|
302
|
-
_.toArray = function(
|
303
|
-
if (!
|
304
|
-
if (
|
305
|
-
if (_.
|
306
|
-
if (_.
|
307
|
-
return _.values(
|
302
|
+
_.toArray = function(obj) {
|
303
|
+
if (!obj) return [];
|
304
|
+
if (_.isArray(obj)) return slice.call(obj);
|
305
|
+
if (_.isArguments(obj)) return slice.call(obj);
|
306
|
+
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
|
307
|
+
return _.values(obj);
|
308
308
|
};
|
309
309
|
|
310
310
|
// Return the number of elements in an object.
|
311
311
|
_.size = function(obj) {
|
312
|
-
return _.
|
312
|
+
return _.isArray(obj) ? obj.length : _.keys(obj).length;
|
313
313
|
};
|
314
314
|
|
315
315
|
// Array Functions
|
316
316
|
// ---------------
|
317
317
|
|
318
318
|
// Get the first element of an array. Passing **n** will return the first N
|
319
|
-
// values in the array. Aliased as `head`. The **guard** check
|
320
|
-
// with `_.map`.
|
321
|
-
_.first = _.head = function(array, n, guard) {
|
319
|
+
// values in the array. Aliased as `head` and `take`. The **guard** check
|
320
|
+
// allows it to work with `_.map`.
|
321
|
+
_.first = _.head = _.take = function(array, n, guard) {
|
322
322
|
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
323
323
|
};
|
324
324
|
|
@@ -372,15 +372,17 @@
|
|
372
372
|
// Aliased as `unique`.
|
373
373
|
_.uniq = _.unique = function(array, isSorted, iterator) {
|
374
374
|
var initial = iterator ? _.map(array, iterator) : array;
|
375
|
-
var
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
375
|
+
var results = [];
|
376
|
+
// The `isSorted` flag is irrelevant if the array only contains two elements.
|
377
|
+
if (array.length < 3) isSorted = true;
|
378
|
+
_.reduce(initial, function (memo, value, index) {
|
379
|
+
if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
|
380
|
+
memo.push(value);
|
381
|
+
results.push(array[index]);
|
380
382
|
}
|
381
383
|
return memo;
|
382
384
|
}, []);
|
383
|
-
return
|
385
|
+
return results;
|
384
386
|
};
|
385
387
|
|
386
388
|
// Produce an array that contains the union: each distinct element from all of
|
@@ -403,7 +405,7 @@
|
|
403
405
|
// Take the difference between one array and a number of other arrays.
|
404
406
|
// Only the elements present in just the first array will remain.
|
405
407
|
_.difference = function(array) {
|
406
|
-
var rest = _.flatten(slice.call(arguments, 1));
|
408
|
+
var rest = _.flatten(slice.call(arguments, 1), true);
|
407
409
|
return _.filter(array, function(value){ return !_.include(rest, value); });
|
408
410
|
};
|
409
411
|
|
@@ -514,7 +516,7 @@
|
|
514
516
|
// it with the arguments supplied.
|
515
517
|
_.delay = function(func, wait) {
|
516
518
|
var args = slice.call(arguments, 2);
|
517
|
-
return setTimeout(function(){ return func.apply(
|
519
|
+
return setTimeout(function(){ return func.apply(null, args); }, wait);
|
518
520
|
};
|
519
521
|
|
520
522
|
// Defers a function, scheduling it to run after the current call stack has
|
@@ -526,7 +528,7 @@
|
|
526
528
|
// Returns a function, that, when invoked, will only be triggered at most once
|
527
529
|
// during a given window of time.
|
528
530
|
_.throttle = function(func, wait) {
|
529
|
-
var context, args, timeout, throttling, more;
|
531
|
+
var context, args, timeout, throttling, more, result;
|
530
532
|
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
|
531
533
|
return function() {
|
532
534
|
context = this; args = arguments;
|
@@ -539,24 +541,27 @@
|
|
539
541
|
if (throttling) {
|
540
542
|
more = true;
|
541
543
|
} else {
|
542
|
-
func.apply(context, args);
|
544
|
+
result = func.apply(context, args);
|
543
545
|
}
|
544
546
|
whenDone();
|
545
547
|
throttling = true;
|
548
|
+
return result;
|
546
549
|
};
|
547
550
|
};
|
548
551
|
|
549
552
|
// Returns a function, that, as long as it continues to be invoked, will not
|
550
553
|
// be triggered. The function will be called after it stops being called for
|
551
|
-
// N milliseconds.
|
552
|
-
|
554
|
+
// N milliseconds. If `immediate` is passed, trigger the function on the
|
555
|
+
// leading edge, instead of the trailing.
|
556
|
+
_.debounce = function(func, wait, immediate) {
|
553
557
|
var timeout;
|
554
558
|
return function() {
|
555
559
|
var context = this, args = arguments;
|
556
560
|
var later = function() {
|
557
561
|
timeout = null;
|
558
|
-
func.apply(context, args);
|
562
|
+
if (!immediate) func.apply(context, args);
|
559
563
|
};
|
564
|
+
if (immediate && !timeout) func.apply(context, args);
|
560
565
|
clearTimeout(timeout);
|
561
566
|
timeout = setTimeout(later, wait);
|
562
567
|
};
|
@@ -641,6 +646,15 @@
|
|
641
646
|
return obj;
|
642
647
|
};
|
643
648
|
|
649
|
+
// Return a copy of the object only containing the whitelisted properties.
|
650
|
+
_.pick = function(obj) {
|
651
|
+
var result = {};
|
652
|
+
each(_.flatten(slice.call(arguments, 1)), function(key) {
|
653
|
+
if (key in obj) result[key] = obj[key];
|
654
|
+
});
|
655
|
+
return result;
|
656
|
+
};
|
657
|
+
|
644
658
|
// Fill in a given object with default properties.
|
645
659
|
_.defaults = function(obj) {
|
646
660
|
each(slice.call(arguments, 1), function(source) {
|
@@ -761,6 +775,7 @@
|
|
761
775
|
// Is a given array, string, or object empty?
|
762
776
|
// An "empty" object has no enumerable own-properties.
|
763
777
|
_.isEmpty = function(obj) {
|
778
|
+
if (obj == null) return true;
|
764
779
|
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
|
765
780
|
for (var key in obj) if (_.has(obj, key)) return false;
|
766
781
|
return true;
|
@@ -807,6 +822,11 @@
|
|
807
822
|
return toString.call(obj) == '[object Number]';
|
808
823
|
};
|
809
824
|
|
825
|
+
// Is a given object a finite number?
|
826
|
+
_.isFinite = function(obj) {
|
827
|
+
return _.isNumber(obj) && isFinite(obj);
|
828
|
+
};
|
829
|
+
|
810
830
|
// Is the given value `NaN`?
|
811
831
|
_.isNaN = function(obj) {
|
812
832
|
// `NaN` is the only value for which `===` is not reflexive.
|
@@ -868,6 +888,14 @@
|
|
868
888
|
return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
|
869
889
|
};
|
870
890
|
|
891
|
+
// If the value of the named property is a function then invoke it;
|
892
|
+
// otherwise, return it.
|
893
|
+
_.result = function(object, property) {
|
894
|
+
if (object == null) return null;
|
895
|
+
var value = object[property];
|
896
|
+
return _.isFunction(value) ? value.call(object) : value;
|
897
|
+
};
|
898
|
+
|
871
899
|
// Add your own custom functions to the Underscore object, ensuring that
|
872
900
|
// they're correctly added to the OOP wrapper as well.
|
873
901
|
_.mixin = function(obj) {
|
@@ -897,39 +925,72 @@
|
|
897
925
|
// guaranteed not to match.
|
898
926
|
var noMatch = /.^/;
|
899
927
|
|
928
|
+
// Certain characters need to be escaped so that they can be put into a
|
929
|
+
// string literal.
|
930
|
+
var escapes = {
|
931
|
+
'\\': '\\',
|
932
|
+
"'": "'",
|
933
|
+
'r': '\r',
|
934
|
+
'n': '\n',
|
935
|
+
't': '\t',
|
936
|
+
'u2028': '\u2028',
|
937
|
+
'u2029': '\u2029'
|
938
|
+
};
|
939
|
+
|
940
|
+
for (var p in escapes) escapes[escapes[p]] = p;
|
941
|
+
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
|
942
|
+
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
|
943
|
+
|
900
944
|
// Within an interpolation, evaluation, or escaping, remove HTML escaping
|
901
945
|
// that had been previously added.
|
902
946
|
var unescape = function(code) {
|
903
|
-
return code.replace(
|
947
|
+
return code.replace(unescaper, function(match, escape) {
|
948
|
+
return escapes[escape];
|
949
|
+
});
|
904
950
|
};
|
905
951
|
|
906
952
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
907
953
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
908
954
|
// and correctly escapes quotes within interpolated code.
|
909
|
-
_.template = function(
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
955
|
+
_.template = function(text, data, settings) {
|
956
|
+
settings = _.extend(_.templateSettings, settings);
|
957
|
+
|
958
|
+
// Compile the template source, taking care to escape characters that
|
959
|
+
// cannot be included in a string literal and then unescape them in code
|
960
|
+
// blocks.
|
961
|
+
var source = "__p+='" + text
|
962
|
+
.replace(escaper, function(match) {
|
963
|
+
return '\\' + escapes[match];
|
964
|
+
})
|
965
|
+
.replace(settings.escape || noMatch, function(match, code) {
|
966
|
+
return "'+\n_.escape(" + unescape(code) + ")+\n'";
|
967
|
+
})
|
968
|
+
.replace(settings.interpolate || noMatch, function(match, code) {
|
969
|
+
return "'+\n(" + unescape(code) + ")+\n'";
|
970
|
+
})
|
971
|
+
.replace(settings.evaluate || noMatch, function(match, code) {
|
972
|
+
return "';\n" + unescape(code) + "\n;__p+='";
|
973
|
+
}) + "';\n";
|
974
|
+
|
975
|
+
// If a variable is not specified, place data values in local scope.
|
976
|
+
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
|
977
|
+
|
978
|
+
source = "var __p='';" +
|
979
|
+
"var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
|
980
|
+
source + "return __p;\n";
|
981
|
+
|
982
|
+
var render = new Function(settings.variable || 'obj', '_', source);
|
983
|
+
if (data) return render(data, _);
|
984
|
+
var template = function(data) {
|
985
|
+
return render.call(this, data, _);
|
932
986
|
};
|
987
|
+
|
988
|
+
// Provide the compiled function source as a convenience for build time
|
989
|
+
// precompilation.
|
990
|
+
template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
|
991
|
+
source + '}';
|
992
|
+
|
993
|
+
return template;
|
933
994
|
};
|
934
995
|
|
935
996
|
// Add a "chain" function, which will delegate to the wrapper.
|