I have a ticket model that has a property for ticket_holders that is a hasMany relationship
ticket_holders: DS.hasMany('ticket-holder'),
The ticket-holder model has a ticket property defined as a belongsTo
ticket: DS.belongsTo('ticket')
In the ticket serializer I defined the EmbeddedRecordsMixin and set the attrs property like so
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin,{
attrs: {
ticket_holders: {embedded: 'always'}
},
...
An example ticket from the API looks like this
{
"eid":"5060",
"user_id":"13193",
"ticket_id":"612",
"ticket_purchase_code":"1ab9e0f20178220a75d5d2ca18322efa",
"ticket_purchase_behalf_name":null,
"ticket_purchase_behalf_email":null,
"ticket_name_alias":null,
"ticket_cost":"0.00",
"ticket_ticketing_time":"2015-11-17 11:24:32",
"ticket_qty":"1",
"ticket_total":"1.00",
"ticket_paid":"1",
"ticket_transaction_id":"noch_5060564b54c0c42ffy0M8tpk",
"ticket_validation_phrase":"",
"ticket_pay_type":"card",
"ticket_qty_redeemed":"0",
"ticket_refunded":"0",
"ticket_promo_code_used":"",
"ticket_name":"Ticket #2",
"ticket_purchase_user_name":"Tony Stark",
"ticket_holders":[
{
"holder_code":"hc_299dc35e",
"holder_user_id":"0",
"holder_id":"8181",
"holder_name":"Jordan again",
"holder_email":"Riser.jordan#gmail.com",
"holder_phone":"",
"holder_paddle_number":"4888",
"holder_rsvp":"0",
"holder_redeemed":"1",
"holder_last_updated":"2016-05-13 19:07:13",
"ticket_qrcode":"barcode\/barcode.processor.php?encode=QRCODE&bdata=&qrdata_type=link&qr_link_link=http%3A%2F%2Fbidr.co%2Fl%2F8qexEa&height=500&scale=2&bgcolor=%23ffffff&color=%231B3448&file=&folder&type=png&Genrate=Create+Barcode&ECLevel=L&margin=",
"ticket_pdf":"http:\/\/bidr.co\/ticket\/pdf_ticket.php?s1=1ab9e0f20178220a75d5d2ca18322efa&s2=hc_299dc35e"
},
...
]
}
The primaryKey for a ticket is set to primaryKey: 'ticket_purchase_code', so in the normalize function for the store method I'm using I loop over each ticket grab it's ticket_purchase_code and then loop over each ticket's ticket_holders array and set a new property on each called ticket_purchase_code to the one I got from it's parent ticket record.
It seems that ticket-holder's are not always being attributed to the ticket when I attempt to loop over the ticket and then it's ticket_holders in the template
{{#each tickets as |purchase|}}
...
{{#each purchase.ticket_holders as |ticket|}}
...
{{/each>
{{/each}}
Sometimes I only see one ticket-holder under each ticket sometimes I see all the correct ticket-holder's under each ticket, but more consistently I'm only seeing one ticket-holder under each ticket
Do I have my relationships set up wrong?
I have also tried changing the ticket_purchase_code on each ticket_holder that I loop over in the ticket serializer to ticket_id to see if that would work but it's not working correctly either.
I've run into this before myself Jordan. For me the situation was that I had 2 tickets that both had the same ticket-holder, i.e., "ticket_id":"612" and "ticket_id":"633" both have "holder_id":"8181". By the nature of a belongsTo poor "holder_id":"8181" can only be linked to a single ticket, so only the last ticket to load into ember-data that has "holder_id":"8181" will get to keep him.
The Solution
Trying commenting out DS.belongsTo('ticket') in ticket-holder, and see if they all load in like you'd expect.
// Try commenting the ling below out
ticket: DS.belongsTo('ticket')
Example, in this http://jsbin.com/sevipa/1/edit?html,js,output ,
App.Contact = DS.Model.extend({
name : DS.attr('string'),
// Turn the below on to see your problem. Matt H belongsTo 2 different companies
//company : DS.belongsTo('Company')
});
I commented out the relationship to company : DS.belongsTo('Company') so that Matt H will show up in both the first 2 companies. If you put the line back in, you will see that he only continues to belongTo Johns Shoe Repair since it loads into ember-data after Housing4Hope.
Related
I'm trying to build a table component that displays all matching data. I don't know how to get this working.
I have multiple Platforms that have many Markets.
The model is easy:
model() {
return this.store.findAll('platform', {include: 'markets'});
}
I can display check boxes so the user can select what platforms to compare and access the id's in the controller.
How do I go about getting the correct records from the model in the controller? I can't do this in the route because it depends on what platforms are selected.
I can use Ember Data:
this.get('store').findRecord('platform', id, {include: 'markets'})
But I can't figure out how to access the markets.
I tried enumerables also, but the same issue:
this.get('model').filterBy('id', id)
After this, what is a clean way to get the matching markets based on their name?
For your situation, you can access and compare the markets based on any selected platforms within your component. Each platform should have a relationship established to its associated markets within the model file. The relationship will allow you to access the markets off of the platform. For example, platform.get('markets') within a controller, component or {{platform.markets}} within a template. For a bit more detail on the rough process for implementing throughout the app files:
//Within the platform.js model just to set the basic relationship
markets: hasMany(),
//Within your controller.js build an array of selected platforms
selectedPlatforms: null,
actions: {
selectPlatform(platform) {
this.get('selectedPlatforms').pushObject(platform);
},
}
//Within your template.hbs provide your component the array
{{matching-markets platforms=selectedPlatforms}}
//Within your component.js compare the platform markets
platforms: null,
matchingMarkets: computed('platforms', function() {
const platform1Markets = this.get('platforms.firstObject.markets');
const platform2Markets = this.get('platforms.lastObject.markets');
return /* Compare platform1Markets against platform2Markets */;
}),
// Within the component.hbs
{{#each matchingMarkets as |market|}}
{{market.name}}
{{/each}}
Please reference the below link to an EmberTwiddle to see a rough (slightly hacky) example that might provide some better insight:
https://ember-twiddle.com/364d9c04d37593f4a7c40cccf065a8fc?openFiles=routes.application.js%2C
I have a one to many relationship in the model of the application to relevantUsers. Now I want to iterate via the {{#each}} helper over those values. Which works.
content: function()
{
return this.get('controllers.application.model.relevantUsers');
}.property('controllers.application.model.relevantUsers'),
And when removing an item from the relevantUsers the view updates. But when adding a new relevantUser nothing happens. The user gets added to the data store, but the view does not update. Am I missing something?
This is how I create a new user
// Create new user
var relevantUser = this.store.createRecord('relevantUser', relevantUserData);
// And push it to remote
relevantUser.save();
In my application, I've done it like [CoffeeScript] :
video = self.store.createRecord 'video', id: #Id, title: #Title, thumbnailUrl: #ThumbnailUrl
#Formats.map (i) -> //Formats is simple JavaScript array.
format = self.store.createRecord 'format', itag: i.itag, quality: i.quality, resolution: i.resolution, type: i.type, url: i.url
video.get('formats').then (f) ->
f.pushObject format
The difference, is, that I use .pushObject() method here. As #fanta wrote in his comment, you have to get array that has relationship with model, so it will notify all observers.
More example code.
Using Ember-Data 0.13-59 & Ember 1.0.0 RC 6 (from starter kit)
Problem: upon save() to a new record made from App.Userstat.createRecord({ ... }) the server gets the POST and successfully returns an id but the new record is not available in the Userstat model.
To better understand example: this is a quiz app(for multiple choice questions). Each question has several choices and when a user selects a choice, their choice to the corresponding question is stored in a Model, App.Userstat.
At each question, the app needs to know whether the user has already answered this question or if it's new.
I use a computed property as a setter and getter. The setter is called when a user selects a choice (the choice's value is passed to computed property). First it checks if a record exists for the user's current question. If it doesn't exist it will create a new record. If it does exist, it should only issue a PUT request with updated content.
Code Updated(July 8, 11AM)
App.UserstatsController = Ember.ArrayController.extend();
App.QuestionController = Ember.ObjectController.extend({
needs: "userstats",
chosen = function(key, value) {
// getter
if(value === undefined) {
// something goes here
// setter
} else {
// the question.id is used to compare for an existing record in Userstat mdoel
var questionId = this.get('id');
var questionModel = this.get('model');
// does any Userstat record belong to the current question??
var stats = this.get('controllers.Userstats');
var stat = stats.get('model').findProperty('question.id', questionId);
// if no record exists for stat, means user has not answered this question yet...
if(!stat) {
newStat = App.Userstat.createRecord({
"question" : questionModel,
"choice" : value // value passed to the computed property
)}
newStat.save(); // I've tried this
// newStat.get('store').commit(); // and this
return value;
// if there is a record(stat) then we will just update the user's choice
} else {
stat.set('choice', value);
stat.get('store').commit();
return value;
}
}.property('controllers.Userstats')
No matter how many times I set chosen it always sends a POST (as opposed to an update only sending a PUT request) because it never adds the record to the model the first time.
To demonstrate further, in the setter part of the computed property, when I put this code:
var stats = this.get('controllers.Userstats')
console.log stats
the Userstats controller shows all previously existing records, but not newly submitted records!
How come the new record isn't available after I save() or commit() it???
Thanks :)
EDIT
maybe it has something to do with me adding a record to the singular model App.Userstat and then when I look for it, I'm searching using the UserstatsController which is an Array controller???
I don't know if it's a typo, but the computed property is defined the wrong way and should be like this:
App.QuestionController = Ember.ObjectController.extend({
needs: 'userstats',
choice: 'controllers.userstats.choice',
chosen: function(key, value) {
...
}.property('choice')
...
});
Inside the property() you should also define properties that trigger the computed property if they change. This way if choice changes the chosen cp will be triggered.
Please let me know if it helps.
I have the following code which I expect to return [1000] but returns [undefined]:
this.get('details').getEach('gross_total')
But
this.get('details')[0].get('gross_total')
does work and returns 1000.
I am using rc3 right now. The same code in rc1 worked fine.
My persistence layer is taken from discourse. The relevant models are below. Invoice has_many details
App.Invoice = App.Model.extend App.Commentable,
invoice_number: App.Attr('string')
description: App.Attr('string')
issue_date: App.Attr('date')
due_date: App.Attr('date')
contact: App.BelongsTo('App.Contact', "contacts")
details: App.HasMany('App.InvoiceDetail', "detail_ids")
payments: App.HasMany('App.Payment', "payment_ids")
invoice_status: App.Attr('string')
comments: App.HasMany('App.Comment', "comment_ids")
App.InvoiceDetail = App.Model.extend App.DestroyableChild,
detail_no: App.Attr('int')
description: App.Attr('string')
quantity: App.Attr('float')
unit_price: App.Attr('currency')
consumption_tax_rate: App.Attr('float')
vat_rate: App.Attr('float')
discount_type: App.Attr('string')
discount_value: App.Attr('currency')
product: App.BelongsTo('App.Product', 'products')
I have seen this issue when prototyping require.js use for loading my app. Using ember.js 1.0.0-rc.1.
I traced it down to having two ember.js script instances (each of which contains its own different Ember.META_KEY) which implied the illogical behavior.
So the answer seems to be to make sure only a single ember.js copy is loaded within the browser window context.
I have a DS.Store which uses the DS.RESTAdapter and a ChatMessage object defined as such:
App.ChatMessage = DS.Model.extend({
contents: DS.attr('string'),
roomId: DS.attr('string')
});
Note that a chat message exists in a room (not shown for simplicity), so in my chat messages controller (which extends Ember.ArrayController) I only want to load messages for the room the user is currently in:
loadMessages: function(){
var room_id = App.getPath("current_room.id");
this.set("content", App.store.find(App.ChatMessage, {room_id: room_id});
}
This sets the content to a DS.AdapterPopulatedModelArray and my view happily displays all the returned chat messages in an {{#each}} block.
Now it comes to adding a new message, I have the following in the same controller:
postMessage: function(contents) {
var room_id = App.getPath("current_room.id");
App.store.createRecord(App.ChatMessage, {
contents: contents,
room_id: room_id
});
App.store.commit();
}
This initiates an ajax request to save the message on the server, all good so far, but it doesn't update the view. This pretty much makes sense as it's a filtered result and if I remove the room_id filter on App.store.find then it updates as expected.
Trying this.pushObject(message) with the message record returned from App.store.createRecord raises an error.
How do I manually add the item to the results? There doesn't seem to be a way as far as I can tell as both DS.AdapterPopulatedModelArray and DS.FilteredModelArray are immutable.
so couple of thoughts:
(reference: https://github.com/emberjs/data/issues/190)
how to listen for new records in the datastore
a normal Model.find()/findQuery() will return you an AdapterPopulatedModelArray, but that array will stand on its own... it wont know that anything new has been loaded into the database
a Model.find() with no params (or store.findAll()) will return you ALL records a FilteredModelArray, and ember-data will "register" it into a list, and any new records loaded into the database will be added to this array.
calling Model.filter(func) will give you back a FilteredModelArray, which is also registered with the store... and any new records in the store will cause ember-data to "updateModelArrays", meaning it will call your filter function with the new record, and if you return true, then it will stick it into your existing array.
SO WHAT I ENDED UP DOING: was immediately after creating the store, I call store.findAll(), which gives me back an array of all models for a type... and I attach that to the store... then anywhere else in the code, I can addArrayObservers to those lists.. something like:
App.MyModel = DS.Model.extend()
App.store = DS.Store.create()
App.store.allMyModels = App.store.findAll(App.MyModel)
//some other place in the app... a list controller perhaps
App.store.allMyModels.addArrayObserver({
arrayWillChange: function(arr, start, removeCount, addCount) {}
arrayDidChange: function(arr, start, removeCount, addCount) {}
})
how to push a model into one of those "immutable" arrays:
First to note: all Ember-Data Model instances (records) have a clientId property... which is a unique integer that identifies the model in the datastore cache whether or not it has a real server-id yet (example: right after doing a Model.createRecord).
so the AdapterPopulatedModelArray itself has a "content" property... which is an array of these clientId's... and when you iterate over the AdapterPopulatedModelArray, the iterator loops over these clientId's and hands you back the full model instances (records) that map to each clientId.
SO WHAT I HAVE DONE
(this doesn't mean it's "right"!) is to watch those findAll arrays, and push new clientId's into the content property of the AdapterPopulatedModelArray... SOMETHING LIKE:
arrayDidChange:function(arr, start, removeCount, addCount){
if (addCount == 0) {return;} //only care about adds right now... not removes...
arr.slice(start, start+addCount).forEach(function(item) {
//push clientId of this item into AdapterPopulatedModelArray content list
self.getPath('list.content').pushObject(item.get('clientId'));
});
}
what I can say is: "its working for me" :) will it break on the next ember-data update? totally possible
For those still struggling with this, you can get yourself a dynamic DS.FilteredArray instead of a static DS.AdapterPopulatedRecordArray by using the store.filter method. It takes 3 parameters: type, query and finally a filter callback.
loadMessages: function() {
var self = this,
room_id = App.getPath('current_room.id');
this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
})
// set content only after promise has resolved
.then(function (messages) {
self.set('content', messages);
});
}
You could also do this in the model hook without the extra clutter, because the model hook will accept a promise directly:
model: function() {
var self = this,
room_id = App.getPath("current_room.id");
return this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
});
}
My reading of the source (DS.Store.find) shows that what you'd actually be receiving in this instance is an AdapterPopulatedModelArray. A FilteredModelArray would auto-update as you create records. There are passing tests for this behaviour.
As of ember.data 1.13 store.filter was marked for removal, see the following ember blog post.
The feature was made available as a mixin. The GitHub page contains the following note
We recommend that you refactor away from using this addon. Below is a short guide for the three filter use scenarios and how to best refactor each.
Why? Simply put, it's far more performant (and not a memory leak) for you to manage filtering yourself via a specialized computed property tailored specifically for your needs