For the next part of the Tracker app, you will focus only on the data layer.
You have already worked quite a bit with data in the form of
object literals. You have created and modified objects and their
properties, and you have created functions to quickly make
objects with default values. You know how to store data in
localStorage
and sessionStorage
.
In Tracker, you are going to work with data in the form of models. Models are essentially functions that create objects with specific properties and methods. They are the architecture of data flowing through your application.
Ember has an object class that can take care of your initial need to define your app’s data architecture: Ember.Object. All Ember classes inherit from this class. With a simple definition and naming pattern, Ember gives you the power to create, retrieve, update, and destroy model instances while the app is running.
However, for your modern application you need more than what Ember.Object provides. Your models need to be able to persist themselves when business logic asks them to retrieve or save data from a data source.
Enter Ember Data, a JavaScript library
built on top of Ember.Object, which will help
you add model-specific functionality. Ember Data adds classes
built on Ember.Object that abstract the
complexity of working with various data sources: RESTful APIs
,
localStorage
, and even static fixture data.
Ember Data also adds an in-memory store for data. The data store is where you create, retrieve, update, and delete your model instances.
Ember CLI has already loaded the Ember Data library, so you are ready to build your models.
In the last chapter, you used
ember g route [route name]
to make Ember CLI create
route files for you. You can also use ember generate
to create a model file
with the command ember g model [model name]
.
Create the model files you will need to back your routes for cryptids, sightings, and witnesses:
ember g model cryptid ember g model sighting ember g model witness
Cryptids will have a
model definition in the file
app/models/cryptid.js. Open this file and add attributes for name, cryptid type (species), profile image, and sightings to the cryptid
model:
import DS from 'ember-data'; export default DS.Model.extend({ name: DS.attr('string'), cryptidType: DS.attr('string'), profileImg: DS.attr('string'), sightings: DS.hasMany('sighting') });
Ember Data, referenced here as DS
(for “data store”), has an attr method that is
used to specify model attributes. When data is parsed from
the source, attr returns the value. If you
give attr an attribute type, it will be
coerced to that type. If you do not set the attribute type, your
data will be passed through to the appropriate key unchanged.
There are a few attribute types built in: string, number,
boolean, and date. You can also create custom model attributes
using transforms
, which you will learn about in
Chapter 22.
attr can also take a second argument to specify default values. This optional argument is a hash with a single key: defaultValue. Here are some examples:
name: DS.attr('string', {defaultValue: 'Bob'}), isNew: DS.attr('boolean', {defaultValue: true}), createdAt: DS.attr('date', {defaultValue: new Date()}), numOfChildren: DS.attr('number', {defaultValue: 1})
In the cryptid definition, you used the string attribute type for the name, cryptidType, and profileImg attributes. (Why a string type for profileImg? It will reference the image path, not the image itself.)
The sightings attribute uses a different method to
define its data: hasMany. This method is
part of Ember Data’s relationship methods.
When you query a RESTful API
for a cryptid, it will have
associated sightings. That association will be returned as an array
of sighting ids referencing an instance of a
sighting model.
Ember Data has methods to handle one-to-one, one-to-many, and many-to-many relationship types:
Relationship | “Owning” model | “Owned” model |
---|---|---|
one-to-one | DS.hasOne | DS.belongsTo |
one-to-many | DS.hasMany | DS.belongsTo |
many-to-many | DS.hasMany | DS.hasMany |
The first argument is the model to associate. In your app, a
cryptid will have many sighting instances (you would be surprised how often people see these creatures).
The second
argument is an optional hash which, similar to
attr’s second argument, is a configuration object to
set values when evaluating the function.
It contains an async
key and a value (with a default of true
).
Model relationships could require requests to a server to retrieve other
model data. For cryptids, a request to sightings
is needed to
display sighting data for each cryptid. The same is true for the
inverse relationship of sightings belonging to a cryptid. The
default value, async: true
, requires a separate
request and API endpoint to retrieve the linked data.
If your API has the ability to send all the data together, you can
set the async
value to
false
. For the Tracker app, leave the
value as the default, true
.
Next, open the model for witnesses in app/models/witness.js and add attributes for a witness’s first and last name, email address, and recorded sightings:
import DS from 'ember-data'; export default DS.Model.extend({ fName: DS.attr('string'), lName: DS.attr('string'), email: DS.attr('string'), sightings: DS.hasMany('sighting') });
You defined a witness to be an object that contains a first name (fName), a last name (lName), an email address (email), and a many-to-many relationship to sightings (sightings).
Finally, open your third model file: app/models/sighting.js.
Add attributes to your sightings
model for the who, what, where, and when of the sighting as well as the date the sighting was recorded:
import DS from 'ember-data'; export default DS.Model.extend({ location: DS.attr('string'), createdAt: DS.attr('date'), sightedAt: DS.attr('date'), cryptid: DS.belongsTo('cryptid'), witnesses: DS.hasMany('witness') });
Sightings are defined much like witnesses and cryptids, with basic properties defined as strings. The location is a value the user will input in the app, while createdAt and sightedAt will be added server-side when the sighting has been added to the database.
The relationship for the property cryptid is something new, DS.belongsTo('cryptid'). This method is a one-to-many relationship linking a cryptid instance to the sighting instance – one-to-many because each cryptid will have many sightings.
18.188.10.1