backbone-rails 0.5.3.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/vendor/assets/javascripts/backbone.js +477 -379
- data/vendor/assets/javascripts/json2.js +27 -20
- data/vendor/assets/javascripts/underscore.js +81 -59
- metadata +5 -5
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
http://www.JSON.org/json2.js
|
3
|
-
2011-
|
3
|
+
2011-10-19
|
4
4
|
|
5
5
|
Public Domain.
|
6
6
|
|
@@ -146,7 +146,7 @@
|
|
146
146
|
redistribute.
|
147
147
|
*/
|
148
148
|
|
149
|
-
/*jslint evil: true,
|
149
|
+
/*jslint evil: true, regexp: true */
|
150
150
|
|
151
151
|
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
152
152
|
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
@@ -165,7 +165,7 @@ if (!JSON) {
|
|
165
165
|
}
|
166
166
|
|
167
167
|
(function () {
|
168
|
-
|
168
|
+
'use strict';
|
169
169
|
|
170
170
|
function f(n) {
|
171
171
|
// Format integers to have at least two digits.
|
@@ -176,13 +176,14 @@ if (!JSON) {
|
|
176
176
|
|
177
177
|
Date.prototype.toJSON = function (key) {
|
178
178
|
|
179
|
-
return isFinite(this.valueOf())
|
180
|
-
this.getUTCFullYear() + '-' +
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
179
|
+
return isFinite(this.valueOf())
|
180
|
+
? this.getUTCFullYear() + '-' +
|
181
|
+
f(this.getUTCMonth() + 1) + '-' +
|
182
|
+
f(this.getUTCDate()) + 'T' +
|
183
|
+
f(this.getUTCHours()) + ':' +
|
184
|
+
f(this.getUTCMinutes()) + ':' +
|
185
|
+
f(this.getUTCSeconds()) + 'Z'
|
186
|
+
: null;
|
186
187
|
};
|
187
188
|
|
188
189
|
String.prototype.toJSON =
|
@@ -218,8 +219,9 @@ if (!JSON) {
|
|
218
219
|
escapable.lastIndex = 0;
|
219
220
|
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
220
221
|
var c = meta[a];
|
221
|
-
return typeof c === 'string'
|
222
|
-
|
222
|
+
return typeof c === 'string'
|
223
|
+
? c
|
224
|
+
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
223
225
|
}) + '"' : '"' + string + '"';
|
224
226
|
}
|
225
227
|
|
@@ -303,9 +305,11 @@ if (!JSON) {
|
|
303
305
|
// Join all of the elements together, separated with commas, and wrap them in
|
304
306
|
// brackets.
|
305
307
|
|
306
|
-
v = partial.length === 0
|
307
|
-
'[
|
308
|
-
|
308
|
+
v = partial.length === 0
|
309
|
+
? '[]'
|
310
|
+
: gap
|
311
|
+
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
|
312
|
+
: '[' + partial.join(',') + ']';
|
309
313
|
gap = mind;
|
310
314
|
return v;
|
311
315
|
}
|
@@ -340,9 +344,11 @@ if (!JSON) {
|
|
340
344
|
// Join all of the member texts together, separated with commas,
|
341
345
|
// and wrap them in braces.
|
342
346
|
|
343
|
-
v = partial.length === 0
|
344
|
-
'{
|
345
|
-
|
347
|
+
v = partial.length === 0
|
348
|
+
? '{}'
|
349
|
+
: gap
|
350
|
+
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
|
351
|
+
: '{' + partial.join(',') + '}';
|
346
352
|
gap = mind;
|
347
353
|
return v;
|
348
354
|
}
|
@@ -468,8 +474,9 @@ if (!JSON) {
|
|
468
474
|
// In the optional fourth stage, we recursively walk the new structure, passing
|
469
475
|
// each name/value pair to a reviver function for possible transformation.
|
470
476
|
|
471
|
-
return typeof reviver === 'function'
|
472
|
-
walk({'': j}, '')
|
477
|
+
return typeof reviver === 'function'
|
478
|
+
? walk({'': j}, '')
|
479
|
+
: j;
|
473
480
|
}
|
474
481
|
|
475
482
|
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
@@ -1,5 +1,5 @@
|
|
1
|
-
// Underscore.js 1.
|
2
|
-
// (c)
|
1
|
+
// Underscore.js 1.3.1
|
2
|
+
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
3
3
|
// Underscore is freely distributable under the MIT license.
|
4
4
|
// Portions of Underscore are inspired or borrowed from Prototype,
|
5
5
|
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
@@ -48,26 +48,21 @@
|
|
48
48
|
// Create a safe reference to the Underscore object for use below.
|
49
49
|
var _ = function(obj) { return new wrapper(obj); };
|
50
50
|
|
51
|
-
// Export the Underscore object for **Node.js
|
52
|
-
// backwards-compatibility for the old `require()` API. If we're
|
53
|
-
//
|
51
|
+
// Export the Underscore object for **Node.js**, with
|
52
|
+
// backwards-compatibility for the old `require()` API. If we're in
|
53
|
+
// the browser, add `_` as a global object via a string identifier,
|
54
|
+
// for Closure Compiler "advanced" mode.
|
54
55
|
if (typeof exports !== 'undefined') {
|
55
56
|
if (typeof module !== 'undefined' && module.exports) {
|
56
57
|
exports = module.exports = _;
|
57
58
|
}
|
58
59
|
exports._ = _;
|
59
|
-
} else if (typeof define === 'function' && define.amd) {
|
60
|
-
// Register as a named module with AMD.
|
61
|
-
define('underscore', function() {
|
62
|
-
return _;
|
63
|
-
});
|
64
60
|
} else {
|
65
|
-
// Exported as a string, for Closure Compiler "advanced" mode.
|
66
61
|
root['_'] = _;
|
67
62
|
}
|
68
63
|
|
69
64
|
// Current version.
|
70
|
-
_.VERSION = '1.
|
65
|
+
_.VERSION = '1.3.1';
|
71
66
|
|
72
67
|
// Collection Functions
|
73
68
|
// --------------------
|
@@ -85,7 +80,7 @@
|
|
85
80
|
}
|
86
81
|
} else {
|
87
82
|
for (var key in obj) {
|
88
|
-
if (
|
83
|
+
if (_.has(obj, key)) {
|
89
84
|
if (iterator.call(context, obj[key], key, obj) === breaker) return;
|
90
85
|
}
|
91
86
|
}
|
@@ -94,20 +89,21 @@
|
|
94
89
|
|
95
90
|
// Return the results of applying the iterator to each element.
|
96
91
|
// Delegates to **ECMAScript 5**'s native `map` if available.
|
97
|
-
_.map = function(obj, iterator, context) {
|
92
|
+
_.map = _.collect = function(obj, iterator, context) {
|
98
93
|
var results = [];
|
99
94
|
if (obj == null) return results;
|
100
95
|
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
|
101
96
|
each(obj, function(value, index, list) {
|
102
97
|
results[results.length] = iterator.call(context, value, index, list);
|
103
98
|
});
|
99
|
+
if (obj.length === +obj.length) results.length = obj.length;
|
104
100
|
return results;
|
105
101
|
};
|
106
102
|
|
107
103
|
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
108
104
|
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
109
105
|
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
110
|
-
var initial =
|
106
|
+
var initial = arguments.length > 2;
|
111
107
|
if (obj == null) obj = [];
|
112
108
|
if (nativeReduce && obj.reduce === nativeReduce) {
|
113
109
|
if (context) iterator = _.bind(iterator, context);
|
@@ -121,20 +117,22 @@
|
|
121
117
|
memo = iterator.call(context, memo, value, index, list);
|
122
118
|
}
|
123
119
|
});
|
124
|
-
if (!initial) throw new TypeError(
|
120
|
+
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
|
125
121
|
return memo;
|
126
122
|
};
|
127
123
|
|
128
124
|
// The right-associative version of reduce, also known as `foldr`.
|
129
125
|
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
|
130
126
|
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
|
127
|
+
var initial = arguments.length > 2;
|
131
128
|
if (obj == null) obj = [];
|
132
129
|
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
133
130
|
if (context) iterator = _.bind(iterator, context);
|
134
|
-
return
|
131
|
+
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
135
132
|
}
|
136
|
-
var reversed =
|
137
|
-
|
133
|
+
var reversed = _.toArray(obj).reverse();
|
134
|
+
if (context && !initial) iterator = _.bind(iterator, context);
|
135
|
+
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
|
138
136
|
};
|
139
137
|
|
140
138
|
// Return the first value which passes a truth test. Aliased as `detect`.
|
@@ -189,7 +187,7 @@
|
|
189
187
|
// Delegates to **ECMAScript 5**'s native `some` if available.
|
190
188
|
// Aliased as `any`.
|
191
189
|
var any = _.some = _.any = function(obj, iterator, context) {
|
192
|
-
iterator
|
190
|
+
iterator || (iterator = _.identity);
|
193
191
|
var result = false;
|
194
192
|
if (obj == null) return result;
|
195
193
|
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
@@ -215,7 +213,7 @@
|
|
215
213
|
_.invoke = function(obj, method) {
|
216
214
|
var args = slice.call(arguments, 2);
|
217
215
|
return _.map(obj, function(value) {
|
218
|
-
return (method
|
216
|
+
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
|
219
217
|
});
|
220
218
|
};
|
221
219
|
|
@@ -402,10 +400,11 @@
|
|
402
400
|
});
|
403
401
|
};
|
404
402
|
|
405
|
-
// Take the difference between one array and
|
403
|
+
// Take the difference between one array and a number of other arrays.
|
406
404
|
// Only the elements present in just the first array will remain.
|
407
|
-
_.difference = function(array
|
408
|
-
|
405
|
+
_.difference = function(array) {
|
406
|
+
var rest = _.flatten(slice.call(arguments, 1));
|
407
|
+
return _.filter(array, function(value){ return !_.include(rest, value); });
|
409
408
|
};
|
410
409
|
|
411
410
|
// Zip together multiple lists into a single array -- elements that share
|
@@ -432,7 +431,7 @@
|
|
432
431
|
return array[i] === item ? i : -1;
|
433
432
|
}
|
434
433
|
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
435
|
-
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
|
434
|
+
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
|
436
435
|
return -1;
|
437
436
|
};
|
438
437
|
|
@@ -441,7 +440,7 @@
|
|
441
440
|
if (array == null) return -1;
|
442
441
|
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
443
442
|
var i = array.length;
|
444
|
-
while (i--) if (array[i] === item) return i;
|
443
|
+
while (i--) if (i in array && array[i] === item) return i;
|
445
444
|
return -1;
|
446
445
|
};
|
447
446
|
|
@@ -507,7 +506,7 @@
|
|
507
506
|
hasher || (hasher = _.identity);
|
508
507
|
return function() {
|
509
508
|
var key = hasher.apply(this, arguments);
|
510
|
-
return
|
509
|
+
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
|
511
510
|
};
|
512
511
|
};
|
513
512
|
|
@@ -579,7 +578,7 @@
|
|
579
578
|
// conditionally execute the original function.
|
580
579
|
_.wrap = function(func, wrapper) {
|
581
580
|
return function() {
|
582
|
-
var args = [func].concat(slice.call(arguments));
|
581
|
+
var args = [func].concat(slice.call(arguments, 0));
|
583
582
|
return wrapper.apply(this, args);
|
584
583
|
};
|
585
584
|
};
|
@@ -587,9 +586,9 @@
|
|
587
586
|
// Returns a function that is the composition of a list of functions, each
|
588
587
|
// consuming the return value of the function that follows.
|
589
588
|
_.compose = function() {
|
590
|
-
var funcs =
|
589
|
+
var funcs = arguments;
|
591
590
|
return function() {
|
592
|
-
var args =
|
591
|
+
var args = arguments;
|
593
592
|
for (var i = funcs.length - 1; i >= 0; i--) {
|
594
593
|
args = [funcs[i].apply(this, args)];
|
595
594
|
}
|
@@ -613,7 +612,7 @@
|
|
613
612
|
_.keys = nativeKeys || function(obj) {
|
614
613
|
if (obj !== Object(obj)) throw new TypeError('Invalid object');
|
615
614
|
var keys = [];
|
616
|
-
for (var key in obj) if (
|
615
|
+
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
|
617
616
|
return keys;
|
618
617
|
};
|
619
618
|
|
@@ -636,7 +635,7 @@
|
|
636
635
|
_.extend = function(obj) {
|
637
636
|
each(slice.call(arguments, 1), function(source) {
|
638
637
|
for (var prop in source) {
|
639
|
-
|
638
|
+
obj[prop] = source[prop];
|
640
639
|
}
|
641
640
|
});
|
642
641
|
return obj;
|
@@ -677,8 +676,8 @@
|
|
677
676
|
if (a._chain) a = a._wrapped;
|
678
677
|
if (b._chain) b = b._wrapped;
|
679
678
|
// Invoke a custom `isEqual` method if one is provided.
|
680
|
-
if (_.isFunction(a.isEqual)) return a.isEqual(b);
|
681
|
-
if (_.isFunction(b.isEqual)) return b.isEqual(a);
|
679
|
+
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
680
|
+
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
682
681
|
// Compare `[[Class]]` names.
|
683
682
|
var className = toString.call(a);
|
684
683
|
if (className != toString.call(b)) return false;
|
@@ -687,13 +686,11 @@
|
|
687
686
|
case '[object String]':
|
688
687
|
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
689
688
|
// equivalent to `new String("5")`.
|
690
|
-
return
|
689
|
+
return a == String(b);
|
691
690
|
case '[object Number]':
|
692
|
-
a = +a;
|
693
|
-
b = +b;
|
694
691
|
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
695
692
|
// other numeric values.
|
696
|
-
return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b);
|
693
|
+
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
|
697
694
|
case '[object Date]':
|
698
695
|
case '[object Boolean]':
|
699
696
|
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
@@ -733,20 +730,20 @@
|
|
733
730
|
}
|
734
731
|
} else {
|
735
732
|
// Objects with different constructors are not equivalent.
|
736
|
-
if (
|
733
|
+
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
|
737
734
|
// Deep compare objects.
|
738
735
|
for (var key in a) {
|
739
|
-
if (
|
736
|
+
if (_.has(a, key)) {
|
740
737
|
// Count the expected number of properties.
|
741
738
|
size++;
|
742
739
|
// Deep compare each member.
|
743
|
-
if (!(result =
|
740
|
+
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
|
744
741
|
}
|
745
742
|
}
|
746
743
|
// Ensure that both objects contain the same number of properties.
|
747
744
|
if (result) {
|
748
745
|
for (key in b) {
|
749
|
-
if (
|
746
|
+
if (_.has(b, key) && !(size--)) break;
|
750
747
|
}
|
751
748
|
result = !size;
|
752
749
|
}
|
@@ -765,7 +762,7 @@
|
|
765
762
|
// An "empty" object has no enumerable own-properties.
|
766
763
|
_.isEmpty = function(obj) {
|
767
764
|
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
|
768
|
-
for (var key in obj) if (
|
765
|
+
for (var key in obj) if (_.has(obj, key)) return false;
|
769
766
|
return true;
|
770
767
|
};
|
771
768
|
|
@@ -786,13 +783,12 @@
|
|
786
783
|
};
|
787
784
|
|
788
785
|
// Is a given variable an arguments object?
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
} else {
|
786
|
+
_.isArguments = function(obj) {
|
787
|
+
return toString.call(obj) == '[object Arguments]';
|
788
|
+
};
|
789
|
+
if (!_.isArguments(arguments)) {
|
794
790
|
_.isArguments = function(obj) {
|
795
|
-
return !!(obj &&
|
791
|
+
return !!(obj && _.has(obj, 'callee'));
|
796
792
|
};
|
797
793
|
}
|
798
794
|
|
@@ -842,6 +838,11 @@
|
|
842
838
|
return obj === void 0;
|
843
839
|
};
|
844
840
|
|
841
|
+
// Has own property?
|
842
|
+
_.has = function(obj, key) {
|
843
|
+
return hasOwnProperty.call(obj, key);
|
844
|
+
};
|
845
|
+
|
845
846
|
// Utility Functions
|
846
847
|
// -----------------
|
847
848
|
|
@@ -891,6 +892,17 @@
|
|
891
892
|
escape : /<%-([\s\S]+?)%>/g
|
892
893
|
};
|
893
894
|
|
895
|
+
// When customizing `templateSettings`, if you don't want to define an
|
896
|
+
// interpolation, evaluation or escaping regex, we need one that is
|
897
|
+
// guaranteed not to match.
|
898
|
+
var noMatch = /.^/;
|
899
|
+
|
900
|
+
// Within an interpolation, evaluation, or escaping, remove HTML escaping
|
901
|
+
// that had been previously added.
|
902
|
+
var unescape = function(code) {
|
903
|
+
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
|
904
|
+
};
|
905
|
+
|
894
906
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
895
907
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
896
908
|
// and correctly escapes quotes within interpolated code.
|
@@ -900,22 +912,29 @@
|
|
900
912
|
'with(obj||{}){__p.push(\'' +
|
901
913
|
str.replace(/\\/g, '\\\\')
|
902
914
|
.replace(/'/g, "\\'")
|
903
|
-
.replace(c.escape, function(match, code) {
|
904
|
-
return "',_.escape(" + code
|
915
|
+
.replace(c.escape || noMatch, function(match, code) {
|
916
|
+
return "',_.escape(" + unescape(code) + "),'";
|
905
917
|
})
|
906
|
-
.replace(c.interpolate, function(match, code) {
|
907
|
-
return "'," + code
|
918
|
+
.replace(c.interpolate || noMatch, function(match, code) {
|
919
|
+
return "'," + unescape(code) + ",'";
|
908
920
|
})
|
909
|
-
.replace(c.evaluate ||
|
910
|
-
return "');" + code.replace(
|
911
|
-
.replace(/[\r\n\t]/g, ' ') + ";__p.push('";
|
921
|
+
.replace(c.evaluate || noMatch, function(match, code) {
|
922
|
+
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
|
912
923
|
})
|
913
924
|
.replace(/\r/g, '\\r')
|
914
925
|
.replace(/\n/g, '\\n')
|
915
926
|
.replace(/\t/g, '\\t')
|
916
927
|
+ "');}return __p.join('');";
|
917
928
|
var func = new Function('obj', '_', tmpl);
|
918
|
-
|
929
|
+
if (data) return func(data, _);
|
930
|
+
return function(data) {
|
931
|
+
return func.call(this, data, _);
|
932
|
+
};
|
933
|
+
};
|
934
|
+
|
935
|
+
// Add a "chain" function, which will delegate to the wrapper.
|
936
|
+
_.chain = function(obj) {
|
937
|
+
return _(obj).chain();
|
919
938
|
};
|
920
939
|
|
921
940
|
// The OOP Wrapper
|
@@ -950,8 +969,11 @@
|
|
950
969
|
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
951
970
|
var method = ArrayProto[name];
|
952
971
|
wrapper.prototype[name] = function() {
|
953
|
-
|
954
|
-
|
972
|
+
var wrapped = this._wrapped;
|
973
|
+
method.apply(wrapped, arguments);
|
974
|
+
var length = wrapped.length;
|
975
|
+
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
|
976
|
+
return result(wrapped, this._chain);
|
955
977
|
};
|
956
978
|
});
|
957
979
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backbone-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-02-06 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &2157255900 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: 3.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2157255900
|
25
25
|
description: Ships backbone and underscore to your Rails 3.1 application through the
|
26
26
|
new asset pipeline. Rails 3.0 is supported via generators.
|
27
27
|
email:
|
@@ -60,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
60
|
version: 1.3.6
|
61
61
|
requirements: []
|
62
62
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.8.
|
63
|
+
rubygems_version: 1.8.11
|
64
64
|
signing_key:
|
65
65
|
specification_version: 3
|
66
66
|
summary: backbone and underscore for Rails
|