Defining record relationships

This brings us to the last type of attribute we will use: the relationship attribute. Records are often related to each other as one-to-one, one-to-many, or many-to-many and we can encode this hierarchy in our SproutCore data model as well. To indicate a relationship, we use one of either SC.SingleAttribute (that is, to one) or SC.ManyAttribute (that is, to many), which is done just like we did with SC.RecordAttribute by using SC.Record helper methods. In this case, the helpers are toOne and toMany to be exact.

For example, if you recall from our simple data model example of Calendar and Event records, we indicated a relationship between the two. This relationship will exist in the raw data on the server, such that Calendar records will contain an array of Event record IDs and Event records will contain a single Calendar ID. To add this relationship to our classes, we would update the records as follows:

MyApp.Calendar = SC.Record.extend({

  // This calendar's events.
  events: SC.Record.toMany('MyApp.Event')

});

MyApp.Event = SC.Record.extend({

  // This event's calendar.
  calendar: SC.Record.toOne('MyApp.Calendar')

});

Note

Notice that the attributes are of string type (that is, 'MyApp.Event'). We use strings so that we don't have to deal with the tricky problem of which record class is to be loaded first. When the attribute is used for the first time, the proper record class will actually be looked up.

Just like with the attr helper method, we can pass options to toOne and toMany. Since SC.SingleAttribute and SC.ManyAttribute inherit from SC.RecordAttribute, we can use the isEditable and key options just the same, but there are two other options specific to relationship attributes: inverse and isMaster.

The inverse option is the name of the property on the other side of the relationship, which, when set, will modify the other side of the relationship when this side changes. This sounds a bit tricky, but it's quite simple once you get it.

Let's look at what happens when we modify the relationship without the inverse option declared. To show this, I've mocked up a few sample records. There are two Calendar records with the guid 'calendar1' and 'calendar2' respectively and there are three Event records with the guid 'event1', 'event2', and 'event3' respectively. For this demo, all of the three events originally belong to 'calendar1'.

Defining record relationships

This simulation shows a couple of things we're not familiar with yet, but let me walk you through it. The basics of what I did are that I retrieved a calendar record as well as the first event related to that calendar. By using the toString method of each of these records, I was able to see that calendar1's events attribute includes 'event1' and event1's calendar attribute was 'calendar1'. But see what happened after I shifted the first record out of calendar1? While calendar1 now showed two events correctly, event1 still believed that it was related to calendar1. If we neglected to fix this up, we could have a serious data inconsistency problem later on.

Note

The READY_CLEAN and READY_DIRTY strings in the Console example explain the status of the records. We will look at the life cycle and all the possible status of records in the next portion.

But do we need to manually fix up both sides of a relationship? Why don't we add inverse options to our models and try again.

First, I will add the inverse to each of the relationship as shown in the following code:

MyApp.Calendar = SC.Record.extend({

  // This calendar's events.
  events: SC.Record.toMany('MyApp.Event', {
    inverse: 'calendar'  // The attribute name on the other side of the relationship.
  })

});

MyApp.Event = SC.Record.extend({

  // This event's calendar.
  calendar: SC.Record.toOne('MyApp.Calendar', {
    inverse: 'events'   // The attribute name on the other side of the relationship.
  })

});

Next, I will run the mock example again in the console as shown in the following screenshot:

Defining record relationships

Wonderful! Now, when we inspect the event1 record after altering the relationship on the calendar side, we see that its relationship has also been modified, which is exactly what we want. Please note that since we specified the inverse of calendar in MyApp.Event as well, the same rule would have applied had we changed event1's calendar attribute instead of calendar1's events attribute.

Now, we'll look at the isMaster option for relationship attributes. It's actually important to have a good understanding of the inverse option in hand because the isMaster option is directly related to it. To be clear, the first thing to remember is that isMaster is only used along with inverse. To describe what isMaster does, we have to skip ahead a bit and pay attention to how the status of the records changed from READY_CLEAN to READY_DIRTY when we modified the relationships in the previous examples.

All you need to know right now is that the READY_CLEAN state is the state of a record when it has no changes and the READY_DIRTY state is the state of a record that has changes. If a record is dirty, we expect to commit the changes back to a remote data source so that our data between the client and server is synchronized. However, it is likely that we only need to commit changes to one side of a relationship and the remote data source will understand that the other side has changed as well.

For example, if we moved event1 from calendar1 to calendar2, we would dirty three records, which could become three commits back to the server. But every API can recognize that if event1 has changed its relationship with calendar2, it also has to update calendar1 and calendar2. Therefore, we only need to dirty the Event record when the event to calendar relationship changes. This is what isMaster does for us in a record relationship. If we set isMaster to false, changes in the relationship will not dirty the record.

Note

The value of isMaster is true by default.

Here's an example to help make this clear. First, we indicate that Calendars are not the master of the relationship and need not be dirtied when the events relationship changes.

MyApp.Calendar = SC.Record.extend({

  events: SC.Record.toMany('MyApp.Event', {
    inverse: 'calendar',
    isMaster: false  // Changes to this relationship only dirty the event record
  })

});

Next, we go back to the Console to see it in action:

Defining record relationships

Can you spot the difference? This time once the relationship is altered, only the Event record is placed in a dirty state, which means only it will be committed back to the server.

Now, I realize that these examples have stepped well into the realm of the SC.Record life cycle and so, let's continue on that path and look at it all in the next section.

Tip

At some point, you will undoubtedly come across nested record relationships. These seem like the normal relationship attributes, but are actually quite different. Nested relationships transform embedded JavaScript objects in the data hash into SC.Record instances. This allows you to use the objects like any other SC.Object/SC.Record object, including binding to properties, adding computed properties, and so forth. However, up to Version 1.10, nested records have remained somewhat temperamental and it's best to avoid using them until such time that the code has been refactored. In the meantime, it is safer to stick to a normal JavaScript object as the attribute type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.158.165