Skip to content

Commit

Permalink
Closes #122
Browse files Browse the repository at this point in the history
Closes #122
  • Loading branch information
juwara0 committed Mar 31, 2015
1 parent d378cd4 commit a53c94d
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 57 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Models have `ajax` specified as default, so you don't need to do this unless you

sl-ember-store adapters always return [Ember Promise Proxies](http://emberjs.com/api/classes/Ember.PromiseProxyMixin.html).
If you request a single object then you will get an `Ember.ObjectProxy` with the promise proxy mixin applied. Requests for
Multiple records will return an `Ember.ArrayProxy` with the promise proxiy mixin applied.
Multiple records will return an `Ember.ArrayProxy` with the promise proxy mixin applied.

### Ajax adapter
The `ajax` adapter uses [ic-ajax](https://github.com/instructure/ic-ajax) to make `xhr` requests to your remote api.
Expand Down Expand Up @@ -135,25 +135,34 @@ Foo.reopenClass({
return response.result;
}
},
post: '/superFooPost'
post: '/superFooPost',
put: '/superFooPut',
delete: 'superFooDelete'
},
'boringFoo': {
url: '/boringFoo',
serializer: someSerializer
},
'superBoringFoo': '/superBoringFoo',
'superBoringFoo': '/superBoringFoo'
}
});

export default Foo;
```
In the example above, the `superFoo:post` endpoint will use the default serializer.
In the example above, the `superFoo:post` endpoint will use the default serializer. Please note that specific endpoint
actions ( get, post, put, and delete ) MUST be in lowercase.
All HTTP verbs on the `boringfoo` endpoint will use the `someSerializer` function as their serializer.
All HTTP verbs on the `superBoringFoo` endpoint will use the default serializer.

Models should always have a `url` specified. Further urls can be specified in the `endpoints` object. Urls and
Serializers can be specified on a per endpoint/action basis and will default to the top level url and serializer.

The creation of a new record, one in which an id has not been assigned by your API, will result in a POST action.
Updating a record, one in which an id has been assigned by your API, will result in a PUT action. The model's save()
method is responsible for both the creation and update of a record. In both cases, the request body payload wil be passed through.
The difference in whether a POST or a PUT is sent is dependent on whether the record already has an id assigned to it from the API.
The deletion of a record requires that a record has an id already assigned to it from the API.

If you find you need an `inflection` service to support your api, we recommend
you use [Ember-Inflector](https://github.com/stefanpenner/ember-inflector). You
can then use `Ember.Inflector` in your serializers and models.
Expand Down
47 changes: 30 additions & 17 deletions addon/adapters/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,24 @@ export default Adapter.extend({
* Delete record
*
* @function deleteRecord
* @param {string} url - The URL to send the DELETE command to
* @param {string} url - The URL to send the DELETE command to
* @param {integer} id - The model record's ID
* @throws {Ember.assert}
* @returns {Ember.RSVP} Promise
*/
deleteRecord: function( url, id ) {
var queryObj = {
url : url,
var _self = this,
queryObj;

Ember.assert( 'A url is required to delete a model', url );
Ember.assert( 'An id property is required to delete a model', id );

queryObj = {
url : url + '/' + id,
type : 'DELETE',
data : JSON.stringify({ id: id }),
context : this
},
_self = this;

Ember.assert( 'A url is required to delete a model', url );
};

this.runPreQueryHooks( queryObj );

Expand All @@ -115,26 +118,36 @@ export default Adapter.extend({
} , 'sl-ember-store.ajaxAdapter:deleteRecord' );
},

/**
/**
* Save record
*
* @function save
* @param {string} url - The URL to send the POST command to
* @param {string} url - The URL to send the POST/PUT command to (depends on whether an id exists or not).
* @param {object} content - Data to save
* @throws {Ember.assert}
* @returns {Ember.RSVP} Promise
*/
save: function( url, content ) {
var promise,
queryObj = {
url : url,
type : 'POST',
data : JSON.stringify( content ),
context : this
},
_self = this;
var _self = this,
promise,
queryObj;

Ember.assert( 'A url property is required to save a model', url );
Ember.assert(
'save() expects parameter to be an Object',
'object' === typeof content && !Array.isArray( content )
);

if ( Ember.get( content, 'id' ) ) {
url = url + '/' + Ember.get( content, 'id' );
}

queryObj = {
url : url,
type : ( content.id && !Ember.isEmpty( content.id ) ) ? 'PUT' : 'POST',
data : JSON.stringify( content ),
context : this
};

this.runPreQueryHooks( queryObj );

Expand Down
7 changes: 5 additions & 2 deletions addon/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ var Model = Ember.ObjectProxy.extend({
*/
save: function( options ) {
var data,
type,
endpoint;

options = options || {};
endpoint = this.constructor.getUrlForEndpointAction( options.endpoint, 'post' );
data = this.get( 'content' );
type = ( !Ember.isEmpty( data.id ) ) ? 'put' : 'post';
endpoint = this.constructor.getUrlForEndpointAction( options.endpoint, type );

Ember.assert( 'Endpoint must be configured on ' + this.toString() + ' before calling save.', endpoint );

Expand All @@ -47,8 +49,9 @@ var Model = Ember.ObjectProxy.extend({
endpoint = this.constructor.getUrlForEndpointAction( options.endpoint, 'delete' );

Ember.assert( 'Enpoint must be configured on ' + this.toString() + ' before calling deleteRecord.', endpoint );
Ember.assert( 'deleteRecord() requires an id', this.get( 'id' ) );

return this.container.lookup( 'adapter:'+this.constructor.adapter ).deleteRecord( endpoint, this.get( 'id' ) )
return this.container.lookup( 'adapter:' + this.constructor.adapter ).deleteRecord( endpoint, this.get( 'id' ) )
.then( Ember.run.bind( this, function() {
Ember.run( this, 'destroy' );
}), null, 'sl-ember-store.model:deleteRecord' );
Expand Down
108 changes: 95 additions & 13 deletions tests/unit/adapters/ajax-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,32 +175,114 @@ asyncTest( 'find should not throw error if response is empty', function( assert
});
});

test( 'save', function( assert ){
var foo = Foo.create({ test: 'foo', 'bar': { id: 1, quiz: 'bar' } });
test( 'save() should send PUT since an id exists', function( assert ){
var foo = Foo.create({ id: 3 });
response = ajaxAdapter.save( '/foo', foo );
assert.ok( requestSpy.calledOnce, 'should call icAjax request once' );
assert.equal( requestSpy.args[0][0].url, '/foo/3', 'should call icAjax with correct url');
assert.equal( requestSpy.args[0][0].type, 'PUT', 'should call icAjax with PUT method');
assert.equal( typeof requestSpy.args[0][0].data, 'string', 'icAjax should return a string');
});

test( 'save() should send POST since NO id exists', function( assert ){
var foo = Foo.create({ label: 'My name' });
response = ajaxAdapter.save( '/foo', foo );
assert.ok( requestSpy.calledOnce, 'should call icAjax request once' );
assert.equal( requestSpy.args[0][0].url, '/foo', 'should call icAjax with correct url');
assert.equal( requestSpy.args[0][0].type, 'POST', 'should call icAjax with correct method');
assert.equal( requestSpy.args[0][0].type, 'POST', 'should call icAjax with POST method');
assert.equal( typeof requestSpy.args[0][0].data, 'string', 'icAjax should return a string');
});

test( 'save, should call $.ajax with the correct arguments', function( assert ){
var foo = Foo.create({ test: 'foo', 'bar': { id: 1, quiz: 'bar' } });
test( 'save() should call $.ajax with the correct arguments', function( assert ){
var foo = Foo.create({ id: 3 });
response = ajaxAdapter.save( '/foo', foo );
assert.ok( requestSpy.calledOnce, 'request called once' );
assert.equal( requestSpy.args[0][0].url, '/foo' );
assert.equal( requestSpy.args[0][0].type, 'POST' );
assert.equal( typeof requestSpy.args[0][0].data, 'string' );
assert.equal( typeof requestSpy.args[0][0].data, 'string' ); // update to check json_decode also in 184 and 193
assert.ok( response.then, 'response is a promise' );
});

test( 'delete, should call icAjax.request once', function( assert ){
var foo = Foo.create({ id: 1, test: 'foo', 'bar': { id: 1, quiz: 'bar' } });
response = ajaxAdapter.deleteRecord( '/foo', 1 );
test( 'save() requires an Object to be provided as the second parameter', function( assert ) {

// Empty parameter

var assertionThrown = false;

try {
ajaxAdapter.save();
} catch( error ) {
assertionThrown = true;
}

assert.ok( assertionThrown, 'Parameter was empty' );

// Number parameter

assertionThrown = false;

try {
ajaxAdapter.save( 'test/', 4 );
} catch( error ) {
assertionThrown = true;
}

assert.ok( assertionThrown, 'Parameter was a Number' );

// Array Parameter

assertionThrown = false;

try {
ajaxAdapter.save( 'test/', [] );
} catch( error ) {
assertionThrown = true;
}

assert.ok( assertionThrown, 'Parameter was an Array' );

// Function

assertionThrown = false;

try {
ajaxAdapter.save( 'test/', function(){} );
} catch( error ) {
assertionThrown = true;
}

assert.ok( assertionThrown, 'Parameter was a Function' );

// String Parameter

assertionThrown = false;

try {
ajaxAdapter.save( 'test/', 'test' );
} catch( error ) {
assertionThrown = true;
}

assert.ok( assertionThrown, 'Parameter was a String' );

// Object Parameter

assertionThrown = false;

try {
ajaxAdapter.save( 'test/', {} );
} catch( error ) {
assertionThrown = true;
}

assert.ok( !assertionThrown, 'Parameter was an Object' );
});

test( 'deleteRecord() should call icAjax.request once', function( assert ){
var foo = Foo.create({ id: 3 });
response = ajaxAdapter.deleteRecord( '/foo', 3 );

assert.ok( requestSpy.calledOnce );
assert.equal( requestSpy.args[0][0].url, '/foo', 'should call icAjax with correct url');
assert.equal( requestSpy.args[0][0].url, '/foo/3', 'should call icAjax with correct url');
assert.equal( requestSpy.args[0][0].type, 'DELETE', 'should call icAjax with correct url');
assert.equal( typeof requestSpy.args[0][0].data, 'string', 'icAjax should return a string');
assert.ok( response.then, 'response is a proxy' );
});
});
Loading

0 comments on commit a53c94d

Please sign in to comment.