ember-resource 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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ember-resource.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Hedtek Ltd.; Mick Staugaard; Shajith Chacko; James A. Rosen; Zendesk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Ember Resource asset pipeline
2
+
3
+ A version of the ember-resource javascript library packaged for the asset pipeline in rails.
4
+
5
+ Original project is https://github.com/staugaard/ember-resource
6
+
7
+ Version taken at commit 3a67468faf03a2866303b640e75daceb4a60ac11
8
+
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'ember-resource'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install ember-resource
23
+
24
+ ## Usage
25
+
26
+ Simply add to your assets group in a rails application and then add the line
27
+ //= require ember-resource
28
+ to your application's javascript manifest
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/ember-resource/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Workman", "Hedtek Ltd."]
6
+ gem.email = ["gems@hedtek.com"]
7
+ gem.description = %q{Ember resource asset pipeline}
8
+ gem.summary = %q{Ember resource asset pipeline}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "ember-resource"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Ember::Resource::VERSION
17
+ end
@@ -0,0 +1,5 @@
1
+ module Ember
2
+ module Resource
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require "ember-resource/version"
2
+
3
+ module EmberResource
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,1175 @@
1
+ (function(undefined) {
2
+
3
+ window.Ember = window.Ember || window.SC;
4
+
5
+ var expandSchema, expandSchemaItem, createSchemaProperties,
6
+ mergeSchemas;
7
+
8
+ function isString(obj) {
9
+ return !!(obj === '' || (obj && obj !== String && obj.charCodeAt && obj.substr));
10
+ }
11
+
12
+ function isObject(obj) {
13
+ return obj === Object(obj);
14
+ }
15
+
16
+ function K() {};
17
+
18
+ Ember.Resource = Ember.Object.extend({
19
+ resourcePropertyWillChange: K,
20
+ resourcePropertyDidChange: K
21
+ });
22
+
23
+ Ember.Resource.deepSet = function(obj, path, value) {
24
+ if (Ember.typeOf(path) === 'string') {
25
+ Ember.Resource.deepSet(obj, path.split('.'), value);
26
+ return;
27
+ }
28
+
29
+ var key = path.shift();
30
+
31
+ if (path.length === 0) {
32
+ Ember.set(obj, key, value);
33
+ } else {
34
+ var newObj = Ember.get(obj, key);
35
+
36
+ if (newObj === null || newObj === undefined) {
37
+ newObj = {};
38
+ Ember.set(obj, key, newObj);
39
+ }
40
+
41
+ Ember.Resource.deepSet(newObj, path, value);
42
+ }
43
+ };
44
+
45
+ Ember.Resource.deepMerge = function(objA, objB) {
46
+ var oldValue, newValue;
47
+
48
+ for (var key in objB) {
49
+ if (objB.hasOwnProperty(key)) {
50
+ oldValue = Ember.get(objA, key);
51
+ newValue = Ember.get(objB, key);
52
+
53
+ if (Ember.typeOf(newValue) === 'object' && Ember.typeOf(oldValue) === 'object') {
54
+ Ember.Resource.deepMerge(oldValue, newValue);
55
+ } else {
56
+ Ember.set(objA, key, newValue);
57
+ }
58
+ }
59
+ }
60
+ };
61
+
62
+ Ember.Resource.AbstractSchemaItem = Ember.Object.extend({
63
+ name: Ember.required(String),
64
+ fetchable: Ember.required(Boolean),
65
+ getValue: Ember.required(Function),
66
+ setValue: Ember.required(Function),
67
+
68
+ dependencies: function() {
69
+ return ['data.' + this.get('path'), 'isExpired'];
70
+ }.property('path'),
71
+
72
+ data: function(instance) {
73
+ return Ember.get(instance, 'data');
74
+ },
75
+
76
+ type: function() {
77
+ var type = this.get('theType');
78
+ if (isString(type)) {
79
+ type = Ember.getPath(type);
80
+ if (type) {
81
+ this.set('theType', type);
82
+ } else {
83
+ type = this.get('theType');
84
+ }
85
+ }
86
+ return type;
87
+ }.property('theType'),
88
+
89
+ propertyFunction: function(name, value) {
90
+ var schemaItem = this.constructor.schema[name];
91
+ if (arguments.length === 2) {
92
+ this.resourcePropertyWillChange(name, value);
93
+ schemaItem.setValue.call(schemaItem, this, value);
94
+ value = schemaItem.getValue.call(schemaItem, this);
95
+ this.resourcePropertyDidChange(name, value);
96
+ } else {
97
+ value = schemaItem.getValue.call(schemaItem, this);
98
+ if ((value === undefined || Ember.get(this, 'isExpired')) && schemaItem.get('fetchable')) {
99
+ this.scheduleFetch();
100
+ }
101
+ }
102
+ return value;
103
+ },
104
+
105
+ property: function() {
106
+ return this.propertyFunction.property.apply(this.propertyFunction, this.get('dependencies')).cacheable();
107
+ },
108
+
109
+ toJSON: function(instance) {
110
+ return undefined;
111
+ }
112
+ });
113
+ Ember.Resource.AbstractSchemaItem.reopenClass({
114
+ create: function(name, schema) {
115
+ var instance = this._super.apply(this);
116
+ instance.set('name', name);
117
+ return instance;
118
+ }
119
+ });
120
+
121
+
122
+ Ember.Resource.SchemaItem = Ember.Resource.AbstractSchemaItem.extend({});
123
+
124
+ Ember.Resource.SchemaItem.reopenClass({
125
+ create: function(name, schema) {
126
+ var definition = schema[name];
127
+
128
+ if (definition instanceof Ember.Resource.AbstractSchemaItem) { return definition; }
129
+
130
+ var type;
131
+ if (definition === Number || definition === String || definition === Boolean || definition === Date || definition === Object) {
132
+ definition = {type: definition};
133
+ schema[name] = definition;
134
+ }
135
+
136
+ if(isObject(definition)) {
137
+ type = definition.type;
138
+ }
139
+
140
+ if (type) {
141
+ if (type.isEmberResource || Ember.typeOf(type) === 'string') { // a has-one association
142
+ return Ember.Resource.HasOneSchemaItem.create(name, schema);
143
+ } else if(type.isEmberResourceCollection) { // a has-many association
144
+ return Ember.Resource.HasManySchemaItem.create(name, schema);
145
+ } else { // a regular attribute
146
+ return Ember.Resource.AttributeSchemaItem.create(name, schema);
147
+ }
148
+ }
149
+ }
150
+ });
151
+
152
+ Ember.Resource.AttributeSchemaItem = Ember.Resource.AbstractSchemaItem.extend({
153
+ fetchable: true,
154
+ theType: Object,
155
+ path: Ember.required(String),
156
+
157
+ getValue: function(instance) {
158
+ var value;
159
+ var data = this.data(instance);
160
+ if (data) {
161
+ value = Ember.getPath(data, this.get('path'));
162
+ }
163
+
164
+ if (this.typeCast) {
165
+ value = this.typeCast(value);
166
+ }
167
+
168
+ return value;
169
+ },
170
+
171
+ setValue: function(instance, value) {
172
+ var data = this.data(instance);
173
+ if (!data) return;
174
+
175
+ if (this.typeCast) {
176
+ value = this.typeCast(value);
177
+ }
178
+ if (value !== null && value !== undefined && Ember.typeOf(value.toJSON) == 'function') {
179
+ value = value.toJSON();
180
+ }
181
+ Ember.Resource.deepSet(data, this.get('path'), value);
182
+ },
183
+
184
+ toJSON: function(instance) {
185
+ return Ember.get(instance, this.name);
186
+ }
187
+ });
188
+
189
+ Ember.Resource.AttributeSchemaItem.reopenClass({
190
+ create: function(name, schema) {
191
+ var definition = schema[name];
192
+ var instance;
193
+
194
+ if (this === Ember.Resource.AttributeSchemaItem) {
195
+ switch (definition.type) {
196
+ case Number:
197
+ return Ember.Resource.NumberAttributeSchemaItem.create(name, schema);
198
+ case String:
199
+ return Ember.Resource.StringAttributeSchemaItem.create(name, schema);
200
+ case Boolean:
201
+ return Ember.Resource.BooleanAttributeSchemaItem.create(name, schema);
202
+ case Date:
203
+ return Ember.Resource.DateAttributeSchemaItem.create(name, schema);
204
+ default:
205
+ instance = this._super.apply(this, arguments);
206
+ instance.set('fetchable', name !== 'id');
207
+ instance.set('path', definition.path || name);
208
+ return instance;
209
+ }
210
+ }
211
+ else {
212
+ instance = this._super.apply(this, arguments);
213
+ instance.set('fetchable', name !== 'id');
214
+ instance.set('path', definition.path || name);
215
+ return instance;
216
+ }
217
+ }
218
+ });
219
+
220
+ Ember.Resource.NumberAttributeSchemaItem = Ember.Resource.AttributeSchemaItem.extend({
221
+ theType: Number,
222
+ typeCast: function(value) {
223
+ if (isNaN(value)) {
224
+ value = undefined;
225
+ }
226
+
227
+ if (value === undefined || value === null || Ember.typeOf(value) === 'number') {
228
+ return value;
229
+ } else {
230
+ return Number(value);
231
+ }
232
+ }
233
+ });
234
+
235
+ Ember.Resource.StringAttributeSchemaItem = Ember.Resource.AttributeSchemaItem.extend({
236
+ theType: String,
237
+ typeCast: function(value) {
238
+ if (value === undefined || value === null || Ember.typeOf(value) === 'string') {
239
+ return value;
240
+ } else {
241
+ return '' + value;
242
+ }
243
+ }
244
+ });
245
+
246
+ Ember.Resource.BooleanAttributeSchemaItem = Ember.Resource.AttributeSchemaItem.extend({
247
+ theType: Boolean,
248
+ typeCast: function(value) {
249
+ if (value === undefined || value === null || Ember.typeOf(value) === 'boolean') {
250
+ return value;
251
+ } else {
252
+ return value === 'true';
253
+ }
254
+ }
255
+ });
256
+
257
+ Ember.Resource.DateAttributeSchemaItem = Ember.Resource.AttributeSchemaItem.extend({
258
+ theType: Date,
259
+ typeCast: function(value) {
260
+ if (value === undefined || value === null || Ember.typeOf(value) === 'date') {
261
+ return value;
262
+ } else {
263
+ return new Date(value);
264
+ }
265
+ },
266
+ toJSON: function(instance) {
267
+ var value = Ember.get(instance, this.name);
268
+ return value ? value.toJSON() : value;
269
+ }
270
+ });
271
+
272
+ Ember.Resource.HasOneSchemaItem = Ember.Resource.AbstractSchemaItem.extend({
273
+ fetchable: true
274
+ });
275
+ Ember.Resource.HasOneSchemaItem.reopenClass({
276
+ create: function(name, schema) {
277
+ var definition = schema[name];
278
+ if (this === Ember.Resource.HasOneSchemaItem) {
279
+ if (definition.nested) {
280
+ return Ember.Resource.HasOneNestedSchemaItem.create(name, schema);
281
+ } else {
282
+ return Ember.Resource.HasOneRemoteSchemaItem.create(name, schema);
283
+ }
284
+ }
285
+ else {
286
+ var instance = this._super.apply(this, arguments);
287
+ instance.set('theType', definition.type);
288
+ if (definition.parse) {
289
+ instance.set('parse', definition.parse);
290
+ }
291
+ return instance;
292
+ }
293
+ }
294
+ });
295
+
296
+ Ember.Resource.HasOneNestedSchemaItem = Ember.Resource.HasOneSchemaItem.extend({
297
+ getValue: function(instance) {
298
+ var data = this.data(instance);
299
+ if (!data) return;
300
+ var type = this.get('type');
301
+ var value = Ember.getPath(data, this.get('path'));
302
+ if (value) {
303
+ value = (this.get('parse') || type.parse).call(type, Ember.copy(value));
304
+ return type.create({}, value);
305
+ }
306
+ return value;
307
+ },
308
+
309
+ setValue: function(instance, value) {
310
+ var data = this.data(instance);
311
+ if (!data) return;
312
+
313
+ if (value instanceof this.get('type')) {
314
+ value = Ember.get(value, 'data');
315
+ }
316
+
317
+ Ember.Resource.deepSet(data, this.get('path'), value);
318
+ },
319
+
320
+ toJSON: function(instance) {
321
+ var value = Ember.get(instance, this.name);
322
+ return value ? value.toJSON() : value;
323
+ }
324
+ });
325
+ Ember.Resource.HasOneNestedSchemaItem.reopenClass({
326
+ create: function(name, schema) {
327
+ var definition = schema[name];
328
+ var instance = this._super.apply(this, arguments);
329
+ instance.set('path', definition.path || name);
330
+
331
+ var id_name = name + '_id';
332
+ if (!schema[id_name]) {
333
+ schema[id_name] = {type: Number, association: instance };
334
+ schema[id_name] = Ember.Resource.HasOneNestedIdSchemaItem.create(id_name, schema);
335
+ }
336
+
337
+ return instance;
338
+ }
339
+ });
340
+ Ember.Resource.HasOneNestedIdSchemaItem = Ember.Resource.AbstractSchemaItem.extend({
341
+ fetchable: true,
342
+ theType: Number,
343
+ getValue: function(instance) {
344
+ return instance.getPath(this.get('path'));
345
+ },
346
+ setValue: function(instance, value) {
347
+ Ember.set(instance, this.getPath('association.name'), {id: value});
348
+ }
349
+ });
350
+ Ember.Resource.HasOneNestedIdSchemaItem.reopenClass({
351
+ create: function(name, schema) {
352
+ var definition = schema[name];
353
+ var instance = this._super.apply(this, arguments);
354
+ instance.set('association', definition.association);
355
+ instance.set('path', definition.association.get('path') + '.id');
356
+ return instance;
357
+ }
358
+ });
359
+
360
+
361
+ Ember.Resource.HasOneRemoteSchemaItem = Ember.Resource.HasOneSchemaItem.extend({
362
+ getValue: function(instance) {
363
+ var data = this.data(instance);
364
+ if (!data) return;
365
+ var id = Ember.getPath(data, this.get('path'));
366
+ if (id) {
367
+ return this.get('type').create({}, {id: id});
368
+ }
369
+ },
370
+
371
+ setValue: function(instance, value) {
372
+ var data = this.data(instance);
373
+ if (!data) return;
374
+ var id = Ember.get(value || {}, 'id');
375
+ Ember.Resource.deepSet(data, this.get('path'), id);
376
+ }
377
+ });
378
+ Ember.Resource.HasOneRemoteSchemaItem.reopenClass({
379
+ create: function(name, schema) {
380
+ var definition = schema[name];
381
+ var instance = this._super.apply(this, arguments);
382
+ var path = definition.path || name + '_id';
383
+ instance.set('path', path);
384
+
385
+ if (!schema[path]) {
386
+ schema[path] = Number;
387
+ schema[path] = Ember.Resource.SchemaItem.create(path, schema);
388
+ }
389
+
390
+ return instance;
391
+ }
392
+ });
393
+
394
+
395
+ Ember.Resource.HasManySchemaItem = Ember.Resource.AbstractSchemaItem.extend({
396
+ itemType: function() {
397
+ var type = this.get('theItemType');
398
+ if (isString(type)) {
399
+ type = Ember.getPath(type);
400
+ if (type) {
401
+ this.set('theItemType', type);
402
+ } else {
403
+ type = this.get('theItemType');
404
+ }
405
+ }
406
+ return type;
407
+ }.property('theItemType')
408
+ });
409
+ Ember.Resource.HasManySchemaItem.reopenClass({
410
+ create: function(name, schema) {
411
+ var definition = schema[name];
412
+ if (this === Ember.Resource.HasManySchemaItem) {
413
+ if (definition.url) {
414
+ return Ember.Resource.HasManyRemoteSchemaItem.create(name, schema);
415
+ } else if (definition.nested) {
416
+ return Ember.Resource.HasManyNestedSchemaItem.create(name, schema);
417
+ } else {
418
+ return Ember.Resource.HasManyInArraySchemaItem.create(name, schema);
419
+ }
420
+ } else {
421
+ var instance = this._super.apply(this, arguments);
422
+ instance.set('theType', definition.type);
423
+ instance.set('theItemType', definition.itemType);
424
+ if (definition.parse) {
425
+ instance.set('parse', definition.parse);
426
+ }
427
+ return instance;
428
+ }
429
+ }
430
+ });
431
+
432
+ Ember.Resource.HasManyRemoteSchemaItem = Ember.Resource.HasManySchemaItem.extend({
433
+ fetchable: false,
434
+ dependencies: ['id', 'isInitializing'],
435
+ getValue: function(instance) {
436
+ if (Ember.get(instance, 'isInitializing')) return;
437
+
438
+ var options = {
439
+ type: this.get('itemType')
440
+ };
441
+
442
+ if (this.get('parse')) options.parse = this.get('parse');
443
+
444
+ var url = this.url(instance);
445
+ if (url) {
446
+ options.url = url;
447
+ } else {
448
+ options.content = [];
449
+ }
450
+
451
+ return this.get('type').create(options);
452
+ },
453
+
454
+ setValue: function(instance, value) {
455
+ throw('you can not set a remote has many association');
456
+ }
457
+ });
458
+ Ember.Resource.HasManyRemoteSchemaItem.reopenClass({
459
+ create: function(name, schema) {
460
+ var definition = schema[name];
461
+
462
+ var instance = this._super.apply(this, arguments);
463
+
464
+ if (Ember.typeOf(definition.url) === 'function') {
465
+ instance.url = definition.url;
466
+ } else {
467
+ instance.url = function(obj) {
468
+ var id = obj.get('id');
469
+ if (id) {
470
+ return definition.url.fmt(id);
471
+ }
472
+ };
473
+ }
474
+
475
+ return instance;
476
+ }
477
+ });
478
+
479
+ Ember.Resource.HasManyNestedSchemaItem = Ember.Resource.HasManySchemaItem.extend({
480
+ fetchable: true,
481
+ getValue: function(instance) {
482
+ var data = this.data(instance);
483
+ if (!data) return;
484
+ data = Ember.getPath(data, this.get('path'));
485
+ if (data === undefined || data === null) return data;
486
+ data = Ember.copy(data);
487
+
488
+ var options = {
489
+ type: this.get('itemType'),
490
+ content: data
491
+ };
492
+
493
+ if (this.get('parse')) options.parse = this.get('parse');
494
+
495
+ return this.get('type').create(options);
496
+ },
497
+
498
+ setValue: function(instance, value) {
499
+ },
500
+
501
+ toJSON: function(instance) {
502
+ var value = Ember.get(instance, this.name);
503
+ return value ? value.toJSON() : value;
504
+ }
505
+ });
506
+ Ember.Resource.HasManyNestedSchemaItem.reopenClass({
507
+ create: function(name, schema) {
508
+ var definition = schema[name];
509
+
510
+ var instance = this._super.apply(this, arguments);
511
+ instance.set('path', definition.path || name);
512
+
513
+ return instance;
514
+ }
515
+ });
516
+
517
+ Ember.Resource.HasManyInArraySchemaItem = Ember.Resource.HasManySchemaItem.extend({
518
+ fetchable: true,
519
+ getValue: function(instance) {
520
+ var data = this.data(instance);
521
+ if (!data) return;
522
+ data = Ember.getPath(data, this.get('path'));
523
+ if (data === undefined || data === null) return data;
524
+
525
+
526
+ return this.get('type').create({
527
+ type: this.get('itemType'),
528
+ content: data.map(function(id) { return {id: id}; })
529
+ });
530
+ },
531
+
532
+ setValue: function(instance, value) {
533
+ },
534
+
535
+ toJSON: function(instance) {
536
+ var value = Ember.get(instance, this.name);
537
+ return value ? value.mapProperty('id') : value;
538
+ }
539
+ });
540
+ Ember.Resource.HasManyInArraySchemaItem.reopenClass({
541
+ create: function(name, schema) {
542
+ var definition = schema[name];
543
+
544
+ var instance = this._super.apply(this, arguments);
545
+ instance.set('path', definition.path || name + '_ids');
546
+
547
+ return instance;
548
+ }
549
+ });
550
+
551
+
552
+ // Gives custom error handlers access to the resource object.
553
+ // 1. `this` will refer to the Ember.Resource object.
554
+ // 2. `resource` will be passed as the last argument
555
+ //
556
+ // function errorHandler() {
557
+ // this; // the Ember.Resource
558
+ // }
559
+ //
560
+ // function errorHandler(jqXHR, textStatus, errorThrown, resource) {
561
+ // resource; // another way to reference the resource object
562
+ // }
563
+ //
564
+ var errorHandlerWithModel = function(errorHandler, resource) {
565
+ return function() {
566
+ var args = Array.prototype.slice.call(arguments, 0);
567
+ args.push(resource);
568
+ errorHandler.apply(resource, args);
569
+ };
570
+ };
571
+
572
+ Ember.Resource.ajax = function(options) {
573
+ options.dataType = options.dataType || 'json';
574
+ options.type = options.type || 'GET';
575
+
576
+ if (!options.error && Ember.Resource.errorHandler) {
577
+ if (options.resource) {
578
+ options.error = errorHandlerWithModel(Ember.Resource.errorHandler, options.resource);
579
+ delete options.resource;
580
+ } else {
581
+ options.error = Ember.Resource.errorHandler;
582
+ }
583
+ }
584
+
585
+ return $.ajax(options);
586
+ };
587
+
588
+ Ember.Resource.Lifecycle = {
589
+ INITIALIZING: 0,
590
+ UNFETCHED: 10,
591
+ EXPIRING: 20,
592
+ EXPIRED: 30,
593
+ FETCHING: 40,
594
+ FETCHED: 50,
595
+ SAVING: 60,
596
+ DESTROYING: 70,
597
+ DESTROYED: 80,
598
+
599
+ clock: Ember.Object.create({
600
+ now: new Date(),
601
+
602
+ tick: function() {
603
+ Ember.Resource.Lifecycle.clock.set('now', new Date());
604
+ },
605
+
606
+ start: function() {
607
+ this.stop();
608
+ Ember.Resource.Lifecycle.clock.set('timer', setInterval(Ember.Resource.Lifecycle.clock.tick, 10000));
609
+ },
610
+
611
+ stop: function() {
612
+ var timer = Ember.Resource.Lifecycle.clock.get('timer');
613
+ if (timer) {
614
+ clearInterval(timer);
615
+ }
616
+ }
617
+ }),
618
+
619
+ classMixin: Ember.Mixin.create({
620
+ create: function(options, data) {
621
+ options = options || {};
622
+ options.resourceState = Ember.Resource.Lifecycle.INITIALIZING;
623
+
624
+ var instance = this._super.apply(this, arguments);
625
+
626
+ if (Ember.get(instance, 'resourceState') === Ember.Resource.Lifecycle.INITIALIZING) {
627
+ Ember.set(instance, 'resourceState', Ember.Resource.Lifecycle.UNFETCHED);
628
+ }
629
+
630
+ return instance;
631
+ }
632
+ }),
633
+
634
+ prototypeMixin: Ember.Mixin.create({
635
+ expireIn: 60 * 5,
636
+ resourceState: 0,
637
+ autoFetch: true,
638
+
639
+ init: function() {
640
+ this._super.apply(this, arguments);
641
+
642
+ var self = this;
643
+
644
+ var updateExpiry = function() {
645
+ var expireAt = new Date();
646
+ expireAt.setSeconds(expireAt.getSeconds() + Ember.get(self, 'expireIn'));
647
+ Ember.set(self, 'expireAt', expireAt);
648
+ };
649
+
650
+ Ember.addListener(this, 'willFetch', this, function() {
651
+ Ember.set(self, 'resourceState', Ember.Resource.Lifecycle.FETCHING);
652
+ updateExpiry();
653
+ });
654
+
655
+ Ember.addListener(this, 'didFetch', this, function() {
656
+ Ember.set(self, 'resourceState', Ember.Resource.Lifecycle.FETCHED);
657
+ updateExpiry();
658
+ });
659
+
660
+ var resourceStateBeforeSave;
661
+ Ember.addListener(this, 'willSave', this, function() {
662
+ resourceStateBeforeSave = Ember.get(self, 'resourceState');
663
+ Ember.set(self, 'resourceState', Ember.Resource.Lifecycle.SAVING);
664
+ });
665
+
666
+ Ember.addListener(this, 'didSave', this, function() {
667
+ Ember.set(self, 'resourceState', resourceStateBeforeSave || Ember.Resource.Lifecycle.UNFETCHED);
668
+ });
669
+ },
670
+
671
+ isFetchable: function() {
672
+ var state = Ember.get(this, 'resourceState');
673
+ return state == Ember.Resource.Lifecycle.UNFETCHED || state === Ember.Resource.Lifecycle.EXPIRED;
674
+ }.property('resourceState').cacheable(),
675
+
676
+ isAutoFetchable: function() {
677
+ return this.get('isFetchable') && this.get('autoFetch');
678
+ }.property('isFetchable', 'autoFetch').cacheable(),
679
+
680
+ isInitializing: function() {
681
+ return (Ember.get(this, 'resourceState') || Ember.Resource.Lifecycle.INITIALIZING) === Ember.Resource.Lifecycle.INITIALIZING;
682
+ }.property('resourceState').cacheable(),
683
+
684
+ isFetching: function() {
685
+ return (Ember.get(this, 'resourceState')) === Ember.Resource.Lifecycle.FETCHING;
686
+ }.property('resourceState').cacheable(),
687
+
688
+ isFetched: function() {
689
+ return (Ember.get(this, 'resourceState')) === Ember.Resource.Lifecycle.FETCHED;
690
+ }.property('resourceState').cacheable(),
691
+
692
+ isSavable: function() {
693
+ var state = Ember.get(this, 'resourceState');
694
+ var unsavableState = [
695
+ Ember.Resource.Lifecycle.INITIALIZING,
696
+ Ember.Resource.Lifecycle.FETCHING,
697
+ Ember.Resource.Lifecycle.SAVING,
698
+ Ember.Resource.Lifecycle.DESTROYING
699
+ ];
700
+
701
+ return state && !unsavableState.contains(state);
702
+ }.property('resourceState').cacheable(),
703
+
704
+ scheduleFetch: function() {
705
+ if (Ember.get(this, 'isAutoFetchable')) {
706
+ Ember.run.next(this, this.fetch);
707
+ }
708
+ },
709
+
710
+ expire: function() {
711
+ Ember.run.next(this, function() {
712
+ Ember.set(this, 'expireAt', new Date());
713
+ Ember.Resource.Lifecycle.clock.tick();
714
+ });
715
+ },
716
+
717
+ updateIsExpired: function() {
718
+ var isExpired = Ember.get(this, 'resourceState') === Ember.Resource.Lifecycle.EXPIRED;
719
+ if (isExpired) return true;
720
+
721
+ var expireAt = Ember.get(this, 'expireAt');
722
+ if (expireAt) {
723
+ var now = Ember.Resource.Lifecycle.clock.get('now');
724
+ isExpired = expireAt.getTime() <= now.getTime();
725
+ }
726
+
727
+ if (isExpired !== Ember.get(this, 'isExpired')) {
728
+ Ember.set(this, 'isExpired', isExpired);
729
+ }
730
+ }.observes('Ember.Resource.Lifecycle.clock.now', 'expireAt', 'resourceState'),
731
+
732
+ isExpired: function(name, value) {
733
+ if (value) {
734
+ Ember.set(this, 'resourceState', Ember.Resource.Lifecycle.EXPIRED);
735
+ }
736
+ return value;
737
+ }.property().cacheable()
738
+ })
739
+ };
740
+ Ember.Resource.Lifecycle.clock.start();
741
+
742
+ Ember.Resource.reopen({
743
+ isEmberResource: true,
744
+
745
+ updateWithApiData: function(json) {
746
+ var data = Ember.get(this, 'data');
747
+ Ember.beginPropertyChanges(data);
748
+ Ember.Resource.deepMerge(data, this.constructor.parse(json));
749
+ Ember.endPropertyChanges(data);
750
+ },
751
+
752
+ willFetch: function() {},
753
+ didFetch: function() {},
754
+ willSave: function() {},
755
+ didSave: function() {},
756
+
757
+ fetch: function() {
758
+ if (!Ember.get(this, 'isFetchable')) return $.when();
759
+
760
+ var url = this.resourceURL();
761
+
762
+ if (!url) return;
763
+
764
+ var self = this;
765
+
766
+ if (this.deferedFetch && !Ember.get(this, 'isExpired')) return this.deferedFetch;
767
+
768
+ self.willFetch.call(self);
769
+ Ember.sendEvent(self, 'willFetch');
770
+
771
+ this.deferedFetch = Ember.Resource.ajax({
772
+ url: url,
773
+ success: function(json) {
774
+ self.updateWithApiData(json);
775
+ }
776
+ });
777
+
778
+ this.deferedFetch.always(function() {
779
+ self.didFetch.call(self);
780
+ Ember.sendEvent(self, 'didFetch');
781
+ });
782
+
783
+ return this.deferedFetch;
784
+ },
785
+
786
+ resourceURL: function() {
787
+ return this.constructor.resourceURL(this);
788
+ },
789
+
790
+ // Turn this resource into a JSON object to be saved via AJAX. Override
791
+ // this method to produce different syncing behavior.
792
+ toJSON: function() {
793
+ var json = {};
794
+ var schemaItem, path, value;
795
+ for (var name in this.constructor.schema) {
796
+ if (this.constructor.schema.hasOwnProperty(name)) {
797
+ schemaItem = this.constructor.schema[name];
798
+ if (schemaItem instanceof Ember.Resource.AbstractSchemaItem) {
799
+ path = schemaItem.get('path');
800
+ value = schemaItem.toJSON(this);
801
+ if (value !== undefined) {
802
+ Ember.Resource.deepSet(json, path, value);
803
+ }
804
+ }
805
+ }
806
+ }
807
+
808
+ return json;
809
+ },
810
+
811
+ isNew: function() {
812
+ return !Ember.get(this, 'id');
813
+ }.property('id').cacheable(),
814
+
815
+ save: function(options) {
816
+ options = options || {};
817
+ if (!Ember.get(this, 'isSavable')) return false;
818
+
819
+ var ajaxOptions = {
820
+ contentType: 'application/json',
821
+ data: JSON.stringify(this.toJSON()),
822
+ resource: this
823
+ };
824
+
825
+ if (Ember.get(this, 'isNew')) {
826
+ ajaxOptions.type = 'POST';
827
+ ajaxOptions.url = this.constructor.resourceURL();
828
+ } else {
829
+ ajaxOptions.type = 'PUT';
830
+ ajaxOptions.url = this.resourceURL();
831
+ }
832
+
833
+ var self = this;
834
+
835
+ self.willSave.call(self);
836
+ Ember.sendEvent(self, 'willSave');
837
+
838
+ var deferedSave = Ember.Resource.ajax(ajaxOptions);
839
+
840
+ deferedSave.done(function(data, status, response) {
841
+ var location = response.getResponseHeader('Location');
842
+ if (location) {
843
+ var id = self.constructor.idFromURL(location);
844
+ if (id) {
845
+ Ember.set(self, 'id', id);
846
+ }
847
+ }
848
+
849
+ if (options.update !== false && Ember.typeOf(data) === 'object') {
850
+ self.updateWithApiData(data);
851
+ }
852
+ });
853
+
854
+ deferedSave.always(function() {
855
+ self.didSave.call(self);
856
+ Ember.sendEvent(self, 'didSave');
857
+ });
858
+
859
+ return deferedSave;
860
+ },
861
+
862
+ destroy: function() {
863
+ var previousState = Ember.get(this, 'resourceState'), self = this;
864
+ Ember.set(this, 'resourceState', Ember.Resource.Lifecycle.DESTROYING);
865
+ return Ember.Resource.ajax({
866
+ type: 'DELETE',
867
+ url: this.resourceURL(),
868
+ resource: this
869
+ }).done(function() {
870
+ Ember.set(self, 'resourceState', Ember.Resource.Lifecycle.DESTROYED);
871
+ }).fail(function() {
872
+ Ember.set(self, 'resourceState', previousState);
873
+ });
874
+ }
875
+ }, Ember.Resource.Lifecycle.prototypeMixin);
876
+
877
+ expandSchema = function(schema) {
878
+ for (var name in schema) {
879
+ if (schema.hasOwnProperty(name)) {
880
+ schema[name] = Ember.Resource.SchemaItem.create(name, schema);
881
+ }
882
+ }
883
+
884
+ return schema;
885
+ };
886
+
887
+ mergeSchemas = function(childSchema, parentSchema) {
888
+ var schema = Ember.copy(parentSchema || {});
889
+
890
+ for (var name in childSchema) {
891
+ if (childSchema.hasOwnProperty(name)) {
892
+ if (schema.hasOwnProperty(name)) {
893
+ throw("Schema item '" + name + "' is already defined");
894
+ }
895
+
896
+ schema[name] = childSchema[name];
897
+ }
898
+ }
899
+
900
+ return schema;
901
+ };
902
+
903
+ createSchemaProperties = function(schema) {
904
+ var properties = {}, schemaItem;
905
+
906
+ for (var propertyName in schema) {
907
+ if (schema.hasOwnProperty(propertyName)) {
908
+ properties[propertyName] = schema[propertyName].property();
909
+ }
910
+ }
911
+
912
+ return properties;
913
+ };
914
+
915
+ Ember.Resource.reopenClass({
916
+ isEmberResource: true,
917
+ schema: {},
918
+
919
+ baseClass: function() {
920
+ if (this === Ember.Resource) {
921
+ return null;
922
+ } else {
923
+ return this.baseResourceClass || this;
924
+ }
925
+ },
926
+
927
+ subclassFor: function(options, data) {
928
+ return this;
929
+ },
930
+
931
+ // Create an instance of this resource. If `options` includes an
932
+ // `id`, first check the identity map and return the existing resource
933
+ // with that ID if found.
934
+ create: function(options, data) {
935
+ data = data || {};
936
+ options = options || {};
937
+
938
+ var klass = this.subclassFor(options, data);
939
+
940
+ if (klass === this) {
941
+ var instance;
942
+ this.identityMap = this.identityMap || {};
943
+
944
+ var id = data.id || options.id;
945
+ if (id && !options.skipIdentityMap) {
946
+ id = id.toString();
947
+ instance = this.identityMap[id];
948
+
949
+ if (!instance) {
950
+ this.identityMap[id] = instance = this._super.call(this, { data: data });
951
+ } else {
952
+ instance.updateWithApiData(data);
953
+ }
954
+ } else {
955
+ instance = this._super.call(this, { data: data });
956
+ }
957
+
958
+ delete options.data;
959
+
960
+ Ember.beginPropertyChanges(instance);
961
+ var mixin = {};
962
+ var hasMixin = false;
963
+ for (var name in options) {
964
+ if (options.hasOwnProperty(name)) {
965
+ if (this.schema[name]) {
966
+ instance.set(name, options[name]);
967
+ } else {
968
+ mixin[name] = options[name];
969
+ hasMixin = true;
970
+ }
971
+ }
972
+ }
973
+ if (hasMixin) {
974
+ instance.reopen(mixin);
975
+ }
976
+ Ember.endPropertyChanges(instance);
977
+
978
+ return instance;
979
+ } else {
980
+ return klass.create(options, data);
981
+ }
982
+ },
983
+
984
+ // Parse JSON -- likely returned from an AJAX call -- into the
985
+ // properties for an instance of this resource. Override this method
986
+ // to produce different parsing behavior.
987
+ parse: function(json) {
988
+ return json;
989
+ },
990
+
991
+ // Define a resource class.
992
+ //
993
+ // Parameters:
994
+ //
995
+ // * `schema` -- the properties schema for the resource class
996
+ // * `url` -- either a function that returns the URL for syncing
997
+ // resources or a string. If the latter, a string of the form
998
+ // "/widgets" is turned into a function that returns "/widgets" if
999
+ // the Widget's ID is not present and "/widgets/{id}" if it is.
1000
+ // * `parse` -- the function used to parse JSON returned from AJAX
1001
+ // calls into the resource properties. By default, simply returns
1002
+ // the JSON.
1003
+ define: function(options) {
1004
+ options = options || {};
1005
+ var schema = expandSchema(options.schema);
1006
+ schema = mergeSchemas(schema, this.schema);
1007
+
1008
+ var klass = this.extend(createSchemaProperties(schema));
1009
+
1010
+ var classOptions = {
1011
+ schema: schema
1012
+ };
1013
+
1014
+ if (this !== Ember.Resource) {
1015
+ classOptions.baseResourceClass = this.baseClass() || this;
1016
+ }
1017
+
1018
+ if (options.url) {
1019
+ classOptions.url = options.url;
1020
+ }
1021
+
1022
+ if (options.parse) {
1023
+ classOptions.parse = options.parse;
1024
+ }
1025
+
1026
+ klass.reopenClass(classOptions);
1027
+
1028
+ return klass;
1029
+ },
1030
+
1031
+ resourceURL: function(instance) {
1032
+ if (Ember.typeOf(this.url) == 'function') {
1033
+ return this.url(instance);
1034
+ } else if (this.url) {
1035
+ if (instance) {
1036
+ var id = Ember.get(instance, 'id');
1037
+ if (id && (Ember.typeOf(id) !== 'number' || id > 0)) {
1038
+ return this.url + '/' + id;
1039
+ }
1040
+ } else {
1041
+ return this.url;
1042
+ }
1043
+ }
1044
+ },
1045
+
1046
+ idFromURL: function(url) {
1047
+ var regex;
1048
+ if (!this.schema.id) return;
1049
+
1050
+ if (this.schema.id.get('type') === Number) {
1051
+ regex = /\/(\d+)(\.\w+)?$/;
1052
+ } else {
1053
+ regex = /\/([^\/\.]+)(\.\w+)?$/;
1054
+ }
1055
+
1056
+ var match = (url || '').match(regex);
1057
+ if (match) {
1058
+ return match[1];
1059
+ }
1060
+ }
1061
+ }, Ember.Resource.Lifecycle.classMixin);
1062
+
1063
+ Ember.ResourceCollection = Ember.ArrayProxy.extend({
1064
+ isEmberResourceCollection: true,
1065
+ type: Ember.required(),
1066
+ fetch: function() {
1067
+ if (!Ember.get(this, 'isFetchable')) return $.when();
1068
+
1069
+ if (!this.prePopulated) {
1070
+ var self = this;
1071
+
1072
+ if (this.deferedFetch && !Ember.get(this, 'isExpired')) return this.deferedFetch;
1073
+
1074
+ Ember.sendEvent(self, 'willFetch');
1075
+
1076
+ this.deferedFetch = this._fetch(function(json) {
1077
+ Ember.set(self, 'content', self.parse(json));
1078
+ });
1079
+
1080
+ this.deferedFetch.always(function() {
1081
+ Ember.sendEvent(self, 'didFetch');
1082
+ });
1083
+ }
1084
+ return this.deferedFetch;
1085
+ },
1086
+ _resolveType: function() {
1087
+ if (isString(this.type)) {
1088
+ var type = Ember.getPath(this.type);
1089
+ if (type) this.type = type;
1090
+ }
1091
+ },
1092
+ _fetch: function(callback) {
1093
+ this._resolveType();
1094
+ return Ember.Resource.ajax({
1095
+ url: this.resolveUrl(),
1096
+ success: callback
1097
+ });
1098
+ },
1099
+ resolveUrl: function() {
1100
+ return this.get('url') || this.type.resourceURL();
1101
+ },
1102
+ instantiateItems: function(items) {
1103
+ this._resolveType();
1104
+ return items.map(function(item) {
1105
+ if (item instanceof this.type) {
1106
+ return item;
1107
+ } else {
1108
+ return this.type.create({}, item);
1109
+ }
1110
+ }, this);
1111
+ },
1112
+ parse: function(json) {
1113
+ this._resolveType();
1114
+ if (Ember.typeOf(this.type.parse) == 'function') {
1115
+ return json.map(this.type.parse);
1116
+ }
1117
+ else {
1118
+ return json;
1119
+ }
1120
+ },
1121
+ length: function() {
1122
+ var content = Ember.get(this, 'content');
1123
+ var length = content ? Ember.get(content, 'length') : 0;
1124
+ if (length === 0 || Ember.get(this, 'isExpired')) this.scheduleFetch();
1125
+ return length;
1126
+ }.property('content.length', 'resourceState', 'isExpired').cacheable(),
1127
+ content: function(name, value) {
1128
+ if (arguments.length === 2) { // setter
1129
+ return this.instantiateItems(value);
1130
+ }
1131
+ }.property().cacheable(),
1132
+
1133
+ autoFetchOnExpiry: function() {
1134
+ if (Ember.get(this, 'isExpired') && Ember.get(this, 'hasArrayObservers')) {
1135
+ this.fetch();
1136
+ }
1137
+ }.observes('isExpired', 'hasArrayObservers'),
1138
+
1139
+ toJSON: function() {
1140
+ return this.map(function(item) {
1141
+ return item.toJSON();
1142
+ })
1143
+ }
1144
+ }, Ember.Resource.Lifecycle.prototypeMixin);
1145
+
1146
+ Ember.ResourceCollection.reopenClass({
1147
+ isEmberResourceCollection: true,
1148
+ create: function(options) {
1149
+ options = options || {};
1150
+ var content = options.content;
1151
+ delete options.content;
1152
+
1153
+ options.prePopulated = !! content;
1154
+
1155
+ var instance;
1156
+
1157
+ if (!options.prePopulated && options.url) {
1158
+ this.identityMap = this.identityMap || {};
1159
+ var identity = [options.type, options.url];
1160
+ instance = this.identityMap[identity] || this._super.call(this, options);
1161
+ this.identityMap[identity] = instance;
1162
+ }
1163
+
1164
+ if (!instance) {
1165
+ instance = this._super.call(this, options);
1166
+
1167
+ if (content) {
1168
+ Ember.set(instance, 'content', instance.parse(content));
1169
+ }
1170
+ }
1171
+
1172
+ return instance;
1173
+ }
1174
+ }, Ember.Resource.Lifecycle.classMixin);
1175
+ }());
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ember-resource
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Workman
9
+ - Hedtek Ltd.
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-03-26 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: Ember resource asset pipeline
16
+ email:
17
+ - gems@hedtek.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - LICENSE
25
+ - README.md
26
+ - Rakefile
27
+ - ember-resource.gemspec
28
+ - lib/ember-resource.rb
29
+ - lib/ember-resource/version.rb
30
+ - vendor/assets/javascripts/ember-resource.js
31
+ homepage: ''
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.16
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Ember resource asset pipeline
55
+ test_files: []