I am using iron:router in my app and I have a controller that subscribes to one document in a collection by using a parameter in the path. I can access all of the documents in my collection on the server, so I know that there is stuff in there, but when I try to access the data that I subscribe to in the waitOn method of the controller, the data is undefined. Here is the relevant code for this problem.
Router code:
this.route('unit', { path: 'unit/:unitId', template: 'unit', controller: 'UnitController' });
UnitController = BaseController.extend({
waitOn: function () {
return Meteor.subscribe('getUnit', this.params.unitId);
},
data: function () {
var id = this.params.unitId;
templateData = {
unit: Collections.units.model(Collections.units.getUnit(id))
};
return templateData;
}
});
Publication:
Meteor.publish('getUnit', function(id) {
return Collections.units.data.find({ unitId: id });
});
Here I have created an object for various things to do with my collection(I only included the important parts here):
Collections.units = {
data: new Mongo.Collection("units"),
getUnit: function (id) {
return this.data.findOne({ unitId: id });
},
model: function(unitEntity) {
return {
unitId: unitEntity.unitId,
createdAt: unitId.createdAt,
packets: unitEntity.packets,
getLastPacket: function (id) {
return _.last(this.packets);
}
};
}
};
I have been trying to debug this for quite a while and I can do all the things to the collection I want to on the server and in the publish method, but when it gets to the controller, I can't access any of the info. In the data method this.params.unitId returns exactly what I want so that isn't the issue. Where the exception gets thrown is when I try to read properties of unitEntity when I'm making the model but that is just because it is undefined.
Have any ideas what I am doing wrong? Thanks in advance for any responses.
The main problem that I was trying to solve's solution was to wrap the code inside the data method inside of if (this.ready()){ ... } and add an action hook with if (this.data()) { this.render(); }. After I got the subscription to work, I found that Christian was right in the comments with saying that my controller setup might mess things up. It was causing other strange exceptions which I fixed by just moving the hooks to each route instead of using the controller. As for my Collections setup, it may be unconventional, but all of that is working fine (as of right now). I may want to set them up the standard way at a later point, but as of right now its pretty handy for me to do things with the collections with the methods already written in the object.
Related
Im building an app using Node.js, specifically Express server-side and Vue client-side, with SQLite + Sequelize for managing the database.
Part of the functionality is that a user can make a post. This is currently possible but I needed to implement a relation so a post can be associated with the author.
I did this server-side in sequelize and all seems to be well on that end as the table columns all look correct with foreign key and references etc.
So now I need to somehow presumably set the current UserId for the post before it gets submitted. Here is the script element for the Vue component which is to be used as the interface to make posts.
<script>
import PostService from '#/services/PostService'
export default {
data () {
return {
post: {
title: null,
body: null
}
}
},
methods: {
async submit () {
try {
await PostService.post(this.post)
} catch (err) {
console.log(err.response.data.error)
}
}
}
}
</script>
I'm using Vuex to manage the state, where the 'user' object response from the database upon login is stored as simply user in my store.
So I was guessing all I had to do was the following to access the user:
post: {
title: null,
body: null
UserId: this.$store.state.user.id
}
The problem is any time I insert this line, the component stops working in some way. Doing as above stops the component displaying at all. So I tried setting it to null as default, then instead doing this inside my submit method:
this.post.UserId = this.$store.state.user.id
If I do this, the component displays but the submit method no longer runs; I also tried both these methods without the .id just incase, no difference.
I then tried removed from the data model completely, after reading that sequelize may be generating a setUser method for my post model. So tried the following in submit method before sending the post:
this.post.setUser(this.$store.state.user)
... again the method stops running. Again, even if adding .id.
Without trying to set any ID at all, everything works until the server returns an error that 'Post.UserID cannot be null', and everything worked perfectly before I implemented the relation.
I haven't really found anything useful beyond what I already tried from doing a few searches. Can anyone help out? I'm totally new to Node.
I have accessed other parts of the state such as isUserLoggedIn, and it all works fine and no crashes occur in those cases.
I just managed to get it working correctly, I'm not sure why it suddenly started working as I am sure I had tried this before, but I solved it by doing the following within my component's script element:
<script>
import PostService from '#/services/PostService'
export default {
data () {
return {
post: {
title: null,
body: null,
UserId: null
}
}
},
methods: {
async submit () {
this.post.UserId = this.$store.state.user.id
try {
await PostService.post(this.post)
} catch (err) {
console.log(err.response.data.error)
}
}
}
}
</script>
Posts now create and display as normal. If anyone knows anything I'm technicially not doing right in any of my approach though please let me know, as I am still learning Node, Express and Vue.
I have a computed property where I'm attempting to create a record if one does not exist, but keep getting a jQuery.Deferred exception when attempting to render the computed property.
Here's what I have so far:
deadlineDay: computed(function() {
const oneWeekFromNow = moment().startOf('day').add(1, 'week');
const store = this.get('store');
return store.queryRecord('deadline-day', {
filter: {
date: oneWeekFromNow
}
}).then(result => {
if (!result) {
result = store.createRecord('deadline-day', {
date: oneWeekFromNow
});
result.save();
}
return result;
});
}),
Then in my templates I'm attempting to render with a simple helper:
{{display-date deadlineDay.date}}
The {{display-date}} helper just calls return date.format('dddd, MMM Do')
It looks like Ember is attempting to render the promise itself instead of waiting for it to resolve.
This results in an error since .format is not a method of the promise.
I imagine this is an extremely common use-case, but that I have a lapse in understanding. Much help appreciated!
I'm not sure if it is relevant, but my backing store is sessionStorage via ember-local-storage
I agree that Ember may be attempting to render the promise itself instead of waiting for the promise to resolve. Unfortunately, I am not able to reproduce the error at this time.
In Ember.js it is typically recommended to place data calls in the route file. This will allow your query/save and all other data gathering to occur prior to the template file being loaded. Your depicted computed property shows no dependent keys, so this may justify moving the calls to the route file for your scenario.
generic route.js example:
import Ember from 'ember';
export default Ember.Route.extend({
async model() {
let record;
record = await this.store.queryRecord('record', { queryParams });
if (!record) {
record = this.store.createRecord('record', { properties });
}
return record.save();
},
});
However, if a promise within a computed property is of use, the author of Ember Igniter may have some additional helpful guidance that may be worthwhile.
The Guide to Promises in Computed Properties
I have a trouble with asynchronously loaded models in Ember. I thought I have already understood the whole "background Ember magic", but I haven't.
I have two models, let's say foo and boo with these properties:
foo: category: DS.belongsTo("boo", { async: true })
boo color: DS.attr("string")
In my route, I load all foos:
model: function(params) {
return this.store.findAll("task", "");
},
than in my template I render a component: {{my-component model=model}}. In the component's code I need to transform the model into another form, so I have:
final_data: function() {
this.get("model").forEach(function(node) {
console.log(node.get("category"));
});
return {};
}.property("model"),
When I try to access the "category" in the model, my code crashes:
EmberError#http://localhost:4200/assets/vendor.js:25705:15
ember$data$lib$adapters$errors$$AdapterError#http://localhost:4200/assets/vendor.js:69218:7
ember$data$lib$adapters$rest$adapter$$RestAdapter<.handleResponse#http://localhost:4200/assets/vendor.js:70383:16
ember$data$lib$adapters$rest$adapter$$RestAdapter<.ajax/</hash.error#http://localhost:4200/assets/vendor.js:70473:25
jQuery.Callbacks/fire#http://localhost:4200/assets/vendor.js:3350:10
jQuery.Callbacks/self.fireWith#http://localhost:4200/assets/vendor.js:3462:7
done#http://localhost:4200/assets/vendor.js:9518:1
.send/callback#http://localhost:4200/assets/vendor.js:9920:8
It seems to me, like the Ember didn't load the boos. How should I access them right to make Ember load them?
It's trying to load category, but the adapter is encountering some error. Can't tell what from your example.
Check your network tab.
When you access an async association from a template, Ember knows what to do. From code, such as your component's logic, Ember has no idea it needs to retrieve the association until you try to get it. The get will trigger the load, but will return a promise. You can do this:
get_final_data: function() {
Ember.RSVP.Promise.all(this.get("model") . map(node => node.get('category'))
.then(vals => this.set('final_data', vals));
}
As I'm sure you can see, this creates an array of promises for each node's category, calls Promise.all to wait for them all to complete, then stores the result into the final_data property.
Note, this is not a computed property; it's a function/method which must be called at some point, perhaps in afterModel.
I am trying to define a computed property that consists of a filtered hasMany relationship. When I loop over the items of the PromiseManyArray, I get undefined when trying to access the attribute I want to filter on. On later calls to this computed property, everything works fine.
This is a simplified version of my controller code:
export default Ember.Controller.extend({
availableModules: function () {
let thisModule = this.get('model')
console.log(thisModule.get('library.modules')) // This logs <DS.PromiseManyArray:ember604>
// loop over siblings
return thisModule.get('library.modules').filter(mod => {
// mod.classification is undefined
return mod.get('classification') !== 'basis'
})
}.property('model')
})
For the Module model we can assume that it has a classification attribute, and it belongs to a Library object, and the Library model hasMany modules.
I have tried something like this, and it logs properly the attribute classification, but I don't know how to return anything so that the template can render it.
availableModules: function () {
let thisModule = this.get('model')
thisModule.get('library.modules').then(mods => {
mods.forEach(mod => {
console.log(mod.get('classification'))
})
})
}.property('model')
So the problem seems to be that inside of the PromiseManyArray.filter method, the attributes of the found objects are not yet resolved... How can I create a promise that will return all filtered objects once those have been resolved? I don't know how to get my head around this. Thanks.
Inspired by Bloomfield's comment, and with help of this thread in the ember forum, I have found an acceptable solution. Basically it consists of resolving all the relationships in the route, so that when the controller is called, you don't have to deal with promises.
Solution:
In the model hook of the route, return a hash of promises of all the needed information
Define a custom setupController, and inside of it, store the model and the extra data in the controller
The route code looks like this:
export default Ember.Route.extend({
model(params) {
let module = this.store.findRecord('module', params.mod_id)
return Ember.RSVP.hash({
module: module,
siblingModules: module.then(mod => mod.get('library.modules')), // promise based on previous promise
})
},
setupController(controller, hash) {
controller.set('model', hash.module)
controller.set('siblingModules', hash.siblingModules)
},
})
Note: for the route to still work properly, the {{#link-to 'route' model}} have to explicitly use an attribute, like the id: {{#link-to 'route' model.id}}
Extra info
Bloomfield's approach consisted of using the afterModel hook to load the extra data in an attribute of the Route object, and then in the setupController, set the extra data in the Controller. Something like this:
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('module', params.mod_id)
},
afterModel(model) {
return model.get('library.modules').then(modules => {
this.set('siblingModules', modules)
})
},
siblingModules: null, // provisional store
setupController(controller, model) {
controller.set('model', model)
controller.set('siblingModules', this.get('siblingModules'))
},
})
But this feels like a hack. You have to return a promise in afterModel, but you can't access the result. Instead the result has to be accessed via .thenand then stored in theRoute` object... which is not a nice flow of information. This has however the advantage that you don't have to specify any attribute for the links in the template.
There are more options like using PromiseProxyArray, but that's too complicated for a newcomer like me.
For anyone running into PromiseManyArray issues in modern times, make sure you have async: false explicitly set on any hasMany relationships directly serialized by the API. Modern versions of Ember will behave unexpectedly if you don't, such as computed properties not working when you use pushObject.
I am trying to work with Ember.js
Can I expose my data model as JSON through a route or controller?
I have an object like this saved in the store:
this.store.createRecord('Person', {
id: 1,
name: this.get('name'),
email: this.get('email')
});
I want to expose this data from a route or controller as JSON object. I don't want to use any view.
Is it possible to do this?
Thanks for help!
EDIT
My route is:
App.ResultRoute = Ember.Route.extend({
model: function() {
return this.store.find('person', 1);
}
});
There is '1' because I want only this record.
In this way It works and I see in the view the {{name}} and the {{email} of the Person object.
I want to see only the JSON, I tried to do how you suggest me :
App.ResultRoute = Ember.Route.extend({
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
});
But I receive this error:
Uncaught Error: Assertion Failed: Error: More context objects were passed than there are dynamic segments for the route: error
What is my error?
The way I would do this would be, I would have an api in my model which would return a plain json object to whoever asked it. So the Person model would have a getPersonDetails method which will hide all the internal details, including the attributes and associations and whatever else, and return the state of the person object it is invoked upon.
So, for example, if you wanted to display a table of persons or something, you would do a createRecord, and just ask the newly created person object for it's details.
Start from the beginning of this guide. http://emberjs.com/guides/routing/specifying-a-routes-model/ It will show you how to specify a model for a route.
Then, read this entire guide on controllers: http://emberjs.com/guides/controllers/
In general, you would access that data from the route's model hook with:
this.store.find('person') // All records
If you wanted to access that first object as JSON, you could do:
var person_JSON = this.store.find('person').then(function (persons) {
//The persons records are now available so you can do whatever you want with them
console.log(persons.objectAt(0).get('content'));
});
You could also iterate over all records and strip out the content to produce raw json without the Ember wrapping... Just depends on what you need to really do.
Really the best place to put this would be the route's afterModel hook, though. You wouldn't be working with a promise, as Ember would have dealt with that for you:
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
Hope that helps.
Edit: Since you have one record try this:
afterModel: function (model) {
console.log(model.get('content'));
}