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.
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
}
});
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.
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 created model Consultation:
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
records: DS.hasMany('record', { async: true }),
currentUser: DS.belongsTo('user'),
remoteUser: DS.belongsTo('user')
});
And also I created model Record:
import DS from 'ember-data';
export default DS.Model.extend({
record_text: DS.attr('string'),
record_poster_id: DS.attr('string'),
record_consultation_id : DS.attr('number'),
consultation: DS.belongsTo('consultation'),
isMine: DS.attr('boolean')
});
At first all consultations load during opening page. And I don't want to load all records of each consultation during opening page. To do this I added async: true but all records loaded simultaneously sending many requests like /consultations/:id/records. After that consultation and records still non-joined. I have next json response for consultation:
{
"consultations":[
{
"id":140721,
"title":"ExpertId: 41217, clientId: 0",
"links":{
"records":"records"
},
"currentUser":41217,
"remoteUser":159984
},
......
]
}
And for records:
{
"records":[
{
"record_id":681952,
"record_consultation_id":140721,
"record_poster_id":0,
"record_text":"1",
},
........
]
}
I think I need to override default serializer. I tried to create serializer for Record:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
primaryKey: 'record_id',
keyForRelationship: function(key, kind) {
return 'record_consultation_id';
}
});
But it still doesn't work.
Please advise how to join models using links?
UPDATE:
Template
{{#each item in model.records}}
<div class="table message">
{{item.record_text}}
</div>
{{/each}}
I am doing Async (lazy loading) of data using RESTAdaptor and Ember-Data too.
For my links area, i put in the full request URL as follows:
"links":{
"records":"/consultations/140721/records"
},
And using firebug to look at when the request gets sent off, its only when I request for the async content that the AJAX gets fired off.
model.get('records');
Can you provide your Controllers & Template code so I can see how your accessing the Records?
Okay, so I'm trying to copy an existing model as a new model instance (so all attributes are the same except for its ID. In my template, I have an action, copy, that passes the model in scope at that place in a list to the controller so it can be copied. My controller code is below. I appear to be able to create a new record, but its ID is set to 'fixture-0', 'fixture-1' etc., and calling .save() on it (see below) results in an error,
Uncaught TypeError: Cannot call method 'serialize' of undefined
As I'm currently stubbing the models for development, I'm using the fixture adapter, as it seems that's got something to do with the problem.
Controller code:
REApp.ApplicationController = Ember.ArrayController.extend({
actions: {
copy: function(current_model){
var self = this;
console.log(current_model);
var new_record = this.store.createRecord('cycle', {
railsId: null,
accountId: current_model._data.accountId,
//lots more attributes here
});
new_record.save();
console.log(new_record);
}
}
});
console.log call from the controller, showing new_record:
a {id: "fixture-0", store: a, container: t, currentState: Object, _changesToSync: Object…}
Note: even if you take out the .save() method call from the controller, this new record is created with the id set to a string. If .save() is not called (and thus no error raised in the controller), then the new record appears in the ember inspector's data panel with its ID set to a string as above.
This is only my third Ember app (and my first with 1.3.0), so open to the idea that I'm just doing it wrong.
Edit:
Here's the model definition. If it's relevant, I've had other errors returning both varOfModelObject.save(); and this.content.save(); from the controller since, so def thinking it's related to the FixtureAdapter.
REApp.Cycle = DS.Model.extend({
//id: DS.attr('number'), not needed with fixtures
railsId: DS.attr('number'),
siteId: DS.attr('number'), //all numbers here are ints unless noted
clientId: DS.attr('number'),
accountId: DS.attr('number'),
startAt: DS.attr('datetime'),
endAt: DS.attr('datetime'),
chargePerEvent: DS.attr('decimal', {defaultValue: 0.0}),
createdAt: DS.attr('datetime'),
updatedAt: DS.attr('datetime'),
scheduleYaml: DS.attr('string'),
allDay: DS.attr('boolean'),
repeat: DS.attr('boolean'),
exceptionDates: DS.attr('string'),
additionalDates: DS.attr('string'),
charge: DS.attr('number'), //decimal in rails
chargePeriod: DS.attr('string'),
oldRegularEventId: DS.attr('number'),
scheduleHuman: DS.attr('string'),
bagLimit: DS.attr('number'),
untilDate: DS.attr('number'),
sendToWebfleet: DS.attr('boolean', {defaultValue: false}),
contractId: DS.attr('number'),
hourlyRate: DS.attr('number'), //decimal in rails
hourlyCharge: DS.attr('number', {defaultValue: 0.0}), //decimal in rails
doubleEvent: DS.attr('number'),
//these are used for the simple view
weekdays: DS.attr('boolean', {defaultValue: null}),
weekends: DS.attr('boolean', {defaultValue: null}),
//data below this needed for associations
clientName: DS.attr('string'),
commentBody: DS.attr('string'),
siteAddress: DS.attr('string'),
carer1Id: DS.attr('number', {defaultValue: null}),
carer1Name: DS.attr('string'),
carer2Id: DS.attr('number', {defaultValue: null}),
carer2Name: DS.attr('string'),
users: DS.hasMany('user', {async: true}),
items: DS.hasMany('item', {async: true})
});
As mentioned in the comments above, when working with fixtures, you can create new objects in memory, but they are distinguished from the hard-coded fixture objects by a fixture- prefix in their object ID. As a result of this, you may find behaviour being a little screwy - perhaps this will be fixed in a newer version of Ember Data, but in the meantime the answer seems to be to get off using fixture data and move to a live data situation as quick as possible in your dev process.
try this and tell me what you get, yes in the console you gonna get an error yet it works, simply override the method createrecord as follows:
REApp.ApplicationAdapter = DS.FixtureAdapter.extend({
mockJSON: function(store, type, record) {
try {
record.id = Math.floor((Math.random() * 100) + 1);
return this._super(store, type, record);
}
catch(err) {
}
},
}
);