How to use DS.Store.registerAdapter - ember.js

I try to use the 'adapter per type' feature of ember-data. Not sure whether I'm doing something wrong or if it's a bug in ember-data. Basically i thought it would work like this:
Person = DS.Model.extend({
name: DS.attr('string')
});
var personAdapter = DS.Adapter.create();
DS.Store.registerAdapter(Person, personAdapter );
The store always uses the the default adapter and not the registered 'per type adapter'
I wrote a test case to show what I mean:
var get = Ember.get, set = Ember.set;
var Person, store, adapter, personAdapter;
module("DS.Store and DS.Adapter 'adapter per type' integration test", {
setup: function() {
Person = DS.Model.extend({
name: DS.attr('string')
});
adapter = DS.Adapter.create();
personAdapter = DS.Adapter.create();
DS.Store.registerAdapter(Person, personAdapter);
store = DS.Store.create({ adapter: adapter });
},
teardown: function() {
adapter.destroy();
store.destroy();
}
});
test("test function on the per type adapter", function() {
adapter.find = function(store, type, id) {
ok(false, "should call find on the registered 'per type adapter' not on the default one");
};
personAdapter.find = function(store, type, id) {
store.load(Person, {
'id': 1,
'name': "My Name"
});
};
var person = store.find(Person, 1);
equal(person.get('name'), "My Name");
});
While debugging I noticed that the "DS.Mappable._reifyMapping" mixing explicitly checks for DS.Store and stops if the "this" is a DS.Store.

This should work.
var get = Ember.get, set = Ember.set;
var Person, StoreClass, store, adapter, personAdapter;
module("DS.Store and DS.Adapter 'adapter per type' integration test", {
setup: function() {
Person = DS.Model.extend({
name: DS.attr('string')
});
adapter = DS.Adapter.create();
personAdapter = DS.Adapter.create();
StoreClass = DS.Store;
StoreClass.registerAdapter(Person, personAdapter);
store = StoreClass.create({ adapter: adapter });
},
teardown: function() {
adapter.destroy();
store.destroy();
}
});
...

The previous answers here are no longer valid (as of Ember Data 1.0.beta.1).
Per the changelog, you now use the ModelNameAdapter syntax. For example,
App.Person = DS.Model.extend({
...
});
App.CustomAdapter = DS.Adapter.create({
url: 'your/custom/url'
});
App.PersonAdapter = App.CustomAdapter;

There is a bug report for this: Per-type Adapter not respected in case of commit
Edit: removed inappropriate comment.

Related

Ember Data: saving polymorphic relationships

I'm having trouble saving "hasMany" polymorphic records in Ember Data (1.0.0-beta.15). It looks as if Ember Data isn't setting the "type" property of the polymorphic relationship. Relationships in serialized records look like:
"roles": ["1", "2"]
When I expect them to look more like:
"roles":[{
"id": "1",
"type": "professionalRole"
}, {
"id": "2",
"type": "personalRole"
}
];
I see the following error in the console:
TypeError: Cannot read property 'typeKey' of undefined
If the records come back from the server in the expected format, all is well. The error only occurs when Ember Data creates the relationship.
I experience this using the FixtureAdapter, LocalStorageAdapter, and the RESTAdapter. I've read every piece of documentation I can find on the subject, but I cannot see my mistake.
I've created a CodePen to demonstrate the problem, but I'll also paste that code below.
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter;
App.Person = DS.Model.extend({
name: DS.attr(),
roles: DS.hasMany('role')
});
App.Role = DS.Model.extend({
title: DS.attr(),
person: DS.belongsTo('person', {
polymorphic: true
})
});
App.ProfessionalRole = App.Role.extend({
rank: DS.attr()
});
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
var person = this.store.createRecord('person', {
name: 'James'
});
var role = this.store.createRecord('professionalRole', {
title: 'Code Reviewer',
rank: 'Expert'
});
var promises = Ember.RSVP.hash({
person: person.save(),
role: role.save()
});
promises.catch(function() {
controller.set('initialSaveResult', 'Failure');
});
promises.then(function(resolved) {
controller.set('initialSaveResult', 'Success!');
var resolvedPerson = resolved.person;
var resolvedRole = resolved.role;
// Either/both of these break it
//resolvedRole.set('person', resolvedPerson);
resolvedPerson.get('roles').addObject(resolvedRole);
var innerPromises = Ember.RSVP.hash({
person: resolvedPerson.save(),
role: resolvedRole.save()
});
innerPromises.catch(function() {
controller.set('secondSaveResult', 'Failure');
});
innerPromises.then(function() {
controller.set('secondSaveResult', 'Success!');
});
});
}
});
App.ApplicationController = Ember.Controller.extend({
initialSaveResult: "Loading...",
secondSaveResult: "Loading..."
});

Implementing model filter

I have set up the following scaffolding for my Ember application.
window.App = Ember.Application.create({});
App.Router.map(function () {
this.resource('coaches', function() {
this.resource('coach', {path: "/:person_id"});
});
});
App.ApplicationAdapter = DS.FixtureAdapter.extend({});
App.Person = DS.Model.extend({
fname: DS.attr('string')
,lname: DS.attr('string')
,sport: DS.attr('string')
,bio: DS.attr('string')
,coach: DS.attr('boolean')
,athlete: DS.attr('boolean')
});
App.Person.FIXTURES = [
{
id: 10
,fname: 'Jonny'
,lname: 'Batman'
,sport: 'Couch Luge'
,bio: 'Blah, blah, blah'
,coach: true
,athlete: true
}
,{
id: 11
,fname: 'Jimmy'
,lname: 'Falcon'
,sport: 'Cycling'
,bio: 'Yada, yada, yada'
,coach: false
,athlete: true
}
];
I am trying to set up a route to filter the person model and return only coaches. Just to make sure I can access the data, I have simply used a findAll on the person model.
App.CoachesRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('person');
}
});
Now though, I am trying to implement the filter method detailed on the bottom of the Ember.js Models - FAQ page.
App.CoachesRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return store.filter('coaches', { coach: true }, function(coaches) {
return coaches.get('isCoach');
});
}
});
The coaches route is not working at all with the new route implemented and the old one commented out. I am using the Ember Chrome extension and when using the filter route the console responds with, Error while loading route: Error: No model was found for 'coaches'. Apparently the route is not working, specifically the model. No kidding, right? What am I missing in my filter model route?
Thank you in advance for your help.
The error message is spot on- there is no CoachModel. I think you need to do this:
App.CoachesRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return store.filter('person', { coach: true }, function(coaches) {
return coaches.get('isCoach');
});
}
});

Ember.js access model values

I'd like to be able to modify/validate data before actually saving.
Model
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
date: DS.attr('date', { defaultValue: new Date() }),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
Route
App.PostsNewRoute = Ember.Route.extend({
model: function() {
return this.get('store').createRecord('post');
},
actions: {
doneEditing: function() {
debugger;
this.modelFor('postsNew').save();
this.transitionTo('posts.index');
}
}
});
So, the questions, before the .save() I want to, let's say, validate that the title is not empty or so.
Everything I've tried gets undefined, or [Object object] has no .val() method. I don't know how to get to the values of the model. How can I do that?
And the other thing I have in mind. Is that defaultValue working as intended? I want to set Date() to every new created post. Somehow date is not being recorded since it's not showing.
Thanks.
App.PostsNewRoute = Ember.Route.extend({
model: function() {
return this.get('store').createRecord('post');
},
actions: {
doneEditing: function() {
debugger;
var model = this.modelFor('postsNew');
var title = model.get('title');
model.save();
this.transitionTo('posts.index');
}
}
});

Delete associated model with ember-data

I have two models:
App.User = DS.Model.create({
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.create({
user: DS.belongsTo('App.User')
});
When a user is deleted, it also will delete all its comments on the backend, so I should delete them from the client-side identity map.
I'm listing all the comments on the system from another place, so after deleting a user it would just crash.
Is there any way to specify this kind of dependency on the association? Thanks!
I use a mixin when I want to implement this behaviour. My models are defined as follows:
App.Post = DS.Model.extend(App.DeletesDependentRelationships, {
dependentRelationships: ['comments'],
comments: DS.hasMany('App.Comment'),
author: DS.belongsTo('App.User')
});
App.User = DS.Model.extend();
App.Comment = DS.Model.extend({
post: DS.belongsTo('App.Post')
});
The mixin itself:
App.DeletesDependentRelationships = Ember.Mixin.create({
// an array of relationship names to delete
dependentRelationships: null,
// set to 'delete' or 'unload' depending on whether or not you want
// to actually send the deletions to the server
deleteMethod: 'unload',
deleteRecord: function() {
var transaction = this.get('store').transaction();
transaction.add(this);
this.deleteDependentRelationships(transaction);
this._super();
},
deleteDependentRelationships: function(transaction) {
var self = this;
var klass = Ember.get(this.constructor.toString());
var fields = Ember.get(klass, 'fields');
this.get('dependentRelationships').forEach(function(name) {
var relationshipType = fields.get(name);
switch(relationshipType) {
case 'belongsTo': return self.deleteBelongsToRelationship(name, transaction);
case 'hasMany': return self.deleteHasManyRelationship(name, transaction);
}
});
},
deleteBelongsToRelationship: function(name, transaction) {
var record = this.get(name);
if (record) this.deleteOrUnloadRecord(record, transaction);
},
deleteHasManyRelationship: function(key, transaction) {
var self = this;
// deleting from a RecordArray doesn't play well with forEach,
// so convert to a normal array first
this.get(key).toArray().forEach(function(record) {
self.deleteOrUnloadRecord(record, transaction);
});
},
deleteOrUnloadRecord: function(record, transaction) {
var deleteMethod = this.get('deleteMethod');
if (deleteMethod === 'delete') {
transaction.add(record);
record.deleteRecord();
}
else if (deleteMethod === 'unload') {
var store = this.get('store');
store.unloadRecord(record);
}
}
});
Note that you can specify via deleteMethod whether or not you want to send the DELETE requests to your API. If your back-end is configured to delete dependent records automatically, then you will want to use the default.
Here's a jsfiddle that shows it in action.
A quick-and-dirty way would be to add the following to your user model
destroyRecord: ->
#get('comments').invoke('unloadRecord')
#_super()
I adapted the answer of #ahmacleod to work with ember-cli 2.13.1 and ember-data 2.13.0. I had an issue with nested relationships and the fact that after deleting an entity from the database its id was reused. This lead to conflicts with remnants in the ember-data model.
import Ember from 'ember';
export default Ember.Mixin.create({
dependentRelationships: null,
destroyRecord: function() {
this.deleteDependentRelationships();
return this._super()
.then(function (model) {
model.unloadRecord();
return model;
});
},
unloadRecord: function() {
this.deleteDependentRelationships();
this._super();
},
deleteDependentRelationships: function() {
var self = this;
var fields = Ember.get(this.constructor, 'fields');
this.get('dependentRelationships').forEach(function(name) {
self.deleteRelationship(name);
});
},
deleteRelationship (name) {
var self = this;
self.get(name).then(function (records) {
if (!records) {
return;
}
var reset = [];
if (!Ember.isArray(records)) {
records = [records];
reset = null;
}
records.forEach(function(record) {
if (record) {
record.unloadRecord();
}
});
self.set(name, reset);
});
},
});
Eventually, I had to set the relationship to [] (hasMany) or null (belongsTo). Else I would have run into the following error message:
Assertion Failed: You cannot update the id index of an InternalModel once set. Attempted to update <id>.
Maybe this is helpful for somebody else.

How to manually create an Array of ember-data objects?

I have the need to override the find on a specific object in my ember.js app. What I have below works great if I was planning to return a "single" ember-data object, but how would I modify this to return an Array of objects (such that I return a AdapterPopulatedRecordArray or RecordArray (not sure the difference to be honest)).
App.Foo = DS.Model.extend({
name: DS.attr('string')
}).reopenClass({
find: function() {
var result = Ember.Object.create({
isLoaded: false
});
$.getJSON('/api/foos/', function(data) {
result.setProperties(data);
result.set('isLoaded', true);
});
return result;
}
});
You can return an array as follows:
App.Foo = DS.Model.extend({
name: DS.attr('string')
}).reopenClass({
records: [],
find: function() {
var self = this;
$.getJSON('/api/foos/', function(response) {
response.forEach(function(record){
self.records.addObject(App.Foo.createRecord(record))
}, this)
});
return this.records;
}
});
This example (adapted from http://trek.github.com/) returns a simple array. To return a record array (or adapter populated record array) will
require more work but it is the same basic pattern. Have a look at this test for detail on what would be required:
https://github.com/emberjs/data/blob/master/packages/ember-data/tests/unit/record_array_test.js