ember-resource 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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: []