Where else would you go to find entities? What is an entity? It's easy;
an entity is an object that represents a record/document in a database.
I think you mean "isn't that a data model?" Most modern frameworks use the term "model" to define what is truly an "entity." A model is where you house your "business logic." It is what makes your app go. Many times a model will work with or on an entity, but not always.
Before we dive in too much, lets look at the the layers we will be working with:
In order to create or query for an Entity, you must go through a Store
. The store layer handles all communication between you and whatever
data store you are using. It currently only supports Mongodb, but adding support for anything is really easy.
The entities themselves are the objects that represent the record/document in the database (or whatever, really). They are meant to house the state of a particular thing (like a user, automobile, whatevs).
Let me show you an entity (and entity store) in action.
I'm going to assume you have created an app using Sockets or Alfred or something similar.
define(['altair/facades/declare',
'apollo/_HasSchemaMixin'
], function (declare, _HasSchemaMixin) {
return declare([_HasSchemaMixin], {
});
});
{
"name": "Users",
"tableName": "users",
"properties": {
"_id": {
"type": "primary",
"options": {
"label": "Id"
}
},
"firstName": {
"type": "string",
"options": {
"label": "First Name"
}
},
"lastName": {
"type": "string",
"options": {
"label": "Last Name"
}
},
"email": {
"type": "string",
"options": {
"label": "email"
}
}
}
}
this.entity('User').then(function (store) {
//the User store is where you'll find all your users. A store as database agnostic
return store.find().where('state', '==', 'CO').sortBy('firstName', 'ASC').thenBy('lastName', 'DESC').skip(10).limit(10).execute();
}).then(function (user) {
if(!user) {
throw new Error('user not found!');
} else {
//since entities use apollo/_HasSchemaMixin, the familiar get/set/setValues/getValues/etc. are available.
return user.set('firstName', 'tay')
.save(); //every entity is extended with save(), it returns a Promise.
}
}).then(function (user) {
//the first name is now updated
console.log(user.get('firstName'), 'updated');
});
##Creating your first entity An entity is a generic AMD module that mixes in apollo/_HasSchemaMixin.
this.entity('User').then(function (store) {
var user = store.create({
firstName: 'tay',
lastName: 'ro'
});
user.set('email', 'tay@ro.com');
console.log(user.values);
});
##Overridding the store
Lets say you want a utility method on your store to do something more than just find()
and create()
. What if we wanted
a findAdminUsers()
. This is a lazy example, don't use it for reals.
Create stores/User.js
and drop this in:
define(['altair/facades/declare',
'liquidfire/modules/spectre/db/Store'
], function (declare, Store) {
return declare([Store], {
findAdminUsers: function () {
return this.find().where('isAdmin', '===', true);
}
});
});
Now you can do the following:
this.entity('User').then(function (store) {
return store.findAdminUsers().execute();
}).then(function (users) {
return users.each().step(function (user) {
console.log(user.get('firstName'), 'is an admin user');
});
});
this.entity('User').then(function (store) {
return store.find().execute();
}).then(function (users) {
return users.each().step(function (user) {
console.log(user.get('firstName'), 'is an admin user');
});
});
##Custom Statement
You can customize how a database Statement
works from a store pretty easily. Here is a real
world example of a custom Store
with a custom Statement
.
define(['altair/facades/declare',
'lodash',
'liquidfire/modules/spectre/db/Store',
'altair/cartridges/database/Statement',
'altair/cartridges/database/cursors/Array'
], function (declare, _, Store, Statement, ArrayCursor) {
return declare([Store], {
/**
* Helper to find tickets by auction (and temporarily uses the legacy rest adapter to fetch results)
*
* @param auction
* @returns {Statement}
*/
findByAuction: function (auction) {
var statement = new Statement(function (q) {
return this.nexus('handbid:LegacyRest').rest().get('tickets', { auctionKey: auction.get('key') }).then(function (response) {
//convert array of ticket objects to ticket Entities (with .get(), .set(), .save(), .delete(), etc.)
var tickets = _.map(response, this.create, this),
cursor = new ArrayCursor(tickets, statement); //a statement always returns an array
return cursor;
}.bind(this));
}.bind(this));
return statement;
}
});
});
When you need to create an endpoint to search entities with all the fancy skip, limit, search term, etc. you can use
liquidfire:Spectre/models/Search
to get very far very fast. Inside your controller, you can:
startup: function (e) {
//mixin dependencies
this.defered = this.all({
_search: this.model('liquidfire:Spectre/models/Search', null, { parent: this }) //using `this` as parent makes this.entity() behave relative to our controller
}).then(function (deps) {
declare.safeMixin(this, deps);
return this;
}.bind(this));
return this.inherited(arguments);
},
users: function (e) {
return this._search.findFromEvent('User', e);
}
That's it. Now you can call /v1/rest/your/endpoint
and pass values in the query string to customize your results.
Example: `/v1/rest/users?perPage=20&page=2&sortField=name&sortDirection=DESC
perPage
: how many results to return at once (defaults to 10, max 100)page
: the page we are onsortField
: the field to sort onsortDirection
: ASC or DESCsearchField
: anything passed tosearchValue
will search against this fieldsearchValue
: the search string
When you need to perform a search in a consistent way, the search model is perfect.
var search = this.model('liquidfire:Spectre/models/Search', null, { parent: this });
//no options are required, this list is just to show everything you can do
search.find('User', {
page: 0,
perPage: 10,
sort: {
lastName: 'ASC'
},
query: {
state: 'CO'
},
//the above is equivalent to the following
searchField: 'state',
searchValue: 'CO'
});
If you are building an app (through Alfred
or Sockets
), you can use the Lifecycle
class to load all your stores
right in startup()
.
{
startup: function () {
//get all our stores ready
this.mixin({
parts: this.entity('Part'),
sets: this.entity('Set'),
variants: this.entity('Variant'),
vendors: this.entity('Vendor'),
users: this.entity('User')
});
return this.inherited(arguments);
}
}
Then, from one of your Controllers
you can do the following.
{
someAction: function (id, cb) {
this.parts.findOne().where('_id', '===', id).execute().then(function (part) {
cb(null, part ? part.geSocketValues() : null);
}).otherwise(function (err) {
cb(err.message || err);
});
}
}
When you are using a controller based solution (Sockets, Alfred, etc.), you find yourself creating/updating/searching/deleting entities often. From your controller, you have the following methods.
//type, values[,options]
this.createEntity('User', {
firstName: 'Test',
lastName: 'User'
}, { ... }).then(function (user) { ... }).otherwise(function (err) {
//cb(_.isArray(err) ? err[0] : err.message || err);
});
//type, id, values[, options]
this.updateEntity('User', '3', { firstName: 'New Name' }).then(...)
//type, options (passthrough to `liquidfire:Spectre/models/Search`)
this.searchEntities('User', {
query: { email: 'test@test.com' }
transform: function (entity) { return entity.getSocketValues(); }
}).then(...)