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 CHANGED
@@ -18,6 +18,6 @@ Gem::Specification.new do |s|
18
18
  s.add_development_dependency "jasmine", "~> 1.1.2"
19
19
  s.add_development_dependency "rails", "~> 3.2"
20
20
  s.add_development_dependency "rspec", "~> 2.8"
21
- s.add_development_dependency "sqlite3-ruby", "~> 1.3.3"
21
+ s.add_development_dependency "sqlite3", "~> 1.3.5"
22
22
  s.add_development_dependency "factory_girl", "~> 2.6"
23
23
  end
@@ -1,4 +1,4 @@
1
- // Judge 1.4.0
1
+ // Judge 1.5.0
2
2
  // (c) 2011–2012 Joe Corcoran
3
3
  // http://raw.github.com/joecorcoran/judge/master/LICENSE.txt
4
4
  // This is the JavaScript part of Judge, a client-side validation gem for Rails 3.
@@ -13,7 +13,7 @@
13
13
  // The judge namespace.
14
14
  var judge = {};
15
15
 
16
- judge.VERSION = '1.4.0';
16
+ judge.VERSION = '1.5.0';
17
17
 
18
18
  // A judge.Watcher is a DOM element wrapper that judge uses to store validation info and instance methods.
19
19
  judge.Watcher = function (element) {
@@ -1,4 +1,5 @@
1
- // Underscore.js 1.3.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.1';
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
- if (index == 0) {
254
- shuffled[0] = value;
255
- } else {
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, iterator, context) {
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(iterable) {
303
- if (!iterable) return [];
304
- if (iterable.toArray) return iterable.toArray();
305
- if (_.isArray(iterable)) return slice.call(iterable);
306
- if (_.isArguments(iterable)) return slice.call(iterable);
307
- return _.values(iterable);
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 _.toArray(obj).length;
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 allows it to work
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 result = [];
376
- _.reduce(initial, function(memo, el, i) {
377
- if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
378
- memo[memo.length] = el;
379
- result[result.length] = array[i];
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 result;
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(func, args); }, wait);
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
- _.debounce = function(func, wait) {
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
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(/\\\\/g, '\\').replace(/\\'/g, "'");
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(str, data) {
910
- var c = _.templateSettings;
911
- var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
912
- 'with(obj||{}){__p.push(\'' +
913
- str.replace(/\\/g, '\\\\')
914
- .replace(/'/g, "\\'")
915
- .replace(c.escape || noMatch, function(match, code) {
916
- return "',_.escape(" + unescape(code) + "),'";
917
- })
918
- .replace(c.interpolate || noMatch, function(match, code) {
919
- return "'," + unescape(code) + ",'";
920
- })
921
- .replace(c.evaluate || noMatch, function(match, code) {
922
- return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
923
- })
924
- .replace(/\r/g, '\\r')
925
- .replace(/\n/g, '\\n')
926
- .replace(/\t/g, '\\t')
927
- + "');}return __p.join('');";
928
- var func = new Function('obj', '_', tmpl);
929
- if (data) return func(data, _);
930
- return function(data) {
931
- return func.call(this, data, _);
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.
data/lib/judge.rb CHANGED
@@ -4,4 +4,5 @@ require "validator"
4
4
  require "validator_collection"
5
5
  require "message_collection"
6
6
  require "form_builder"
7
- require "html"
7
+ require "html"
8
+ require "each_validator"
@@ -0,0 +1,21 @@
1
+ module Judge
2
+ module EachValidator
3
+
4
+ def self.included(base)
5
+ base.send(:extend, ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def declare_messages(*keys)
11
+ send :define_method, :messages_to_lookup do
12
+ keys
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+
21
+ ::ActiveModel::EachValidator.send(:include, Judge::EachValidator) if defined?(::ActiveModel::EachValidator)
@@ -1,95 +1,86 @@
1
+ require 'message_config'
2
+
1
3
  module Judge
2
4
 
3
5
  class MessageCollection
4
6
 
5
- MESSAGE_MAP = {
6
- :confirmation => { :base => :confirmation },
7
- :acceptance => { :base => :accepted },
8
- :presence => { :base => :blank },
9
- :length => { :base => nil,
10
- :options => {
11
- :minimum => :too_short,
12
- :maximum => :too_long,
13
- :is => :wrong_length
14
- }
15
- },
16
- :format => { :base => :invalid },
17
- :inclusion => { :base => :inclusion },
18
- :exclusion => { :base => :exclusion },
19
- :numericality => { :base => :not_a_number,
20
- :options => {
21
- :greater_than => :greater_than,
22
- :greater_than_or_equal_to => :greater_than_or_equal_to,
23
- :equal_to => :equal_to,
24
- :less_than => :less_than,
25
- :less_than_or_equal_to => :less_than_or_equal_to,
26
- :odd => :odd,
27
- :even => :even
28
- }
29
- }
30
- }
31
-
32
- ALLOW_BLANK = [:format, :exclusion, :inclusion, :length]
33
-
34
- DEFAULT_OPTS = { :generate => true }
35
-
36
- attr_reader :object, :method, :kind, :options, :mm
37
- attr_accessor :messages
38
-
39
- def initialize(object, method, amv, opts = {})
40
- opts = DEFAULT_OPTS.merge(opts)
7
+ include MessageConfig
8
+
9
+ attr_reader :object, :method, :amv, :kind, :options
10
+
11
+ def initialize(object, method, amv)
41
12
  @object = object
42
13
  @method = method
14
+ @amv = amv
43
15
  @kind = amv.kind
44
16
  @options = amv.options.dup
45
- @mm = MESSAGE_MAP
46
17
  @messages = {}
47
- generate_messages! unless opts[:generate] == false
18
+ generate_messages!
48
19
  end
49
20
 
50
21
  def generate_messages!
51
- if messages.blank?
52
- %w{base options blank integer}.each do |type|
53
- self.send(:"generate_#{type}!")
54
- end
22
+ %w{base options integer custom blank}.each do |type|
23
+ @messages = @messages.merge(self.send(:"#{type}_messages"))
55
24
  end
56
25
  end
57
26
 
58
27
  def to_hash
59
- messages
28
+ @messages
29
+ end
30
+
31
+ def custom_messages?
32
+ amv.respond_to?(:messages_to_lookup) && amv.messages_to_lookup.present?
60
33
  end
61
34
 
62
35
  private
63
36
 
64
- def generate_base!
65
- if mm.has_key?(kind) && mm[kind][:base].present?
66
- base_message = mm[kind][:base]
67
- messages[base_message] = object.errors.generate_message(method, base_message, options)
37
+ def base_messages
38
+ msgs = {}
39
+ if MESSAGE_MAP.has_key?(kind) && MESSAGE_MAP[kind][:base].present?
40
+ base_message = MESSAGE_MAP[kind][:base]
41
+ msgs[base_message] = object.errors.generate_message(method, base_message, options)
68
42
  end
43
+ msgs
69
44
  end
70
45
 
71
- def generate_options!
72
- if mm.has_key?(kind) && mm[kind][:options].present?
73
- opt_messages = mm[kind][:options]
46
+ def options_messages
47
+ msgs = {}
48
+ if MESSAGE_MAP.has_key?(kind) && MESSAGE_MAP[kind][:options].present?
49
+ opt_messages = MESSAGE_MAP[kind][:options]
74
50
  opt_messages.each do |opt, opt_message|
75
51
  if options.has_key?(opt)
76
52
  options_for_interpolation = { :count => options[opt] }.merge(options)
77
- messages[opt_message] = object.errors.generate_message(method, opt_message, options_for_interpolation)
53
+ msgs[opt_message] = object.errors.generate_message(method, opt_message, options_for_interpolation)
78
54
  end
79
55
  end
80
56
  end
57
+ msgs
81
58
  end
82
59
 
83
- def generate_blank!
84
- if ALLOW_BLANK.include?(kind) && options[:allow_blank].blank? && messages[:blank].blank?
85
- messages[:blank] = object.errors.generate_message(method, :blank)
60
+ def blank_messages
61
+ msgs = {}
62
+ if ALLOW_BLANK.include?(kind) && options[:allow_blank].blank? && @messages[:blank].blank?
63
+ msgs[:blank] = object.errors.generate_message(method, :blank)
86
64
  end
65
+ msgs
87
66
  end
88
67
 
89
- def generate_integer!
68
+ def integer_messages
69
+ msgs = {}
90
70
  if kind == :numericality && options[:only_integer].present?
91
- messages[:not_an_integer] = object.errors.generate_message(method, :not_an_integer)
71
+ msgs[:not_an_integer] = object.errors.generate_message(method, :not_an_integer)
72
+ end
73
+ msgs
74
+ end
75
+
76
+ def custom_messages
77
+ msgs = {}
78
+ if custom_messages?
79
+ amv.messages_to_lookup.each do |key|
80
+ msgs[key.to_sym] = object.errors.generate_message(method, key)
81
+ end
92
82
  end
83
+ msgs
93
84
  end
94
85
 
95
86
  end