Finding data in a belongs to relationship in ember - javascript

I am trying to find the account associated with the current user in an ember project. I am able to get the users id and pass it to a handlebars script via {{account.user.id}}. All my attempts to find an account with this user ID in my model hook, however, have been unsuccessful.
My current model hook in routes/my-account.js:
model (params) {
let accountID = this.store.query('account', { filter: { user: { id:currentUser} } });
console.log(accountID.id);
return this.get('store').findRecord('account', accountID);
}
accountID is returning as an ember class, but I cannot seem to parse any data from it. How would I go about getting the ID from the returned ember class in order to pass it to my get request?

To get and set properties from and to Ember objects, you have to use get and set, e.g.:
console.log(account.get('id'));
More to the point, though, your .query will (or should, at least) return an array of account models matching the filter. It will be wrapped in a promise—because it's an asynchronous network call—so you'll need to .then it. And you probably just want to grab the first account:
model() {
return this.store.query('account', { filter: { user: { id: currentUser } } })
.then(function(accounts) {
return accounts.get('firstObject');
});
}
If you have a proper {json:api}, you can just get the user, and then get its account relationship from e.g. /api/users/:id/account. Your model hook would look something like:
model() {
return this.store.findRecord('user', currentUser)
.then(function(user) {
return user.get('account');
});
}

Related

Trouble accessing data-context data in template from Iron Router

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();
});

Passing an _id and a search query in the string

Using meteor for a test project. Can't figure out how to pass an ID and a search parameter when playing with the sample todo app they have.
For the moment, I have in my iron router:
this.route('team', {
path: '/team/:_id',
onBeforeAction: function() {
this.todosHandle = Meteor.subscribe('todos', this.params._id);
// Then filter mongoDB to search for the text
}});
The thing is, I also want to pass an optional search parameter to search for todos. So something like path: '/team/:_id(/search/:search)?'
Any ideas how to do this?
From your explanation, it sounds like you would like to carefully control which documents are actually published to the client, rather than publishing all of them and narrowing down your result set on the client. In this case, I would suggest first defining a publication on the server like so:
Meteor.publish('todosByTeamIdAndSearch', function(todoTeamId, searchParameter) {
var todosCursor = null;
// Check for teamId and searchParameter existence and set
// todosCursor accordingly. If neither exist, return an empty
// cursor, while returning a subset of documents depending on
// parameter existence.
todosCursor = Todos.find({teamId: todoTeamId, ...}); // pass parameters accordingly
return todosCursor;
});
To read more about defining more granular publications, check this out.
With a publication like the one above defined, you can then setup your route like so:
Router.route('/team/:_id/search/:search', {
name: 'team',
waitOn: function() {
return Meteor.subscribe('todosByTeamIdAndSearch', this.params._id, this.params.search);
},
data: function() {
if(this.ready()) {
// Access your Todos collection like you normally would
var todos = Todos.find({});
}
}
});
As you can see from the example route definition, you can define the path for the route exactly as you would like to see it directly in the call to the Router.route() function and access the parameters directly passed in like in the waitOn route option. Since the publication has been defined like I suggested, you can simply pass those route parameters right to the Meteor.subscribe() function. Then, in the data route option, once you have checked that your subscription is ready, you can access the Todos collection like normal with no further narrowing of the result set if you do not need to do so.
In order to learn more about how to configure your routes, check these two links out: Iron Router Route Parameters and Iron Router Route Options
On the client, you would just use Meteor.subscribe('todos'); in top-level code. 'todos' here doesn't refer to the Collection, it's an arbitrary string. Subscriptions don't care about what route you're on.
On the server, you would have a publish function like this:
Meteor.publish('todos', function() {
if (!Meteor.userId()) return;
// return all todos (you could pass whatever query params)
return Todos({});
});
Then, on your route definition:
Router.route('team', {
path: '/team/:_id',
data: function() {
if (this.params.query) { //if there's a query string
return Todos.find(/* according to the query string */).fetch();
else {
// return all the user's todos
return Todos.find({ uid: this.params._id }).fetch();
}
}
});

Accessing parent route model in Ember.js

I'm trying to write a route that need access to its parent's model. I use this.modelFor(), but when I do that, the parent's model isn't completely loaded, so all its properties contains null.
This is the router, with two dynamic segments:
MGames.Router.map(function () {
this.resource('games', function () {
this.resource ('game', {path: '/:game_id'}, function () {
this.resource('board', {path: '/boards/:board_id'});
});
});
});
This is my GameRoute, who works perfectly:
MGames.GameRoute = Ember.Route.extend ({
model: function (params) {
return MGames.Game.find(params.game_id);
}
});
And finally this is the child route, who need access to the Game model, and this is what I wrote. But no matters what I do, the console.log() always prints null. If I check the game variable, the isLoad property is always null:
MGames.BoardRoute = Ember.Route.extend ({
model: function (params) {
var game = this.modelFor ('game');
console.log (game.get("id"));
return MGames.Board.find(game.get("id"), params.board_id);
}
});
Am I doing something wrong, or (as I suspect) I'm missing some Ember concept?
This part of your code looks good. Your assumptions are correct in that, the nested route should get the model of the parent via modelFor.
I suspect your find method is the source of the bug. I looked at your previous question, and I'm assuming the same Game.find is used here(?)
The problem is to do with Promises. Ember's router understands the async nature of the model hook. But it relies on you returning a Promise to do it's work. Currently you are using the jQuery promise but returning the game object immediately in it's uninitialized state. The query loads from the server but the model() hook is assumed to have resolved before that happens.
You want to directly return the jQuery Promise from your model hook + Do the parsing in the first then and return that as the result.
Here's your modified Game.find. Same principles apply to the other finders.
find: function (game, board) {
url = [MGames.GAMES_API_URL];
url.push ('games');
url.push (game);
url.push ('boards');
url.push (board);
url = url.join('/');
return $.getJSON(url)
.then(function(response) {
var game = MGames.Game.create({isLoaded: false});
game.setProperties(response);
game.set('isLoaded', true);
return game;
});
}
Note that, the game object is returned as is. Ember understands that when the promise is resolved(by returning anything other than a promise), that result is the model for the model() hook. This game object is the model that will be now be available in modelFor in the nested route.

Ember deleteRecord too much recursion

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

Overriding backbone's parse function

I'm trying to use Backbone with an API.
The default API response format is:
{
somemetadatas:xxx ,
results:yyy
}
Whether it's a fetch for a single model or a collection.
So as far as I know I can override the Backbone parse function with:
parse: function (response) {
return response.results;
},
But I've seen in the documentation:
parse collection.parse(response)
parse is called by Backbone whenever
a collection's models are returned by the server, in fetch. The
function is passed the raw response object, and should return the
array of model attributes to be added to the collection. The default
implementation is a no-op, simply passing through the JSON response.
Override this if you need to work with a preexisting API, or better
namespace your responses. Note that afterwards, if your model class
already has a parse function, it will be run against each fetched
model.
So if I have a response for a collection fetch like that:
{
somemetadatas:xxx ,
results:[user1,user2]
}
The first parse function on the collection will extract [user1,user2].
But the doc says:
Note that afterwards, if your model class
already has a parse function, it will be run against each fetched
model.
So it will try to find response.results; on user1 and user2
I need both parse functions on the model and collection because both model and collection datas will be under the result attribute.
But if i fetch on a collection, I don't want the model parse function to be used againt a single array element.
So is there a solution to this problem?
I think of a solution where my collection parse function will transform something like this:
{
somemetadatas:xxx ,
results:[user1,user2]
}
into something like this:
[ {results.user1} , {results.user2} ]
So that the model parse function will not fail on a collection fetch.
But it's a bit hacky... is there any elegant solution to this problem?
By the way, as my API will always produce results of this form, is it possible to override by default the parse function of all my models and collections? (Sorry i'm a JS noob...)
You could test if the data you receive is wrapped by a results member and react accordingly. For example,
var M = Backbone.Model.extend({
parse: function (data) {
if (_.isObject(data.results)) {
return data.results;
} else {
return data;
}
}
});
And a Fiddle http://jsfiddle.net/9rCH3/
If you want to generalize this behavior, either derive all your model classes from this base class or modify Backbone's prototype to provide this function :
Backbone.Model.prototype.parse = function (data) {
if (_.isObject(data.results)) {
return data.results;
} else {
return data;
}
};
http://jsfiddle.net/9rCH3/1/
Parse also must be implemented in the Collection.
var EgCollection = Backbone.Collection.extend({
parse: function (data) {
if (_.isObject(data.results)) {
return data.results;
} else {
return data;
}
}
});
http://backbonejs.org/#Collection-parse

Categories

Resources