js-model-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in js-model-rails.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Work with models in your JavaScript in your Rails (3.1)
2
+
3
+ At the moment all this Gem does is bundle the latest [js-model](https://github.com/benpickles/js-model) and set `include_root_in_json = false` - which is something I always forget to do.
4
+
5
+ ## Usage
6
+
7
+ Gemfile:
8
+
9
+ gem 'js-model-rails'
10
+
11
+ app/assets/javascripts/application.js:
12
+
13
+ //= require js-model
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "js-model/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "js-model-rails"
7
+ s.version = JsModel::VERSION
8
+ s.authors = ["Ben Pickles"]
9
+ s.email = ["spideryoung@gmail.com"]
10
+ s.homepage = "https://github.com/benpickles/js-model-rails"
11
+ s.summary = 'Work with models in your JavaScript in your Rails'
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = "js-model-rails"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'railties', '~> 3.0'
22
+ end
@@ -0,0 +1 @@
1
+ //= require ./js-model
@@ -0,0 +1,688 @@
1
+ /* js-model JavaScript library, version 0.10.1
2
+ * (c) 2010-2011 Ben Pickles
3
+ *
4
+ * Released under MIT license.
5
+ */
6
+ var Model = function(name, func) {
7
+ // The model constructor.
8
+ var model = function(attributes) {
9
+ this.attributes = jQuery.extend({}, attributes)
10
+ this.changes = {};
11
+ this.errors = new Model.Errors(this);
12
+ this.uid = [name, Model.UID.generate()].join("-")
13
+ if (jQuery.isFunction(this.initialize)) this.initialize()
14
+ };
15
+
16
+ // Use module functionality to extend itself onto the constructor. Meta!
17
+ Model.Module.extend.call(model, Model.Module)
18
+
19
+ model._name = name
20
+ model.collection = []
21
+ model.unique_key = "id"
22
+ model
23
+ .extend(Model.Callbacks)
24
+ .extend(Model.ClassMethods)
25
+
26
+ model.prototype = new Model.Base
27
+ model.prototype.constructor = model
28
+
29
+ if (jQuery.isFunction(func)) func.call(model, model, model.prototype)
30
+
31
+ return model;
32
+ };
33
+
34
+ Model.Callbacks = {
35
+ bind: function(event, callback) {
36
+ this.callbacks = this.callbacks || {}
37
+ this.callbacks[event] = this.callbacks[event] || [];
38
+ this.callbacks[event].push(callback);
39
+ return this;
40
+ },
41
+
42
+ trigger: function(name, data) {
43
+ this.callbacks = this.callbacks || {}
44
+
45
+ var callbacks = this.callbacks[name];
46
+
47
+ if (callbacks) {
48
+ for (var i = 0; i < callbacks.length; i++) {
49
+ callbacks[i].apply(this, data || []);
50
+ }
51
+ }
52
+
53
+ return this;
54
+ },
55
+
56
+ unbind: function(event, callback) {
57
+ this.callbacks = this.callbacks || {}
58
+
59
+ if (callback) {
60
+ var callbacks = this.callbacks[event] || [];
61
+
62
+ for (var i = 0; i < callbacks.length; i++) {
63
+ if (callbacks[i] === callback) {
64
+ this.callbacks[event].splice(i, 1);
65
+ }
66
+ }
67
+ } else {
68
+ delete this.callbacks[event];
69
+ }
70
+
71
+ return this;
72
+ }
73
+ };
74
+
75
+ Model.ClassMethods = {
76
+ add: function(model) {
77
+ var id = model.id()
78
+
79
+ if (jQuery.inArray(model, this.collection) === -1 && !(id && this.find(id))) {
80
+ this.collection.push(model)
81
+ this.trigger("add", [model])
82
+ }
83
+
84
+ return this;
85
+ },
86
+
87
+ all: function() {
88
+ return this.collection.slice()
89
+ },
90
+
91
+ // Convenience method to allow a simple method of chaining class methods.
92
+ chain: function(collection) {
93
+ return jQuery.extend({}, this, { collection: collection })
94
+ },
95
+
96
+ count: function() {
97
+ return this.all().length;
98
+ },
99
+
100
+ detect: function(func) {
101
+ var all = this.all(),
102
+ model
103
+
104
+ for (var i = 0, length = all.length; i < length; i++) {
105
+ model = all[i]
106
+ if (func.call(model, model, i)) return model
107
+ }
108
+ },
109
+
110
+ each: function(func, context) {
111
+ var all = this.all()
112
+
113
+ for (var i = 0, length = all.length; i < length; i++) {
114
+ func.call(context || all[i], all[i], i, all)
115
+ }
116
+
117
+ return this;
118
+ },
119
+
120
+ find: function(id) {
121
+ return this.detect(function() {
122
+ return this.id() == id;
123
+ })
124
+ },
125
+
126
+ first: function() {
127
+ return this.all()[0]
128
+ },
129
+
130
+ load: function(callback) {
131
+ if (this._persistence) {
132
+ var self = this
133
+
134
+ this._persistence.read(function(models) {
135
+ for (var i = 0, length = models.length; i < length; i++) {
136
+ self.add(models[i])
137
+ }
138
+
139
+ if (callback) callback.call(self, models)
140
+ })
141
+ }
142
+
143
+ return this
144
+ },
145
+
146
+ last: function() {
147
+ var all = this.all();
148
+ return all[all.length - 1]
149
+ },
150
+
151
+ map: function(func, context) {
152
+ var all = this.all()
153
+ var values = []
154
+
155
+ for (var i = 0, length = all.length; i < length; i++) {
156
+ values.push(func.call(context || all[i], all[i], i, all))
157
+ }
158
+
159
+ return values
160
+ },
161
+
162
+ persistence: function(adapter) {
163
+ if (arguments.length == 0) {
164
+ return this._persistence
165
+ } else {
166
+ var options = Array.prototype.slice.call(arguments, 1)
167
+ options.unshift(this)
168
+ this._persistence = adapter.apply(adapter, options)
169
+ return this
170
+ }
171
+ },
172
+
173
+ pluck: function(attribute) {
174
+ var all = this.all()
175
+ var plucked = []
176
+
177
+ for (var i = 0, length = all.length; i < length; i++) {
178
+ plucked.push(all[i].attr(attribute))
179
+ }
180
+
181
+ return plucked
182
+ },
183
+
184
+ remove: function(model) {
185
+ var index
186
+
187
+ for (var i = 0, length = this.collection.length; i < length; i++) {
188
+ if (this.collection[i] === model) {
189
+ index = i
190
+ break
191
+ }
192
+ }
193
+
194
+ if (index != undefined) {
195
+ this.collection.splice(index, 1);
196
+ this.trigger("remove", [model]);
197
+ return true;
198
+ } else {
199
+ return false;
200
+ }
201
+ },
202
+
203
+ reverse: function() {
204
+ return this.chain(this.all().reverse())
205
+ },
206
+
207
+ select: function(func, context) {
208
+ var all = this.all(),
209
+ selected = [],
210
+ model
211
+
212
+ for (var i = 0, length = all.length; i < length; i++) {
213
+ model = all[i]
214
+ if (func.call(context || model, model, i, all)) selected.push(model)
215
+ }
216
+
217
+ return this.chain(selected);
218
+ },
219
+
220
+ sort: function(func) {
221
+ var sorted = this.all().sort(func)
222
+ return this.chain(sorted);
223
+ },
224
+
225
+ sortBy: function(attribute_or_func) {
226
+ var is_func = jQuery.isFunction(attribute_or_func)
227
+ var extract = function(model) {
228
+ return attribute_or_func.call(model)
229
+ }
230
+
231
+ return this.sort(function(a, b) {
232
+ var a_attr = is_func ? extract(a) : a.attr(attribute_or_func)
233
+ var b_attr = is_func ? extract(b) : b.attr(attribute_or_func)
234
+
235
+ if (a_attr < b_attr) {
236
+ return -1
237
+ } else if (a_attr > b_attr) {
238
+ return 1
239
+ } else {
240
+ return 0
241
+ }
242
+ })
243
+ },
244
+
245
+ use: function(klass) {
246
+ var args = Array.prototype.slice.call(arguments, 1)
247
+ args.unshift(this)
248
+ klass.apply(this, args)
249
+ return this
250
+ }
251
+ };
252
+
253
+ Model.Errors = function(model) {
254
+ this.errors = {};
255
+ this.model = model;
256
+ };
257
+
258
+ Model.Errors.prototype = {
259
+ add: function(attribute, message) {
260
+ if (!this.errors[attribute]) this.errors[attribute] = [];
261
+ this.errors[attribute].push(message);
262
+ return this
263
+ },
264
+
265
+ all: function() {
266
+ return this.errors;
267
+ },
268
+
269
+ clear: function() {
270
+ this.errors = {};
271
+ return this
272
+ },
273
+
274
+ each: function(func) {
275
+ for (var attribute in this.errors) {
276
+ for (var i = 0; i < this.errors[attribute].length; i++) {
277
+ func.call(this, attribute, this.errors[attribute][i]);
278
+ }
279
+ }
280
+ return this
281
+ },
282
+
283
+ on: function(attribute) {
284
+ return this.errors[attribute] || [];
285
+ },
286
+
287
+ size: function() {
288
+ var count = 0;
289
+ this.each(function() { count++; });
290
+ return count;
291
+ }
292
+ };
293
+
294
+ Model.InstanceMethods = {
295
+ asJSON: function() {
296
+ return this.attr()
297
+ },
298
+
299
+ attr: function(name, value) {
300
+ if (arguments.length === 0) {
301
+ // Combined attributes/changes object.
302
+ return jQuery.extend({}, this.attributes, this.changes);
303
+ } else if (arguments.length === 2) {
304
+ // Don't write to attributes yet, store in changes for now.
305
+ if (this.attributes[name] === value) {
306
+ // Clean up any stale changes.
307
+ delete this.changes[name];
308
+ } else {
309
+ this.changes[name] = value;
310
+ }
311
+ return this;
312
+ } else if (typeof name === "object") {
313
+ // Mass-assign attributes.
314
+ for (var key in name) {
315
+ this.attr(key, name[key]);
316
+ }
317
+ return this;
318
+ } else {
319
+ // Changes take precedent over attributes.
320
+ return (name in this.changes) ?
321
+ this.changes[name] :
322
+ this.attributes[name];
323
+ }
324
+ },
325
+
326
+ callPersistMethod: function(method, callback) {
327
+ var self = this;
328
+
329
+ // Automatically manage adding and removing from the model's Collection.
330
+ var manageCollection = function() {
331
+ if (method === "destroy") {
332
+ self.constructor.remove(self)
333
+ } else {
334
+ self.constructor.add(self)
335
+ }
336
+ };
337
+
338
+ // Wrap the existing callback in this function so we always manage the
339
+ // collection and trigger events from here rather than relying on the
340
+ // persistence adapter to do it for us. The persistence adapter is
341
+ // only required to execute the callback with a single argument - a
342
+ // boolean to indicate whether the call was a success - though any
343
+ // other arguments will also be forwarded to the original callback.
344
+ var wrappedCallback = function(success) {
345
+ if (success) {
346
+ // Merge any changes into attributes and clear changes.
347
+ self.merge(self.changes).reset();
348
+
349
+ // Add/remove from collection if persist was successful.
350
+ manageCollection();
351
+
352
+ // Trigger the event before executing the callback.
353
+ self.trigger(method);
354
+ }
355
+
356
+ // Store the return value of the callback.
357
+ var value;
358
+
359
+ // Run the supplied callback.
360
+ if (callback) value = callback.apply(self, arguments);
361
+
362
+ return value;
363
+ };
364
+
365
+ if (this.constructor._persistence) {
366
+ this.constructor._persistence[method](this, wrappedCallback);
367
+ } else {
368
+ wrappedCallback.call(this, true);
369
+ }
370
+ },
371
+
372
+ destroy: function(callback) {
373
+ this.callPersistMethod("destroy", callback);
374
+ return this;
375
+ },
376
+
377
+ id: function() {
378
+ return this.attributes[this.constructor.unique_key];
379
+ },
380
+
381
+ merge: function(attributes) {
382
+ jQuery.extend(this.attributes, attributes);
383
+ return this;
384
+ },
385
+
386
+ newRecord: function() {
387
+ return this.id() === undefined
388
+ },
389
+
390
+ reset: function() {
391
+ this.errors.clear();
392
+ this.changes = {};
393
+ return this;
394
+ },
395
+
396
+ save: function(callback) {
397
+ if (this.valid()) {
398
+ var method = this.newRecord() ? "create" : "update";
399
+ this.callPersistMethod(method, callback);
400
+ } else if (callback) {
401
+ callback(false);
402
+ }
403
+
404
+ return this;
405
+ },
406
+
407
+ valid: function() {
408
+ this.errors.clear();
409
+ this.validate();
410
+ return this.errors.size() === 0;
411
+ },
412
+
413
+ validate: function() {
414
+ return this;
415
+ }
416
+ };
417
+
418
+ Model.localStorage = function(klass) {
419
+ if (!window.localStorage) {
420
+ return {
421
+ create: function(model, callback) {
422
+ callback(true)
423
+ },
424
+
425
+ destroy: function(model, callback) {
426
+ callback(true)
427
+ },
428
+
429
+ read: function(callback) {
430
+ callback([])
431
+ },
432
+
433
+ update: function(model, callback) {
434
+ callback(true)
435
+ }
436
+ }
437
+ }
438
+
439
+ var collection_uid = [klass._name, "collection"].join("-")
440
+ var readIndex = function() {
441
+ var data = localStorage[collection_uid]
442
+ return data ? JSON.parse(data) : []
443
+ }
444
+ var writeIndex = function(uids) {
445
+ localStorage.setItem(collection_uid, JSON.stringify(uids))
446
+ }
447
+ var addToIndex = function(uid) {
448
+ var uids = readIndex()
449
+
450
+ if (jQuery.inArray(uid, uids) === -1) {
451
+ uids.push(uid)
452
+ writeIndex(uids)
453
+ }
454
+ }
455
+ var removeFromIndex = function(uid) {
456
+ var uids = readIndex()
457
+ var index = jQuery.inArray(uid, uids)
458
+
459
+ if (index > -1) {
460
+ uids.splice(index, 1)
461
+ writeIndex(uids)
462
+ }
463
+ }
464
+ var store = function(model) {
465
+ localStorage.setItem(model.uid, JSON.stringify(model.asJSON()))
466
+ addToIndex(model.uid)
467
+ }
468
+
469
+ return {
470
+ create: function(model, callback) {
471
+ store(model)
472
+ callback(true)
473
+ },
474
+
475
+ destroy: function(model, callback) {
476
+ localStorage.removeItem(model.uid)
477
+ removeFromIndex(model.uid)
478
+ callback(true)
479
+ },
480
+
481
+ read: function(callback) {
482
+ if (!callback) return false
483
+
484
+ var existing_uids = klass.map(function() { return this.uid })
485
+ var uids = readIndex()
486
+ var models = []
487
+ var attributes, model, uid
488
+
489
+ for (var i = 0, length = uids.length; i < length; i++) {
490
+ uid = uids[i]
491
+
492
+ if (jQuery.inArray(uid, existing_uids) == -1) {
493
+ attributes = JSON.parse(localStorage[uid])
494
+ model = new klass(attributes)
495
+ model.uid = uid
496
+ models.push(model)
497
+ }
498
+ }
499
+
500
+ callback(models)
501
+ },
502
+
503
+ update: function(model, callback) {
504
+ store(model)
505
+ callback(true)
506
+ }
507
+ }
508
+ };
509
+
510
+ Model.Log = function() {
511
+ if (window.console) window.console.log.apply(window.console, arguments);
512
+ };
513
+
514
+ Model.Module = {
515
+ extend: function(obj) {
516
+ jQuery.extend(this, obj)
517
+ return this
518
+ },
519
+
520
+ include: function(obj) {
521
+ jQuery.extend(this.prototype, obj)
522
+ return this
523
+ }
524
+ };
525
+
526
+ Model.REST = function(klass, resource, methods) {
527
+ var PARAM_NAME_MATCHER = /:([\w\d]+)/g;
528
+ var resource_param_names = (function() {
529
+ var resource_param_names = []
530
+ var param_name
531
+
532
+ while ((param_name = PARAM_NAME_MATCHER.exec(resource)) !== null) {
533
+ resource_param_names.push(param_name[1])
534
+ }
535
+
536
+ return resource_param_names
537
+ })()
538
+
539
+ return jQuery.extend({
540
+ path: function(model) {
541
+ var path = resource;
542
+ jQuery.each(resource_param_names, function(i, param) {
543
+ path = path.replace(":" + param, model.attributes[param]);
544
+ });
545
+ return path;
546
+ },
547
+
548
+ create: function(model, callback) {
549
+ return this.xhr('POST', this.create_path(model), model, callback);
550
+ },
551
+
552
+ create_path: function(model) {
553
+ return this.path(model);
554
+ },
555
+
556
+ destroy: function(model, callback) {
557
+ return this.xhr('DELETE', this.destroy_path(model), model, callback);
558
+ },
559
+
560
+ destroy_path: function(model) {
561
+ return this.update_path(model);
562
+ },
563
+
564
+ params: function(model) {
565
+ var params;
566
+ if (model) {
567
+ var attributes = model.asJSON()
568
+ delete attributes[model.constructor.unique_key];
569
+ params = {};
570
+ params[model.constructor._name.toLowerCase()] = attributes;
571
+ } else {
572
+ params = null;
573
+ }
574
+ if(jQuery.ajaxSettings.data){
575
+ params = jQuery.extend({}, jQuery.ajaxSettings.data, params)
576
+ }
577
+ return JSON.stringify(params)
578
+ },
579
+
580
+ read: function(callback) {
581
+ return this.xhr("GET", this.read_path(), null, function(success, xhr, data) {
582
+ data = jQuery.makeArray(data)
583
+ var models = []
584
+
585
+ for (var i = 0, length = data.length; i < length; i++) {
586
+ models.push(new klass(data[i]))
587
+ }
588
+
589
+ callback(models)
590
+ })
591
+ },
592
+
593
+ read_path: function() {
594
+ return resource
595
+ },
596
+
597
+ update: function(model, callback) {
598
+ return this.xhr('PUT', this.update_path(model), model, callback);
599
+ },
600
+
601
+ update_path: function(model) {
602
+ return [this.path(model), model.id()].join('/');
603
+ },
604
+
605
+ xhr: function(method, url, model, callback) {
606
+ var self = this;
607
+ var data = method == 'GET' ? undefined : this.params(model);
608
+
609
+ return jQuery.ajax({
610
+ type: method,
611
+ url: url,
612
+ contentType: "application/json",
613
+ dataType: "json",
614
+ data: data,
615
+ dataFilter: function(data, type) {
616
+ return /\S/.test(data) ? data : undefined;
617
+ },
618
+ complete: function(xhr, textStatus) {
619
+ self.xhrComplete(xhr, textStatus, model, callback)
620
+ }
621
+ });
622
+ },
623
+
624
+ xhrComplete: function(xhr, textStatus, model, callback) {
625
+ // Allow custom handlers to be defined per-HTTP status code.
626
+ var handler = Model.REST["handle" + xhr.status]
627
+ if (handler) handler.call(this, xhr, textStatus, model)
628
+
629
+ var success = textStatus === "success"
630
+ var data = Model.REST.parseResponseData(xhr)
631
+
632
+ // Remote data is the definitive source, update model.
633
+ if (success && model && data) model.attr(data)
634
+
635
+ if (callback) callback.call(model, success, xhr, data)
636
+ }
637
+ }, methods)
638
+ };
639
+
640
+ // TODO: Remove in v1 if it ever gets there.
641
+ Model.RestPersistence = Model.REST;
642
+
643
+ // Rails' preferred failed validation response code, assume the response
644
+ // contains errors and replace current model errors with them.
645
+ Model.REST.handle422 = function(xhr, textStatus, model) {
646
+ var data = Model.REST.parseResponseData(xhr);
647
+
648
+ if (data) {
649
+ model.errors.clear()
650
+
651
+ for (var attribute in data) {
652
+ for (var i = 0; i < data[attribute].length; i++) {
653
+ model.errors.add(attribute, data[attribute][i])
654
+ }
655
+ }
656
+ }
657
+ };
658
+
659
+ Model.REST.parseResponseData = function(xhr) {
660
+ try {
661
+ return /\S/.test(xhr.responseText) ?
662
+ jQuery.parseJSON(xhr.responseText) :
663
+ undefined;
664
+ } catch(e) {
665
+ Model.Log(e);
666
+ }
667
+ };
668
+
669
+ Model.UID = {
670
+ counter: 0,
671
+
672
+ generate: function() {
673
+ return [new Date().valueOf(), this.counter++].join("-")
674
+ },
675
+
676
+ reset: function() {
677
+ this.counter = 0
678
+ return this
679
+ }
680
+ };
681
+
682
+ Model.VERSION = "0.10.1";
683
+
684
+ Model.Base = (function() {
685
+ function Base() {}
686
+ Base.prototype = jQuery.extend({}, Model.Callbacks, Model.InstanceMethods)
687
+ return Base
688
+ })();
@@ -0,0 +1 @@
1
+ require 'js-model'
data/lib/js-model.rb ADDED
@@ -0,0 +1,5 @@
1
+ module JsModel
2
+ class Engine < Rails::Engine
3
+ config.active_record.include_root_in_json = false
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module JsModel
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: js-model-rails
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ben Pickles
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-28 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: railties
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ version: "3.0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: Work with models in your JavaScript in your Rails
37
+ email:
38
+ - spideryoung@gmail.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - .gitignore
47
+ - Gemfile
48
+ - README.md
49
+ - Rakefile
50
+ - js-model-rails.gemspec
51
+ - lib/assets/javascripts/js-model/index.js
52
+ - lib/assets/javascripts/js-model/js-model.js
53
+ - lib/js-model-rails.rb
54
+ - lib/js-model.rb
55
+ - lib/js-model/version.rb
56
+ has_rdoc: true
57
+ homepage: https://github.com/benpickles/js-model-rails
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 3
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project: js-model-rails
86
+ rubygems_version: 1.6.2
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Work with models in your JavaScript in your Rails
90
+ test_files: []
91
+