If you imagine two models defined thus:
App.User = DS.Model.extend({
emails: DS.hasMany('email', {embedded: 'always'}),
});
App.Email = DS.Model.extend({
address: DS.attr('string'),
alias: DS.attr('string'),
user: DS.belongsTo('user')
});
... and a REST Adapter:
App.UserAdapter = DS.RESTAdapter.extend({
url: 'http://whatever.com',
namespace: 'api/v1'
});
... with routing set up like so:
App.Router.map(function () {
this.route('index', { path: '/' });
this.resource('users', function () {
this.route('index');
this.route('add');
this.resource('user', { path: ':user_id' }, function () {
this.route('delete');
this.route('edit');
this.resource('emails', function () {
this.route('index');
this.route('add');
this.resource('email', { path: ':email_id' }, function () {
this.route('delete');
this.route('edit');
});
});
});
});
});
... and a controller action to save the edited email, which looks like this:
App.EmailEditController = Ember.ObjectController.extend({
actions: {
save: function () {
var self = this;
var email = this.get('model');
email.save().then(function(){
self.transitionToRoute('email', email);
});
}
}
});
The issue is this...
The PUT request is being sent to: http://whatever.com/api/v1/emails/[email_id]
However the correct API endpoint is: http://whatever.com/api/v1/users/[user_id]/emails/[email_id]
What is the correct way to remedy this issue?
The solution I came up with was just to rewrite createRecord, updateRecord and deleteRecord in the REST adapter.
I added a 'parent' attribute to the models affected. In the *Record hooks, I can check if this is set and edit the path sent to buildURL accordingly.
My createRecord, updateRecord and deleteRecord hooks now looks something similar to this:
App.UserAdapter = DS.RESTAdapter.extend({
createRecord: function (store, type, record) {
if (!record.get('parent') || null === record.get('parent')) {
return this._super(store, type, record);
}
var data = {};
var serializer = store.serializerFor(type.typeKey);
var parent_type = record.get('parent');
var parent_id = record.get(parent_type).get('id');
var child_type = Ember.String.camelize(
Ember.String.pluralize(
type.typeKey.split(
record.get('parent')
).pop()
)
);
var path = Ember.String.pluralize(parent_type) + '/' + parent_id + '/' + child_type;
serializer.serializeIntoHash(data, type, record, { includeId: true });
return this.ajax(this.buildURL(path), "POST", { data: data });
},
updateRecord: function(store, type, record) {
if(!record.get('parent') || null === record.get('parent')){
return this._super(store, type, record);
}
var data = {};
var serializer = store.serializerFor(type.typeKey);
var parent_type = record.get('parent');
var parent_id = record.get(parent_type).get('id');
var child_type = Ember.String.camelize(
Ember.String.pluralize(
type.typeKey.split(
record.get('parent')
).pop()
)
);
var path = Ember.String.pluralize(parent_type) + '/' + parent_id + '/' + child_type;
serializer.serializeIntoHash(data, type, record);
var id = record.get('id');
return this.ajax(this.buildURL(path, id), "PUT", { data: data });
},
deleteRecord: function (store, type, record) {
if (!record.get('parent')) {
return this._super(store, type, record);
}
var parent_type = record.get('parent');
var parent_id = record.get('parent_id');
var child_type = Ember.String.camelize(
Ember.String.pluralize(
type.typeKey.split(
record.get('parent')
).pop()
)
);
var path = Ember.String.pluralize(parent_type) + '/' + parent_id + '/' + child_type;
var id = record.get('id');
return this.ajax(this.buildURL(path, id), "DELETE");
}
});
The Email model in the example would be something like:
App.Email = DS.Model.extend({
address: DS.attr('string'),
alias: DS.attr('string'),
user: DS.belongsTo('user'),
parent: 'user'
});
I solved this by overriding the buildURL method in model-specific adapters when required, using a mixin to encapsulate the method. Basically, it uses the default method to get the URL built according to Ember's rules and then it slices and puts additional info in place. Of course, this works because in buildURL we have access to the record...
Here is the basic idea in CoffeeScript:
module.exports = App.RestWithParentMixin = Ember.Mixin.create
host: App.Environment.get('hostREST')
namespace: App.Environment.get('apiNamespace')
ancestorTypes: null
buildURL: (type, id, record) ->
url = #_super(type, id, record)
ancestorTypes = #get('ancestorTypes')
if ancestorTypes == null
urlFixed = url
else
urlPrefix = #urlPrefix()
urlWithoutPrefix = url.slice(urlPrefix.length)
ancestry = []
ancestorTypes
if not Array.isArray(ancestorTypes)
ancestorTypes = [ancestorTypes]
for ancestorType in ancestorTypes
ancestor = record.get(ancestorType)
ancestorID = ancestor.get('id')
ancestry.push(ancestorType)
ancestry.push(ancestorID)
urlFixed = urlPrefix + '/' + ancestry.join('/') + urlWithoutPrefix
urlFixed
PS: A small edit to add that I this was made using Ember 1.7.1 and Ember Data 1.0.0-beta.11
Related
I am trying to set data from two models (that has hasMany & belongsTo relationship) and save them to firebase.
'list' data ends up being saved to firebase but not user data.
I think I'm doing something wrong at step 3. I'd appreciate your help!
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('list');
},
actions: {
createList: function() {
var newListTitle = this.controllerFor('lists').get('newListTitle');
var username = this.get('session.user.displayName');
alert(this.get('session.user.displayName'));
if (Ember.isBlank(newListTitle)) { return false; }
//1
var list = this.store.createRecord('list', {
title: newListTitle,
user: username,
});
//2
this.controllerFor('lists').set('newListTitle', '');
var _this = this;
//3
list.save().then(function(list) {
user.get('lists').addObject(list);
user.save();
_this.transitionTo('lists.show', list); //4
});
}
}
});
Restructured your adding logic as well as user defined models, also modified your route, which could look like this in Edit and View mode. Meaning you can have more than one item returned from "model".
// Update models
App.List = DS.Model.extend({
value: DS.attr('string')
});
App.User = DS.Model.extend({
name: DS.attr('string')
});
App.UserLists = DS.Model.extend({
user: DS.belongsTo('user'),
list: DS.belongsTo('list')
});
export default Ember.Route.extend({
LIST:SHOW ROUTE
model: function(params) {
var store = this.get('store');
var userPromise = store.find('user', params.id);
return Ember.RSVP.hash({
user: userPromise,
userList : userPromise.then(function(user) {
return store.find(userList, { WhereUserIdIs : user.get('id') })
});
});
},
actions: {
createList: function() {
var self = this;
var failure = function(reason) {
// handle stuff
};
var list = this.store.createRecord('list', {
title: this.get('title'),
});
var user = this.get('user');
var usersList = store.createRecord('userList', {
'user': user,
'list': list
});
list.save().then(function(list) {
user.save().then(function() {
userList.save().then(function() {
self.transitionTo('lists.show', list.get('id'));
}, failure);
}, failure);
}, failure);
}
});
I am newbie to ember.js, I am using ember-1.9.1.js and ember-data for my project.
For back-end configuration I have created a REST API with core php and the db is MySQL.
Now I can create new records (posts) from client side using with DS.RESTAdapter's "createRecord" function.
But I don't know how to update a Record (post) with DS.RESTAdapter's "updateRecord" function.
When I try to call the "updateRecord" function from "App.PostController (doneEditing)" I got this error:
Uncaught TypeError: store.updateRecord is not a function --------- app.js
app.js code below
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('home');
}
});
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'pran/webapp/rest_adapter/api',
createRecord: function(store, type, snapshot) {
var data = this.serialize(snapshot, { includeId: true });
var url = "api/new_post";
return new Ember.RSVP.Promise(function(resolve, reject) {
jQuery.ajax({
type: 'POST',
url: url,
dataType: 'json',
data: data
}).then(function(data) {
Ember.run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
Ember.run(null, reject, jqXHR);
});
});
},
updateRecord: function(store, type, snapshot) {
var data = this.serialize(snapshot, { includeId: true });
var id = snapshot.id;
var url = [type, id].join('/');
return new Ember.RSVP.Promise(function(resolve, reject) {
jQuery.ajax({
type: 'PUT',
url: url,
dataType: 'json',
data: data
}).then(function(data) {
Ember.run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
Ember.run(null, reject, jqXHR);
});
});
}
});
App.Store = DS.Store.extend({
revision: 12,
adapter: 'App.ApplicationAdapter'
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
App.Router.map(function() {
this.resource('home');
this.resource('about');
this.resource('posts', function(){
this.resource('post', { path: ':post_id' });
});
this.resource('newstory' , {path : 'story/new'});
});
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.filter('post', { id: true }, function(post) {
return post.get('id');
return this.store.find('post');
});
}
});
App.PostRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.post_id);
}
});
App.NewstoryController = Ember.ObjectController.extend({
actions :{
save : function(){
var title = $('#title').val();
var author = $('#author').val();
var excerpt = $('#excerpt').val();
var body = $('#body').val();
var store = this.get('store');
var new_post = store.createRecord('post',{
title : title,
author : author,
date : new(Date),
excerpt : excerpt,
body : body
});
new_post.save();
this.transitionToRoute('posts');
}
}
});
App.PostController = Ember.ObjectController.extend({
isEditing: false,
actions: {
edit: function() {
this.set('isEditing', true);
},
doneEditing: function() {
this.set('isEditing', false);
var title = $('#title').val();
var excerpt = $('#excerpt').val();
var body = $('#body').val();
var store = this.get('store');
var update_post = store.updateRecord('post',{
title : title,
excerpt : excerpt,
body : body
});
update_post.save();
}
}
});
Somebody please suggest a way to fix the issue.
updateRecord is adapter's method not store's
so try next:
store.adapterFor(App.Post).updateRecord(...
this is fast fix
And better create Post object and call method .save() - it's not good practice to work with adapter from controller like
App.Post.create({
title : title,
excerpt : excerpt,
body : body
}).save();
P.S. The final solution was
App.NewstoryController = Ember.ObjectController.extend({
actions :{
save : function(){
var title = $('#title').val();
var author = $('#author').val();
var excerpt = $('#excerpt').val();
var body = $('#body').val();
var store = this.get('store');
var new_post = store.createRecord('Post',{
title : title,
author : author,
date : new(Date),
excerpt : excerpt,
body : body
});
new_post.save();
this.transitionToRoute('posts');
}
}
});
i'm working with a a router and a controller, and i need to complete some operations on the controller, this is my model code
AcornsTest.StockRoute = Ember.Route.extend({
model: function(params) {
"use strict";
var url_params = params.slug.split('|'),
url = AcornsTest.Config.quandl.URL + '/' + url_params[0] + '/' + url_params[1] + '.json',
stockInStore = this.store.getById('stock', url_params[1]),
today = new Date(),
yearAgo = new Date(),
self = this;
yearAgo.setFullYear(today.getFullYear() - 1);
today = today.getFullYear()+'-'+today.getMonth()+'-'+today.getDate();
yearAgo = yearAgo.getFullYear()+'-'+yearAgo.getMonth()+'-'+yearAgo.getDate();
if(stockInStore && stockInStore.get('data').length) {
return stockInStore;
}
return Ember.$.getJSON(url,{ trim_start: yearAgo, trim_end: today, auth_token: AcornsTest.Config.quandl.APIKEY })
.then(function(data) {
if(stockInStore) {
return stockInStore.set('data', data.data);
} else {
return self.store.createRecord('stock', {
id: data.code,
source_code: data.source_code,
code: data.code,
name: data.name,
description: data.description,
display_url: data.display_url,
source_name: data.source_name,
data: data.data,
slug: data.source_code+'|'+data.code
});
}
});
}
});
and this is my controller
AcornsTest.StockController = Ember.ObjectController.extend({
init: function() {
"use strict";
this.send('generateChartInfo');
},
actions: {
generateChartInfo: function() {
"use strict";
console.log(this.model);
console.log(this.get('model'));
}
}
});
from the controller i'm trying to get access to the model to get some information and format it, and send it to the view
but this.model or this.get('model') always returns null, how can i successful get access to the model from the controller? thanks
You are overriding the init method, but its broken, do this:
AcornsTest.StockController = Ember.ObjectController.extend({
init: function() {
"use strict";
this._super();
this.send('generateChartInfo');
});
You need to call the parent method.
See this test case: http://emberjs.jsbin.com/gijon/3/edit?js,console,output
The model is not ready at init time. If anyone has official docs please share.
I have a route like following where it builds the data from multiple rest calls.
App.IndexRoute = Ember.Route.extend({
model: function() {
var id = 1; //will get as url param later
var modelData = {ab:{},ef:{}};
return ajaxPromise('https://url1/'+ id +'?order=desc').then(function(data){
modelData.ab = data.items[0];
return ajaxPromise('https://url2/'+ id +'/?order=desc').then(function(data){
modelData.ab.x = data.items;
return modelData;
})
});
}
});
My ajaxPromise function is as follows:
var ajaxPromise = function(url, options){
return Ember.RSVP.Promise(function(resolve, reject) {
var options = options || {
dataType: 'jsonp',
jsonp: 'jsonp'
};
options.success = function(data){
resolve(data);
};
options.error = function(jqXHR, status, error){
reject(arguments);
};
Ember.$.ajax(url, options);
});
};
Now the issue is i know that i can use RSVP.all with promise instances but the data returned from these url has to be set to model object like above.
Also there may be few more rest calls which require data from other rest call. Is there any other way i can handle this promises.
PS: data is required right away for a single route
App.IndexRoute = Ember.Route.extend({
model: function() {
var id = 1; //will get as url param later
return Ember.RSVP.hash({
r1: ajaxPromise('https://url1/'+ id +'?order=desc'),
r2: ajaxPromise('https://url2/'+ id +'/?order=desc')
});
},
setupController:function(controller, model){
model.ab = model.r1.items[0];
model.ab.x = model.r2.items;
this._super(controller, model);
}
);
If you have two that have to run synchronously(second depends on first), you can create your own promise, which eon't resolve until you call resolve.
model: function() {
var promise = new Ember.RSVP.Promise(function(resolve, reject){
var modelData = {ab:{},ef:{}};
ajaxPromise('https://url1/'+ id +'?order=desc').then(function(data){
modelData.ab = data.items[0];
ajaxPromise('https://url2/'+ id +'/?order=desc').then(function(data){
modelData.ab.x = data.items;
resolve(modelData);
})
});
});
return promise;
},
I do an example like this,but still can't get pagination
this is my store.js.coffee
Eme.serializer = DS.RESTSerializer.create()
Eme.serializer.configure
meta: 'meta'
pagination: 'pagination'
Eme.CustomAdapter = DS.RESTAdapter.extend
serializer: Eme.serializer
namespace: "api/v1"
Eme.Store = DS.Store.extend
revision: 13
adapter: 'Eme.CustomAdapter'
this is my controller
Eme.PluginsController = Em.ArrayController.extend
content: []
pagination: (->
if this.get('model.isLoaded')
console.log #get('model.type')
console.log #get('store').typeMapFor(modelType).metadata
modelType = #get('model.type')
#get('store').typeMapFor(modelType).metadata.pagination
).property('model.isLoaded')
this is response
{
"meta":{
"pagination":{
"total_count":16,
"total_pages":2,
"current_page":1
}
},
"plugins":[{
"id":"1",
"name":"zhangsan",
}]
}
this is my log:
Eme.Plugin
Object {}
In the example you pasted, the modelType variable is output to console before it has been defined. That could be why you are not seeing the pagination data as expected.
I've created a jsbin with a slightly modified version of your code and it appears to output pagination data correctly. See: http://jsbin.com/anIKAfO/2/edit
App = Ember.Application.create({});
App.IndexRoute = Ember.Route.extend({
model: function(){
return App.Plugin.find();
}
});
App.IndexController = Ember.ArrayController.extend({
pagination: function() {
if (this.get('model.isLoaded')) {
var store = this.get('store');
modelType = this.get('model.type');
console.log('modeltype: ', this.get('model.type'));
var metadata = store.typeMapFor(modelType).metadata;
console.log('metadata: ', metadata);
return metadata.pagination;
}
}.property('model.isLoaded')
});
App.Store = DS.Store.extend({
adapter: 'App.Adapter'
});
App.Plugin = DS.Model.extend({
name: DS.attr('string')
});
App.serializer = DS.RESTSerializer.create();
App.serializer.configure({
meta: 'meta',
pagination: 'pagination'
});
App.Adapter = DS.RESTAdapter.extend({
serializer: App.serializer,
ajax: function(url, type, hash) {
console.log('App.Adapter.ajax:', url, type, hash);
json = App.RESTDATA[url];
if (json) {
console.log('App.Adapter.ajax: Found RESTDATA: ', json);
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.run(null, resolve, json);
});
} else {
console.log('App.Adapter.ajax: No RESTDATA for url, calling API', url);
return this._super(url, type, hash);
}
}
});
App.RESTDATA = {
'/plugins':
{
"meta":{
"pagination":{
"total_count":16,
"total_pages":2,
"current_page":1
}
},
"plugins":[{
"id":"1",
"name":"zhangsan"
}]
}
};