judge 1.4.0 → 1.5.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/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.
|