How to efficiently specify nested relationships in ember-data? - ember.js

I have a nested model as such:
var School = DS.Model.extend({
classrooms: DS.hasMany('classroom', {async: true})
});
var Classroom = DS.Model.extend({
students: DS.hasMany('student', {async: true}),
school: DS.belongsTo('school', {async: true})
});
var Student = DS.Model.extend({
name: DS.attr('string'),
classroom: DS.belongsTo('classroom', {async: true})
});
I am using firebase as a backend, and I understand it is advisable to denormalize the schema for efficiency sake. Is there any utility in explicitly specifying the relationship
var Student = DS.Model.extend({
school: DS.belongsTo('school', {async: true});
});
for the Student model, even though this is implied by each Student belonging to a Classroom and each Classroom belonging to a School?

No. You shouldn't need to provide nested relationship information unless model in question is directly accessible from the root object (hope that makes sense).
The only "benefit" of doing so is that in your case the student model will be loaded when the school data is being loaded rather than waiting until the classroom data is loaded. However, this will provide a data structure that is not reflective of your intentions and so I wouldn't advocate doing this.

I would use a computed property or a binding to get what you're after - something like
school: Ember.computed.alias('classroom.school')
schoolBinding: 'classroom.school'
It wouldn't be a DS.belongsTo because the data wouldn't actually contain a school property

Related

EmberFire Data Relationships not updating correctly

UPDATE: Below is an original post regarding Ember Data Relationships that I believe are more related to a problem with the EmberFire adapter and how hasMany array records are handled. It seems the local store in Ember Data is updating appropriately, however these records are not being pushed into the appropriate Firebase database after the store is updated.
I've added the emberfire and firebase tags to hopefully find an answer in that community. Any help is appreciated and I'm happy to provide additional information as needed.
I have two models that are defined with hasMany and belongsTo relationships. Here is the first "frameworks" model:
// -- Frameworks Model
import DS from 'ember-data';
const { attr, hasMany } = DS;
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
questions: { serialize: 'ids' }
}
});
export default DS.Model.extend({
name: attr('string'),
reports: hasMany('report'),
questions: hasMany('question', {async: true}),
includedFramework: attr('string'),
reportOnlyOnce: attr('string', { defaultValue: false }),
frameBuilder: attr('string', {defaultValue: "none"}),
createdAt: attr('string'),
createdBy: attr('string'),
updatedAt: attr('string'),
updatedBy: attr('string')
});
And the second "question" model:
// -- Question Model
import DS from 'ember-data';
const { attr, hasMany, belongsTo } = DS;
export default DS.Model.extend({
framework: belongsTo('frameworks', {async: true}),
detail: attr('string'),
type: attr('string'),
createdAt: attr('string'),
createdBy: attr('string'),
position: attr('number'),
options: hasMany('option')
});
On my route, I pull in the model for the framework (in this case defining the model ID for testing):
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
let id = params.report_id;
var self = this;
return Ember.RSVP.hash({
report: this.store.find('report', id),
general: this.store.find('frameworks', '-KITSAxjdE5TiVdk-aPC')
})
}
});
For some reason, this framework has an array stored in questions that has 4 of the 6 different "questions" that should belong to it. I don't know why 4 of the stored in the framework model as well and 2 of them did not. In my template for the page:
{{#each model.general.questions as |question|}}
{{#paper-item class="md-1-line"}}
<p class="question">
{{question.detail}}
</p>
<p class="option">
{{#if (eq question.type "text")}}
{{paper-input value=question.id onChange=(action (mut question.id))}}
{{/if}}
</p>
{{/paper-item}}
{{#each model.general.questions as |question|}} works great for the 4 questions that are actually stored in the framework model for this ID, but not the other 2. The other 2 questions do have the belongsTo relationship referencing the correct framework.
I have tried adding a serializer in the framework model (as you can see above) but there was no change.
I have also tried manually adding the question to the framework store when a new question is created with:
framework.get('questions').then(function(question) {
return question.pushObject(newQuestion);
})
but no change with that either.
Any thoughts are welcome, I'm happy to provide any other relevant code snippets as well.
This issue seems to be related to the way EmberFire responds to models with a belongsTo and hasMany relationships where the "parent" model with a hasMany reference to the child models does not update automatically when a child model is created.
An issue has been submitted on GitHub to continue investigating how to resolve this issue: https://github.com/firebase/emberfire/issues/412

Ember data: reuse one model for two guises

In the app user has two associations - billing address and company address which are identical
//address
export default DS.Model.extend({
email: DS.attr('string'),
country: DS.attr('string'),
zip: DS.attr('string'),
user: DS.belongsTo('user')
//...
});
//user
export default DS.Model.extend({
name: DS.attr('string'),
company: DS.belongsTo('address'),
billing: DS.belongsTo('address')
//...
});
How to pull this kind of trick?
What I would do is use a Polymorphic relationship. The steps would be (I'm assuming ember-cli, if not the gist is the same):
ember generate model address
ember generate model company-address
ember generate model billing-address
In models/address.js you define the actual address model:
export default DS.Model.extend({
email: DS.attr('string'),
country: DS.attr('string'),
zip: DS.attr('string'),
user: DS.belongsTo('user')
//...
});
Then in the other models you extend from the parent model (polymorphism saves the day!).
company-address
import Address from './address';
export default Address.extend({
// Yeah, its empty.
});
billing-address
import Address from './address';
export default Address.extend({
// But, you could add custom fields here and
// still share the ones with the parent
// Polymorphism.
});
Now in your user model you do this:
export default DS.Model.extend({
name: DS.attr('string'),
company: DS.belongsTo('address', {polymorphic: true}),
billing: DS.belongsTo('address', {polymorphic: true})
});
There is no free lunch though, your JSON has to change its format now:
{ name: 'foobar',
company: {
// company fields or id if async
type: 'company-address'
},
billing: {
// billing fields or id if async
type: 'billing-address'
}
}
You are looking for the inverse options.
You need two fields on the Address model, one referring to the company with this address, one referring to the user with this address. Then, specify which is which with the inverse option.
//address
export default DS.Model.extend({
...
billing: DS.belongsTo('user', { inverse: 'billing' })
company: DS.belongsTo('user', { inverse: 'company' })
});
//user
export default DS.Model.extend({
...
company: DS.belongsTo('address'),
billing: DS.belongsTo('address')
});
See http://emberjs.com/guides/models/defining-models#toc_explicit-inverses.
For what you are trying to do, there is no reason whatsoever to define two separate derived models simply in order to get the associations set up properly. inverse does exactly what you need.

Multiple relationship between two objects

How do I define multiple relationships between two objects in Ember? For example A user can be either a staff or a customer the differentiator is the userType property. If a customer buys a product, the product object need to have link to the customer that bought it and the staff that facilitated the sale.
Here is a basic version of a data model that would suffice for what you have described. You can adapt it for your needs. I have used the app global haven't specified whether you are using ember-cli.
App.User = DS.Model.extend({
name: DS.attr('string'),
userType: DS.attr('string')
});
App.Order = DS.Model.extend({
orderedBy: DS.belongsTo('user'),
facilitatedBy: DS.belongsTo('user')
});
App.Product = DS.Model.extend({
name: DS.attr('string')
});
A useful tool for creating a first pass of a data model when you are unsure of the ember syntax is Ember Data Model Maker. You can use it to see how you should set up your model definitions and then modify them later.

Ember-data lazy load association with "links" attribute

I have a model Teacher which has many Students. The models are defined as follows:
App.Teacher = DS.Model.extend({
email: DS.attr('string'),
students: DS.hasMany('student')
});
App.Student = DS.Model.extend({
teacher: DS.belongsTo('teacher'),
});
When a Teacher logs in, the server returns a JSON representation of the Teacher:
{
id: 1,
email: "abc#example.com",
links: {
students: /teacher/1/students
}
}
In the controller for login, I then push this data into the store and store it in a property of the Session controller:
this.set('currentUser', this.get('store').push('teacher', teacherJson))
I want to lazy-load the students association so I used the "links" format as defined in the API (http://emberjs.com/api/data/classes/DS.Store.html#method_push). So, ideally, whenever I would call
App.SessionController.get('currentUser').get('students')
it would load the associated students by sending a GET request to /teacher/1/students. But that never happens. Why is the request not triggered?
Ok, I found the answer. I had to add a property async: true to the students association in the model for Teacher:
App.Teacher = DS.Model.extend({
email: DS.attr('string'),
students: DS.hasMany('student', { async: true })
});

Ember Data 1.0.0: Model name capitalized in URL

I am transitioning from Ember Data 0.13 to Ember Data 1.0 (beta 1). It seems that the URL constructed for a model is capitalized when it shouldn't. In ED 0.13, capitalization and pluralization occurred automatically and without problems. I suppose the same is still true in ED 1.0, but I must be overlooking something.
App.Account = DS.Model.extend({
// Attributes
company: DS.attr('string'),
// Relationships
users: DS.hasMany('User')
});
App.AccountAdapter = DS.RESTAdapter.extend({
namespace: 'api',
});
In the controller, I create a new record, populate it, and save it.
var account = this.store.createRecord('account');
account.set('company', this.get('company'));
account.save();
The request URL that Ember Data uses for saving the record is http://localhost:3000/api/Accounts. Why is the plural of the model name capitalized? How do I configure the model/adapter to use accounts instead of Accounts?
It appears that a change in the naming convention of model associations is the cause of this issue. Before ED 1.0, an association was declared as shown below.
App.Account = DS.Model.extend({
// Attributes
company: DS.attr('string'),
// Relationships
users: DS.hasMany('App.User')
});
In ED 1.0, however, there is no need to use App.User to declare an association. Passing the model's name, user suffices. Because I capitalized the name of the Account model in the User model, ED capitalized the name (plural) of the model in the URL.
App.User = DS.Model.extend({
// Attributes
first_name: DS.attr('string'),
last_name: DS.attr('string'),
email: DS.attr('string'),
password: DS.attr('string'),
password_confirmation: DS.attr('string'),
// Relationships
account: DS.belongsTo('account') // model name should be lowercase
});
Just like you need to pass the lowercase name of the model for creating a new record (this.store.createRecord('user');), you also need to use the lowercase model name to specify the model for an association.