The default REST adapter and serializer

As we saw in the section, Understanding the Ember Data identity map – DS.Store, all interactions with the server are handled by the adapter. Adapter in ember-data understands how to connect to the server, in order to fetch and save the records from/to the server. Similarly, serializer, as the name implies, is responsible for serializing or converting the response received from the server to DS.Model objects.

By default in ember-data, the store will use the DS.RESTAdapter and DS.RESTSerializer. As the name implies, the DS.RESTAdapter is based on the REST (Representational State Transfer) principles to load or save a record from/to the server. The DS.RESTAdapter communicates with the HTTP server by sending and receiving JSON (JavaScript Object Notation) data, via AJAX requests. If the server with which the Ember.js application talks with, understands and follows the URL conventions according to REST principles, then your ember-data communication will work flawlessly without any customization to the adapter or serializer.

Let us see what the URL endpoints for our book model (chapter-6/example1/app/models/book.js) discussed when used with different actions:

Action

HTTP Verb

URL

Find

GET

/books/2

Find All

GET

/books

Update

PUT

/books/2

Create

POST

/books

Delete

DELETE

/books/2

The URL endpoints which DS.RESTAdapter makes call to when used with different actions

As you can see, the DS.RESTAdapter uses plural form of the model name to create the URLs, hence the URL for the book model becomes /books/. Another important thing to note here is the use of HTTP verbs while making calls to the server. To fetch a record or records from the server, the adapter uses GET verb or action; to create a new record, the POST verb is used; to update an existing record, the PUT verb is used; and to delete a record, the DELETE verb is used.

Now that we have understood how the adapter creates the URLs to communicate with the server, let's understand how we need to send the JSON response back from the server, in order for it to be compliant with the DS.RESTAdapter and DS.RESTSerializer.

In order to understand the JSON response from the server, let us take the look at the book model:

import DS from "ember-data";

export default DS.Model.extend({
  title: DS.attr('string'),
  isbn: DS.attr('string'),
  pages: DS.attr('number'),
  description: DS.attr('string'),
  authors: DS.hasMany('author',{ async: true }),
  publisher: DS.belongsTo('publisher',{ async: true }),
  reviews: DS.hasMany("review",{ async: true })
});

The book model is present at chapter6/example1/app/models/book.js

Here, you can see that the book model contains both attributes and relationships. When we make a call to fetch a particular book from the server using store.find("book",1), this gets translated to GET /books/1 that should return a JSON response, which can be de-serialized into book model object. The JSON response returned from the server should be of the following structure:

{
  "book": {
    "id":1
    "title": "Ember.js Essentials",
    "isbn": "ISBN1",
    "pages": 180,
    "description": "Ember.js essentials to master",
    "authors": [1],
    "publisher": 1,
    "reviews": [1,2,3]
  }
}

Here, you can see that we return a JSON response that has a top-level key that is the same as the name of the model, book in our case. This is followed by the attributes and their values. One important thing to note is that it is mandatory to send the ID of the object with the JSON response, the ID attribute is defined automatically on every DS.Model object and is used by the store to uniquely identify the records.

Let's look at relationships now. The book record has three relationships, namely authors, publisher, and reviews. The relationships of DS.hasMany type use the plural form of the model and expect the relationship IDs in an array.

The relationship response "reviews": [1,2,3] tells the serializer that this book object has three reviews, whose IDs are 1, 2, and 3. Similarly, the "publisher": 1 tells the adapter and serializer that a book has one publisher with ID 1.

Now, as we used {async:true} in defining our relationships of the book model, the DS.RESTAdapter and DS.RESTSerializer that the JSON response of the book object will only contain the IDs of the relationships in proper format. These relationship objects (authors, publisher and reviews) should be fetched separately only when they are referred to by the application code. When such references to the relationships are encountered, a call store.find('reviews',1) or GET /reviews/1 is sent for each object present in the "reviews": [1,2,3] array.

The async property is very helpful in cases where an object has a lot of relationships, but initially only the details of the objects are visible on the screen as the relationships are fetched lazily when required by the application.

Sideloaded relationships

Till now, we have seen the response structure when relationships are defined with {async: true}. There could be cases when you don't want this behavior, and want to load the complete object in one go. This is also the default behavior of the relationships in ember-data library. Let us see how we should structure the response in order for it to load the book object with its associated relationships synchronously, in one go.

For side loading the relationships, we will have to define the book model without the async property set to true in its relationships, as follows:

import DS from "ember-data";
export default DS.Model.extend({
  title: DS.attr('string'),
  isbn: DS.attr('string'),
  pages: DS.attr('number'),
  description: DS.attr('string'),
  authors: DS.hasMany('author'),
  publisher: DS.belongsTo('publisher'),
  reviews: DS.hasMany("review")
});

The side-loaded response from the server should be of the following structure:

{
  "book": {
    "id":1,
    "title": "Ember.js Essentials",
    "isbn": "ISBN1",
    "pages": 180,
    "description": "Ember.js essentials to master",
    "authors": [1],
    "publisher": 1,
    "reviews": [1,2,3]
  },

  "publisher": {
    "id": 1,
    "name": "Packt Publishing",
    "organizationName": "Pact",
    "address":"Packt Publishing London",
    "book":1
  },

  "authors": [{
    "id": 1,
    "firstName": "firstName1",
    "lastName" : "LastName1",
    "bio": "Great Guy",
    "book":[1]
  }],
  "reviews": [{
    "id": 1
    "name": "Reviewer 1",
    "comment": "comment 1",
    "book":1
  },{
    "id": 2
    "name": "Reviewer 2",
    "comment": "comment 2",
    "book":1
  },
  {
    "id": 3
    "name": "Reviewer 3",
    "comment": "comment 3",
    "book":1
  }]
}

In the preceding response, you can see that instead of sending just the book objectJSON, we also side load the relationships of the book model that is, we also side load the publisher, authors, and reviews. Please note that all DS.hasMany relationships are provided as array of objects, and DS.belongsTo relationships are provided as single objects.

Customizing the DS.RESTAdapter

Till now, we have discussed the behavior of the default DS.RESTAdapter, but there are situations, especially when integration with API which is not fully compliant with REST conventions, which require to customize and modify the adapter, according to your needs. When using Ember CLI, all the customizations of the adapter go in app/adapters/ directory. You can customize the behavior for the adapter for the whole application as well as restrict it to only one or more model classes.

Application-wide changes to the adapter should go in app/adapters/application.js, and any model specific customizations should go in app/adapters/<name of the model>.js, just as if we had to customize the adapter for only the book model, we would have created an adapter for it in app/adapters/book.js.

Customizing the URL endpoints

If you want to define any prefix that is prepended to the URL, you can do that by defining the namespace property of your adapter.

import DS from 'ember-data';
export default DS.RESTAdapter.extend({
  namespace: 'api'
});

Application adapter using custom namespace is present at chapter-6/example1/app/adapters/application.js

Setting the namespace property of the application adapter will prefix all the generated URL for all the models of the application with api, for example this.store.find ("book",1) will now become /api/books/1, instead of /books/1. If your API server runs on a different domain or you are integrating with third party APIs directly from your Ember.js application, you will need to change the host name while building the URLs for your data models.

You can do that by setting the host property of the adapter as follows:

import DS from 'ember-data';
export default DS.RESTAdapter.extend({
  host: 'http://api.someotherdomain.com/'
});

By setting the host property of the adapter, the call for this.store.find ("book",1) will now go to http://api.someotherdomain.com/books/1 instead of just /books/1, which goes to the same server that hosts your Ember.js application.

Please note here that in order for the above functionality of cross-domain functionality to work properly, you will have use a browser that supports CORS (cross-origin resource sharing), and you will have to configure the server to send in the correct CORS headers.

By default, Ember Data comes with a few more adapters other than DS.RESTAdapter, which are mentioned as follows:

  • DS.FixtureAdapter – This adapter is used primarily for testing the model classes. The fixture adapters don't make the actual calls to the backend server but serve the model data from the fixtures already loaded and configured in the memory.
  • DS.Adapter – This is base adapter class, it does not implement any functionality but is primarily used as a contract to implement new adapters.
  • DS.ActiveModelAdapter – This adapter extends the default DS.RESTAdapter and hence most of the features and conventions remain the same, here. The main benefit of this adapter is that it works well with the Ruby on Rails active model serializer.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.219.130