I'm having trouble getting Ember to delete a record that has a belongsTo relationship. I have a couple models set up with a one-to-one relationship like this:
App.User = DS.Model.extend({
account: DS.belongsTo('App.Account')
...
});
App.Account = DS.Model.extend({
user: DS.belongsTo('App.User'),
...
});
This is my deleteUser method on my UserController
deleteUser: function() {
user = this.get('model');
var transaction = App.store.transaction();
transaction.add(user);
user.deleteRecord();
transaction.commit();
this.transitionTo('users');
}
When it gets to user.deleteRecord(); I get an error in the console Too much recursion. Trying to step through the problem I found that infinite loop is happening in this section of code in the main ember.js file
var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
isUnknown, currentValue;
if (desc) {
desc.set(obj, keyName, value);
} else {
....
}
deleteRecord calls clearRelationships which calls Ember.set(this, "account", null) on the user object. Inside Ember.set() when it hits the above code it finds the reference to the user object and calls set on it.. which then finds the account and calls set on it.. which finds the user and calls set on it.. etc.
If this is a bug in Ember can anyone help me with a fix or workaround? Here is a jsFiddle of my example
Looks like it was an oversight. This pull request on github fixed the issue for me https://github.com/emberjs/data/pull/715
Related
I have a template that has data getting passed into it through the Iron Router params here, it might be obvious what the template is being designed to do:
lib/routes.js
// custom reset password page for child user
Router.route('/reset-password/child/:_id', {
name: 'reset-child-password',
template: 'childResetPassword',
layoutTemplate: 'appLayout',
data: function() {
return Users.findOne({ _id: this.params._id });
}
});
But, when I try to access this child user data in the template I get errors saying this.data is undefined. or cannot read property 'profile' of undefined. Here are my helpers and template use of the helper.
client/templates/childResetPassword.html
...
<h3>Reset Password for {{childFirstName}}</h3>
<form id='childResetPassword'>
<div class="form-group">
<input type="password" name="new-child-password" class="form-control" value=''>
</div>
...
client/templates/helpers/childResetPassword.js
Template.childResetPassword.helpers({
childFirstName: function() {
console.log("Template helpers:");
console.log(this.data);
return this.data.profile.firstname;
}
});
Any thoughts on how to access the data-context passed with the iron router data callback? Am I using it incorrectly?
UPDATE (STILL NOT ANSWERED): I have verified that this particular user that I am passing into the template data-context is being found and that their profile is populated with a firstname property, and I'm still getting the same error.
Based on another question I found I tried this. I added a Template rendered callback function like this:
client/templates/helpers/childResetPassword.js
Template.childResetPassword.rendered = function() {
console.log(this);
};
I do see this.data containing the correct user object in the browser console, but my this.data.profile.firstname still fails, yet again, with the same console output error. If there something I need to do between the template rendering and the template helper?? SO CONFUSED!!!
You don't have to mention data ... you can just call this.profile.firstname. Your application already understands 'this' as the data object that is returned.
Template.childResetPassword.helpers({
childFirstName: function() {
return this.profile.firstname;
}
});
So, #Joos answer is not wrong, however after a lot more trial and error I found the solution for the meteor project I am working on.
My project (unbeknownst to me until I looked around more) has the meteor package autopublish removed. Therefore in order to access data in my collections I have to be subscribed to them. So the best place I put this subscription line is within my Router.route declaration for this template:
Router.route('/reset-password/child/:_id', {
name: 'reset-child-password',
template: 'childResetPassword',
layoutTemplate: 'appLayout',
waitOn: function() { // this is new new line/option i added to my route
return Meteor.subscribe('users');
},
data: function() {
if (this.ready()) {
var childUser = Users.findOne({_id: this.params._id});
if (childUser)
return childUser;
else
console.error("Child User not found", childUser);
}
else {
this.render("Loading");
}
}
});
So with that being said, if you still have autopublish package in your project and you intending to keep it, then #Joos answer is all you need to do.
However, if you DO intend to remove it, you need my router solution above, combined with making sure that you have published your users collection like this somewhere on the server:
server/publications.js
Meteor.publish("users", function () {
return Meteor.users.find();
});
What is wrong with my code. Meteor.users.findOne() or Meteor.user.find() only works in Chrome. Does not work in Firefox nor Safari -- Im on a Mac. My error is:
TypeError: Meteor.users.findOne(...) is undefined
I want to have a user profile so upon registration, the profile field is created using Reactjs:
Registration component (Client):
Accounts.createUser({
...
profile: [] //later you'll see why for this.
});
Server:
// We us this to add more fields to a user registration:
Accounts.onCreateUser(function(options, user) {
user['regtype'] = options.regtype,
user['profile'] = options.profile
return user
});
Meteor.publish(null, function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'regtype': 1, 'profile': 1}});
} else {
this.ready();
}
});
The error comes from my Profile.jsx:
...
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user(),
};
},
getInitialState(){
profile = Meteor.users.findOne().profile; // the error is here
return{name: profile.name}
}
...
Strangely if I console.log(Meteor.user.find()) it shows as undefined. But works great in Chrome only. I have not tried MS Edge.
According to meteor docs
The basic Accounts system is in the accounts-base package, but
applications typically include this automatically by adding one of the
login provider packages: accounts-password, accounts-facebook,
accounts-github, accounts-google, accounts-meetup, accounts-twitter,
or accounts-weibo.
So If you have the accounts-base package you'll get the Meteor.user() and Meteor.users() functions. Check your .meteor/packages file for account-base package. Add if it is not listed in there.
Here is my answer to this. I hope others find this very useful. Basically I wish to bind the input fields with user's data (profile fields). Upon user registration, I add a profile field and set it to an empty array: profile: []
I need not to use getInitialState() but instead use getMeteorData(). With same code, I need to move the component's render in a function and render that:
...
renderedComponent(){
let instance = this;
render(<div><input value={instance.data.currentUser.profile.name} /></div>)
},
// we now wait until currentUser is loaded before calling the render function
render(){
return(<div>{this.data.currentUser? this.renderedComponent() : <p>Loading...</p>}</div>)
}
...
To sum up, nothing is wrong with firefox or any part of my code. When working with Meteor and React, this is the best approach.
I'm trying to implement a basic route using Flow Router. But no matter what _id of a collection document I request; I always only get the info about the first document in my collection - 'Requests'.
So here's my route definition in the file /lib/routes.js:
FlowRouter.route('/requests/:reqId', {
subscriptions: function (params, queryParams) {
this.register('theRequest', Meteor.subscribe('singleRequest', params.reqId));
},
action: function (params, queryParams) {
FlowLayout.render('layout', { aside: 'aside', main: 'singleRequest' });
console.log("Yeah! We are on the post:", params.reqId);
},
name: 'aRequest'
});
Here's my helper:
Template.singleRequest.helpers({
getRequest: function () {
return Requests.findOne();
}
});
Here's my server publish:
Meteor.publish('singleRequest', function (reqId) {
return Requests.find({ _id: reqId});
});
And here's the template:
<template name="singleRequest">
{{#if isSubReady 'theRequest'}}
{{#with getRequest}}
<h2>{{_id}}</h2>
<p>{{reqFrom}}</p>
<p>{{reqBy}}</p>
{{/with}}
{{else}}
loading...
{{/if}}
</template>
What am I doing wrong?
Note: In the console, I do see right 'reqId' slug due to the console.log command in the route definition. But I do not see the relevant info for the document which it belongs to.
Thanks!
OK my problem was that I previously had another subscription where I published all the Requests - not just the one with that certain _id. And because I did not create the helper to get only the one with that certain _id; of course server just sent me the very first request.
My solution was only to subscribe to previous subscription and define in the helper to fetch to the request _id:
FlowRouter.route('/requests/:reqId', {
subscriptions: function (params, queryParams) {
this.register('allRequests', Meteor.subscribe('Requests', params.reqId));
},
action: function (params, queryParams) {
FlowLayout.render('layout', { aside: 'aside', main: 'singleRequest' });
console.log("Yeah! We are on the post:", params.reqId);
},
name: 'aRequest'
});
and the helper:
Template.singleRequest.helpers({
getRequest: function () {
var reqId = FlowRouter.getParam("reqId")
return Requests.findOne({_id:reqId});
}
});
For anyone who browses to this question looking for how to get Flow Router to capture and dynamically link to slugs from the db, then call a template page for each item, I made a very simple example app and posted it on here on GitHub.
Hope it will help someone.
I believe your code is correct for what you are trying to do. However, I infer from your direct replication of the code from flow-router that you're pretty new to Meteor. Therefore, I'm willing to take a punt that you still have the autopublish package in your app.
Autopublish pushes all data in your collection to the client. Even without a publish/subscribe call.
You can do two things. To keep autopublish (which will make your development process easier at the start but maybe harder later on), just change your template helper to:
Template.singleRequest.helpers({
getRequest: function () {
return Requests.findOne({ _id: FlowRouter.getParam("reqId")});
}
});
Alternatively (and you will want to do this eventually), go the the command line and type:
meteor remove autopublish
You can read up on the pros and cons of autopublish in lots of places to make your own decision on which option is best for you. However, you should also consider whether or not you want to cache your data in future (e.g. using subsManager) so you may want to change your template helper regardless.
I am trying to search through several thousand records for a particular record. I have loaded a User model and it has several thousand Shipments that are async. In other words I have the serializer send down shipment_ids with the user model.
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true })
});
I want to search through all those ids to see if a particular record's id is among them. However I don't want to go to the api and load each and every single record. I just want to be able to search through the shipment_ids.
I have a jsbin that showcases it loading all the records. How do I change this so it doesn't make any api calls for a shipment and still finds the specific record?
Instead of doing
this.get('shipments').any(function(shipment){
return shipment.get('id') === "10124";
there has to be a way to go through only the ids, no?
Thanks!
The solution described in "Get belongsTo ID without fetching record"
seems to work as of Ember Data 1.0.0-beta.10. Briefly, you can do the following to access the underlying
modelInstance.get('data.belongsToRelation.id');
Presumably you can also do this (though I have not tested it):
modelInstance.get('data.hasManyRelation');
Original Answer (DO NOT USE):
I have accomplished this on a belongsTo by adding a second model field for the id. My example:
App.User = DS.Model.extend({
group: DS.belongsTo('group', { async: true }),
groupId: DS.attr('number')
});
So maybe you can do this?
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true }),
shipmentIds: DS.attr('array')
});
You will need to add an array transform in app/transforms/array.js (assuming you use ember-cli)
import DS from 'ember-data';
var ArrayTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
export default ArrayTransform;
Let me know if it works as I probably will need to something similar in my app soon.
This hack is very smelly but it's the only way I have found. And it is to search with _data
this.get('content._data.shipments').any(function(shipment){
return shipment.get('id') === "10124";
});
It won't make any api calls. But there has to be a more acceptable method that won't be prone to breakage when updating Ember.
Here is an updated jsbin to show this. Does anyone have a better solution?
Thanks!
Since Ember Data 2.3 you can use this code:
// get all ids without triggering a request
var commentIds = post.hasMany('comments').ids();
See http://emberjs.com/blog/2016/01/12/ember-data-2-3-released.html for details.
I have a route that creates a new record like so:
App.ServicesNewRoute = Ember.Route.extend({
model : function() {
return this.store.createRecord('service');
},
setupController: function(controller, model) {
controller.set('model', model);
},
});
Then I bind that model's properties to the route's template using {{input type="text" value=model.serviceId ... }} which works great, the model gets populated as I fill up the form.
Then I save record:
App.ServicesNewController = Ember.ObjectController.extend({
actions : {
saveService : function() {
this.get('model').save(); // => POST to '/services'
}
}
});
Which works too.
Then I click the save button again, now the save method does a PUT as expected since the model has an id set (id: 102):
But then when I look at the PUT request in Dev Tools, I see that the id attribute was not serialized:
As a result, a new instance is created in the backend instead of updating the existing one.
Please ignore the serviceId property, it is just a regular string property unrelated to the record id which should be named just id.
I don't know why the id is not being serialized... I cannot define an id property on the model of course since Ember Data will not allow it, it is implicit. So I don't know what I am missing...
Any help is greatly appreciated!
The base JSONSerializer in Ember-Data only includes id in the payload when creating records. See DS.JSONAdapter.serialize docs.
The URL the RestAdapter generates for PUTting the update includes the ID in the path. In your case I believe it would be: PUT '/services/102'.
You can either extract it from the path in your backend service. Or you should be able to override the behavior of your serializer to add the id like this:
App.ServiceSerializer = DS.JSONSerializer.extend({
serialize: function(record, options) {
var json = this._super.apply(this, arguments); // Get default serialization
json.id = record.id; // tack on the id
return json;
}
});
There's plenty of additional info on serialization customization in the docs.
Hope that helps!
Initially I used ronco's answer and it worked well.
But when I looked at ember data's source code I noticed that this option is supported natively. You just need to pass the includeId option to the serializer.
Example code:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serialize: function(record, options) {
options = options ? options : {}; // handle the case where options is undefined
options.includeId = true;
return this._super.apply(this, [record, options]); // Call the parent serializer
}
});
This will also handle custom primary key definitions nicely.
Well, as far as I know it's a sync issue. After first request you do the post request and then, it has been saved in the server, when you click next time the store haven't got enough time to refresh itself. I've got similar issue when I've created something and immediately after that (without any transition or actions) I've tried to delete it - the error appears, in your case there's a little bit another story but with the same source. I think the solution is to refresh state after promise resolving.