I am a noob in Ember js/node js/mongo db, and find hard to implement a simple case. I am using nodejs with mongoose as the backend, and emberjs for front end. The requirement is:
A user can belong to many organizations and an organization can have many users. A user can be admin or a customer. This is how I have modelled this in the backend:
models/organization.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var OrganizationSchema = new Schema({
org_name: String,
org_address: String,
org_admins: [{ type: Schema.Types.ObjectId, ref: 'User' }],
org_customers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
module.exports = mongoose.model('Organization',OrganizationSchema);
models/user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
username: {type: String, unique: true},
password: String,
role: String,
//organizations: [{type: Schema.Types.ObjectId, ref: 'Organization'}]
});
module.exports = mongoose.model('User',UserSchema);
then I have CRUD for Users. I created some organizations storing some users ids as org_admins, and some as org_customers.
I want to create an index view, where all the organizations come up, with corresponding admins and corresponding customers.
This is how the basic template appears on the browser now:
Organizations:
Org1 address1
admins:
5534a5172751c5b05b7e7cd0
5534cb359262868030211796
Customers:
org2 address2
admins:
5534a5172751c5b05b7e7cd0
customers:
5534cb359262868030211796
I want the username of the admins and username of the customers to be displayed.
This is the Ember side code:
app/models/organization.js
import DS from 'ember-data';
export default DS.Model.extend({
org_name: DS.attr('string'),
org_address: DS.attr('string'),
org_admins: DS.attr(),
org_customer: DS.attr()
});
app/models/user.js
import DS from 'ember-data';
var User = DS.Model.extend({
username: DS.attr('string'),
password: DS.attr('string'),
role: DS.attr('string')
});
export default User;
This is the template file app/templates/organization.hbs
<ul>
Organizations:
<p>
{{#each organization in model}}
<li>
<label> {{organization.org_name}} </label>
<label> {{organization.org_address}}</label>
<p> <label> admins: </label></p>
<ul>
{{#each admin in organization.org_admins}}
<li>{{admin}}</li>
{{/each}}
</ul>
<p> <label> Customers: </label></p>
<ul>
{{#each customer in organization.org_customers}}
<li>{{customer}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</p>
</ul>
<p>
<button {{action 'neworganization'}} > Add Organization </button>
</p>
{{outlet}}
The routes file
app/routes/organizations.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return this.store.find('organization');
}
});
I proceeded by getting the username in the controller, but it didnt work.
I understand I am doing something wrong in modelling the associations. I also tried using DS.hasMany instead of DS.attr(), but could not achieve my motive, I may be doing something wrong, not understanding the associations properly.
What is the right way I could do this. Please help.
Take a look at the section on defining releationships in the guide http://guides.emberjs.com/v1.11.0/models/defining-models/
When you use DS.hasMany you need a corresponding DS.belongsTo on the other model.
Another thing to watch out for would be making sure you either include all the corresponding user models in your organisation response payload, or mark the relationship with {async: true} so ember knows to retrieve those records separately if it needs them.
Related
I have four models
//models/exam.js
name: attr('string'),
owner: belongsTo('user'),
//models/question.js
content: attr('string'),
exam: belongsTo('exam')
//models/answer.js
owner: belongsTo('user'),
question: belongsTo('question'),
answer: attr('string'),
remarks: attr('string'),
exam: belongsTo('exam')
//models/user.js
owner : attr('string'),
email : attr('string'),
password : attr('string'),
I load the models into a route. Then, when I run the following template code,
{{#each model.answers as |ans|}}
<p>{{ans.question.content}}</p>
{{/each}}
//route.js
import Route from '#ember/routing/route';
import { hash } from 'rsvp';
export default Route.extend({
model: function(params){
return hash({
student: this.store.findRecord('student',params.id),
answers: this.store.query('answer',{
owner: params.id
}),
});
}
});
it shows the output as follows
<frontend#model:question::ember276:5>
<frontend#model:question::ember281:6>
<frontend#model:question::ember286:4>
why is it showing such an code, why not showning the original content?
I think you encountered a very special and rare case. The content has a special meaning for ember relationships. That's internal stuff and an end-user should not deal with it. But that's the reason, why you get
<frontend#model:question::ember276:5>
for
{{ans.question.content}} {{!-- .content doesn't return the content attribute --}}
I would work around it by changing the attribute's name at server and ember-model. If I server's attribute-name is not mutable, I would customize the ember-serializer. I.e.:
//app/serializers/person.js (ember g serializer question)
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
attrs: {
questionContent: 'content' //map server's attribute content to ember-model's questionContent
}
});
DEBUG: Ember : 1.7.1
DEBUG: Ember Data : 1.0.0-beta.12
DEBUG: Handlebars : 1.1.2
DEBUG: jQuery : 1.10.2
Having an issue with what I believe is the belongsTo attribute on my user model. (This happens on my other belongsTo attributes within my application as well). I have a Django backend which returns a response when I comment out the network: attribute.
{
email: "test#test.com",
first_name: "Test",
global_code: "daht64q691zy4k887ch",
global_id: "GBID-USER-dat64q6917zy4k887ch",
institution_gbid: "GBID-GINS-567j53ey0lojsu2kys",
institution_name: "Some University",
last_name: "Testing",
network: { },
view_policy: {
capability: "system:view",
description: "Anyone can view a user",
hold: true,
id: "daht64q691y4k887ch:system:view",
values: ""
}
}
Code for the User Model:
App.User = DS.Model.extend({
first_name: DS.attr('string'),
last_name: DS.attr('string'),
global_id: DS.attr('string'),
network: DS.belongsTo('basicgrouping')
}):
Code for Basic Grouping model:
App.Basicgrouping = DS.Model.extend({
global_id: DS.attr('string'),
name: DS.attr('string'),
gbid_code: function(){
return getGBIDCode(this.get('global_id'));
}.property('global_id')
});
Debugging ember-data I placed a console.log() within the following code:
relationshipsByName: Ember.computed(function() {
var map = Map.create();
this.eachComputedProperty(function(name, meta) {
console.log(name, meta);
if (meta.isRelationship) {
meta.key = name;
var relationship = relationshipFromMeta(this.store, meta);
relationship.type = typeForRelationshipMeta(this.store, meta);
map.set(name, relationship);
}
});
This seems to show that the type of the object that it belongs to is not being found (Basicgrouping) as it's returning App.undefined.
My theory is it may have something to do when parsing the server response and maybe the payload response. This also happens in other belongTo relationships in my code.
It turns out that there was a file that was overriding some of the DS. methods and causing an empty type to be sent. I was in the process of removing use of the shim but didn't know that it was being used.
Thanks the Bmacs from the ember community for the help debugging the issue.
I have the route /bets with a component to display bets filtered by future dates. The route template also has a link to route /bets/new where the user can add new bets. Each bet has a belongsTo relationship to User.
My problem is that my bets doesn't show up in the component even though I can see in Ember Inspector that the data is loaded. It does show up when I add a new bet through the form in /bets/new though.
My reasoning was that since the data is async, it doesn't load until I request it by pushing a new bet, but I can't get my head around it.
bets.hbs
{{outlet}}
{{#link-to 'bets.new' class="btn btn-default"}}
{{fa-icon "plus"}} New bet
{{/link-to}}
{{#upcoming-bets bets=model.bets}}
{{/upcoming-bets}}
bets.js
import Ember from 'ember';
export default Ember.Route.extend({
model () {
return this.store.findRecord('user', this.get('session.currentUser.uid'));
}
});
upcoming-bets.hbs
<h2>Upcoming bets:</h2>
{{#each upcomingBets as |bet|}}
<div class="bet">
<h3>{{bet.homeTeam}} vs {{bet.awayTeam}}</h3>
<p>
<small>{{moment-format bet.eventDate 'MMMM Do'}}</small>
</p>
<p>
Bet type: {{bet.type}}
</p>
<p>
Bet: {{bet.bet}}
</p>
<p>
Stake: {{bet.stake}}
</p>
<p>
User: {{bet.user}}
</p>
<button class="btn btn-danger" {{action "deleteBet" bet}}>{{fa-icon "trash"}}</button>
</div>
{{else}}
<p>
You don't have any upcoming bets. Maybe {{#link-to 'bets.new'}}add one?{{/link-to}}
</p>
{{/each}}
upcoming-bets.js
import Ember from 'ember';
export default Ember.Component.extend({
upcomingBets: function() {
var today = new Date();
today.setHours(0,0,0,0);
return this.get('bets').filter(function(bet){
return bet.get('eventDate') > today;
});
}.property('bets.#each'),
actions: {
deleteBet: function(bet){
bet.destroyRecord();
}
}
});
new.js
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
addBet() {
var newBet = this.store.createRecord('bet', {
created: new Date(),
sport: this.get('selectedSport.name'),
league: this.get('league'),
homeTeam: this.get('homeTeam'),
awayTeam: this.get('awayTeam'),
type: this.get('type'),
bet: this.get('bet'),
stake: this.get('stake'),
odds: this.get('odds'),
eventDate: this.get('eventDate'),
});
// Save user as well as bet
var user = this.get('user');
user.get('bets').addObject(newBet);
newBet.save().then(function(){
return user.save();
});
}
}
});
user.js
import DS from 'ember-data';
export default DS.Model.extend({
email: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
bets: DS.hasMany('bet', { async: true })
});
bet.js
import DS from 'ember-data';
export default DS.Model.extend({
created: DS.attr('date'),
sport: DS.attr('string'),
league: DS.attr('string'),
homeTeam: DS.attr('string'),
awayTeam: DS.attr('string'),
type: DS.attr('string'),
bet: DS.attr('string'),
stake: DS.attr('number'),
odds: DS.attr('number'),
eventDate: DS.attr('date'),
win: DS.attr('boolean', {defaultValue: false}),
closed: DS.attr('boolean', {defaultValue: false}),
user: DS.belongsTo('user', { async: true })
});
I appreciate any pointers. Thank you!
Update 22 nov 2016
I've tried making things simpler and more clean by moving the filtering part to a controller as well as making the filter itself simpler by just matching a string. I still have the exact same issue - nothing gets rendered until I add a new bet at which point the filter works as intended and the correct bets show up. I've also had a hard time researching the issue as most examples with filtering out there waits for some sort of action before the filtering is done, for example typing something in an input field. I need the filtering to be done on load.
Here are my updated files:
bets.hbs
{{outlet}}
{{#link-to 'bets.new' class="btn btn-default"}}
{{fa-icon "plus"}} New bet
{{/link-to}}
{{#each upcomingTest as |bet|}}
<p>
{{bet.homeTeam}}
</p>
{{/each}}
controllers/bets.js
import Ember from 'ember';
export default Ember.Controller.extend({
upcomingTest: function() {
var team = 'EXAMPLE';
return this.get('model.bets').filterBy('homeTeam', team);
}.property('model.bets.#each')
});
Update 23 nov 2016
So I think I've proved that the issue has to do with me wanting to filter data that has a relationship to the model and isn't being loaded directly in the route. I tried using the exact same filter on data that I loaded directly in the route and that works great, even directly on load.
I have two models, a User with many Messages:
App.Router.map(function() {
this.resource('user', { path: ':user_id' }, function () {
this.route('profile', { path: 'profile' });
this.resource('messages');
});
});
App.User = DS.Model.extend({
displayName: DS.attr('string'),
email: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
location: DS.attr('string'),
messages: DS.hasMany('message')
});
App.Message = DS.Model.extend({
user: DS.belongsTo('user'),
createdAt: DS.attr('date'),
updatedAt: DS.attr('date'),
fullText: DS.attr('string'),
subject: DS.attr('string'),
recipients: DS.attr('string')
});
Currently, I must load the entire model, including all messages associated with the user, when I perform a search for users.
What I'd like to do instead is:
Retrieve the user
Retrieve the messages of the user (when the user is selected)
I cannot find an easy way to do this in Ember. My best guess is call find in the route, possibly:
App.UserRoute = Ember.Route.extend({
model: function (params) {
var user = this.store.find('user', params.user_id);
this.store.find('message', { user: 3 });
return user;
}
}
But this generates the url /messages?user_id=3. What I'd like instead is something like /users/3/messages.
How do I query for messages associated with a user?
I think { async: true } is what you're looking for. See http://discuss.emberjs.com/t/what-is-an-async-relationship-async-true-vs-async-false/4107 for some discussion on what it is, and how to use it.
You would probably want to setup your model like this:
App.User = DS.Model.extend({
displayName: DS.attr('string'),
email: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
location: DS.attr('string'),
messages: DS.hasMany('message', { async: true } )
});
Then when your Handlebars template makes a request for {{myUser.messages}} (or {{myUser.messages.someProperty}} Ember Data will look at the User model, and note that the message id's (say, 5, 10, and 12). It will then look in the local datastore for messages 5, 10, and 12. If it has them, it will display them. If they're not present, it will then use the Adapter you've defined for Message (or if no adapter is defined the default RESTAdapter) to fetch these using an HTTP GET.
I believe it will make one request for each id (versus one request for all of them), but that's something I'm not 100% sure about.
I'm having some issue with my Ember web application.
I'm using Ember 1.0 stable as the base JS framework and Ember-data.js 1.0 beta 1 to handle the relationships between model and the CRUD operations.
My simple application manages two entities: User and Order with a many to many relationship between them.
Here are the models:
/*User Model*/
App.User = DS.Model.extend({
first_name: DS.attr('string'),
last_name: DS.attr('string'),
fullName: function() {
return this.get('first_name') + ' ' + this.get('last_name');
}.property('first_name', 'last_name'),
userorders: DS.hasMany('userorder', {async: true})
});
/*Order Model*/
App.Order = DS.Model.extend({
description: DS.attr('string'),
userorders: DS.hasMany('userorder', {async: true})
});
I created the model Userorder which maintains the one-to-one relationships with both User and Order.
Here is the model for Userorder:
App.Userorder = DS.Model.extend({
order: DS.belongsTo("order", {async: true, key: "order_id"}),
user: DS.belongsTo("user", {async: true, key: "user_id"})
});
My problem comes up when i try to get (into UserorderRoute) the single instance of a usersorder. Follows the code for UserorderRoute:
App.UserorderRoute = Ember.Route.extend({
model: function(params) {
return this.store.find("userorder", params.userorder_id);
}
});
The userorder instance is correctly fetched, but if I try to access to the 'order' object I get undefined, (obviously, I get the same behavior with the access to 'user').