Giter Club home page Giter Club logo

sails101-associations-overview's Introduction

Introduction to Model Associations

##Overview of the project

I want to create a way of retrieving, storing, and manipulating information in an application I'm going to build called -- sailsPeople. Initially, the application will have Users (e.g. people that will use the application to track their sales activity), Profiles (e.g. information realating to and describing a User), Leads (e.g.) prospective people user's want to sell products are services, and SailsTeams (e.g. groups of users that work together).

I need some way of organizing Users, Profiles, Leads, and SailsTeams in my application and each of these is usually referred to as a Data Model.

Getting ready for the project

Create a sails project using:

sails new sailsPeople

Sails creates the project using the following folder structure:

sailsPeople
       |_api
       |_assets
       |_config
       |_node_modules
       |_tasks
       |_views                
.gitignore
.sailsrc
app.js
Gruntfile.js
package.json
README.md                

##What are data models? The term model is one of those over-loaded words that can cause a bunch of confusion. Therefore, I want to provide some context for how I'm using the term model in this project. For my purposes, a model is a representation of a set of structured data, usually corresponding with a table in a SQL database or a collection in databases like MongoDB, Redis, or Riak.

This model can be considered a back-end model because it resides on the server, although it can be accessed via any Client that can make a request via http and/or sockets. For context, models fit here in the overall Sails framework.

###When do I use models? Models are useful any time I want to store, retrieve, update, and/or delete data in my app. For instance, my app might need to save User data entered into a web form. The data will passed into to a model and ultimately stored on the server. The distinction between the model and the underlying place it's stored is important because with Sails once I define the model I have a common set of methods to interact with the data. Therefore, regardless of whether I'm using an SQL database (e.g. mySQL, PostgreSQL), a schema-less database (e.g. MongoDB, Redis, or Riak) or combination of databases, Sails removes the need to address each of the database's unique api.

I can simply configure my models, choose an Adapter via a Connection, and rely upon Model methods to access and manipulate my data across various databases.

Defining models

Task: Configure a model to hold my User data.

A model is defined by group of attributes that describe a particular thing or entity.

For example, in this project I want to create a User model that consists of a single attribute: name. The model will ultimately consist of other attributes (e.g. email address, title, phone number, etc), however, for the purpose of understanding how models work and interact I'll limit the attributes to simply the user's name.

The Sails command-line tool ships with a few simple generators for quickly generating common code files. This keeps me from having to manually create new files and put together the boilerplate code every time I want to add something new to my app.

So to define my first model named User, I'll open the command line and enter:

 $ sails generate api user

This will create the model in a file named User.js in the sailsPeople/api/models/ folder and a controller in a file named UserController.js in the sailsPeople/api/controllers/ folder.

When I open sailsPeople/api/models/User.js, an empty model has been created for me. My user model is going to consist of a single attribute name which I'll add now.

module.exports = {

  attributes: {
    
    name: {
      type: 'string'
    }
  }
};

When I start my app using sails lift, Sails will look for models in /sailsPeople/api/models. Finding my newly created User.js file, Sails will connect to the database where my User data is stored and provide an inteface for me to interact with it. By default, Sails stores data in a temporary file on my hard disk, but I can change this to MySQL, PostgreSQL, MongoDB, Redis, or CouchDB at any time down the road.

I'm going to do something similar with my second model called Profile. In my app, a Profile contains attributes that describe a particular User.

To define my Profile model, I'll open the command line and enter:

$ sails generate api profile

Again, a model and controller file were generated.

For now, I just want to track two pieces of information for my Profile -- aboutMe which not surprisingly will be a description of a User and gender. I'll open Profile.js in the /sailsPeople/api/models folder and add the two attributes:

module.exports = {

  attributes: {
    
    aboutMe: {
      type: 'string'
    },

    gender: {
      type: 'string'
    }
  }
};

At this point, I have defined and configured two models:

Models, Records, and Collections

Throughout this project I'm going to differentiate between a model, a record and a collection.

A model is a definition of a thing (e.g. User.js):

attributes: {
  name: {
    type: 'string'
  }
}

An record is one of those things:

{
"name": "Nikola Tesla",
"createdAt": "2014-04-14T17:18:49.439Z",
"updatedAt": "2014-04-14T17:18:49.439Z",
"id": 1
}

A collection is a group of records:

[  {
 "name": "Nikola Tesla",
 "createdAt": "2014-04-14T17:18:49.439Z",
 "updatedAt": "2014-04-14T17:18:49.439Z",
 "id": 1
},
{
 "name": "Neal Stephenson",
 "createdAt": "2014-04-14T20:47:01.703Z",
 "updatedAt": "2014-04-14T20:47:01.703Z",
 "id": 2
}  ]

Don't confuse my use of the word collection with a collection in MongoDB or a Backbone collection, although they share some similar qualities they are not the same thing.

So with my User model, I can create, update, find and destroy a new Record in any number of different places in Sails:

Although, I'll use a few different methods from the list above to work with data throughout this project, I'm concentrating on Associations and not Model methods. For more information on Model methods and how to use them, see the reference section under Models.

I need to create some Users and Profiles so I'll start with using some of the blueprint routes. If you'd like a more detailed explanation of blueprint routing, particularly the shortcut blueprint routes below, check out: How do blueprint routes work in sails?

Records: creating some Users and Profiles

Task: Create a couple of Users and what will be their associated Profiles.

I'll lift my app using sails lift.

I'll create my first User by opening my browser and entering the following URL: http://localhost:1337/user/create?name=Nikola Tesla

####How does this work?

I'm using the blueprint api through the shortcut blueprint routes which allow me to access blueprint CRUD model methods from the browser. These *routes are extremely helpful during development, however, can and should be disabled during production.

  • My browser is sending a GET request to my running Sails app at localhost:1337
  • That request matches up to a shortcut blueprint route, /user/create
  • The shortcut blueprint triggers an action, create() defined in api/controllers/UserController.js.
  • Note: I haven't defined a custom create action, so the implicit blueprint action will be run in its place.
  • This inspects the parameters I passed in (?name=Nikola Tesla) and passes them to User.create().
  • This creates a Record using my User model for a new user named "Nikola Tesla".

After creating the user Nikola Tesla, Sails returns the following json:

{
name: "Nikola Tesla",
createdAt: "2014-04-14T17:18:49.439Z",
updatedAt: "2014-04-14T17:18:49.439Z",
id: 1
}

Note:

By default, Sails adds id, createdAt, and updatedAt attributes without me having to explicitly define them in the model. This behavior is configurable.

Next, I'm going to create an additional User with the name Neal Stephenson. I'll visit exactly the same URL as before, but change out the name parameter: http://localhost:1337/user/create?name=Neal Stephenson

So now I have two Records of my User model:

Instances of User Model
id name createdAt updatedAt
1 'Nikola Tesla' '2014-04-14T17:18:49.439Z' '2014-04-14T17:18:49.439Z'
2 'Neal Stephenson' '2014-04-14T20:47:01.703Z' '2014-04-14T20:47:01.703Z'

Next I'll create two Profiles. The aboutMe is going to be large so instead of putting a bunch of text in the URL of the browser, I'm going to use a chrome extension called Postman. I highly recommend it as it makes sending requests to RESTful apis really easy. From within Postman I sent a post request to http://localhost:1337/profile with two parameters -- aboutMe and gender with the following values below:

Instances of Profile Model
id aboutMe gender createdAt updatedAt
1 I was born on July 10 1856 in Serbia. I'm an inventor, electrical engineer, mechanical engineer, physicist, and futurist best known for my contributions to the design of the modern alternating current (AC) electricity supply system. male '2014-04-14T20:47:25.571Z' '2014-04-14T20:47:25.571Z'
2 I'm an American author and game designer known for my work in speculative fiction. My novels have been variously categorized as science fiction, historical fiction, cyberpunk, and 'postcyberpunk. male '2014-04-14T20:47:25.571Z' '2014-04-14T20:47:25.571Z'

Records: finding Users and Profiles

Task: Get a list of User and Profile Records

So now that I have some Users and Profiles, let's talk about how to retrieve them.

Again, using the blueprint API, I can access my Users and Profiles without writing any code. I simply open up my browser and visit: http://localhost:1337/user (remember: this means my browser is sending a GET request to Sails at the /user path)

Sails returns the following json:

[  {
  "name": "Nikola Tesla",
  "createdAt": "2014-04-14T17:18:49.439Z",
  "updatedAt": "2014-04-14T17:18:49.439Z",
  "id": 1
},
{
  "name": "Neal Stephenson",
  "createdAt": "2014-04-14T20:47:01.703Z",
  "updatedAt": "2014-04-14T20:47:01.703Z",
  "id": 2
}  ]

Accessing stored Profiles from the browser is just as easy via http://localhost:1337/profile. As before, Sails returns the following json:

[{
  "aboutMe": "I was born on July 10 1856 in Serbia. I'm an inventor, electrical engineer, mechanical engineer, physicist, and futurist best known for my contributions to the design of the modern alternating current (AC) electricity supply system.",
  "gender": "male",
  "id": 1,
  "createdAt": "2014-04-24T22:26:54.000Z",
  "updatedAt": "2014-04-24T22:26:54.000Z"
},
{
  "aboutMe": "I'm an American author and game designer known for my work in speculative fiction. My novels have been variously categorized as science fiction, historical fiction, cyberpunk, and 'postcyberpunk.'",
  "gender": "male",
  "id": 2,
  "createdAt": "2014-04-24T22:26:54.000Z",
  "updatedAt": "2014-04-24T22:26:54.000Z"
}]

With our models defined, attributes configured, and some records created, it's time to address associations.

##Why do I use Associations? Associations provide a way to relate models so that finding, creating, updating, and destroying Records requires less programming. Associations require some initial configuration, however, once configured, allow me to find and change records easily.

##One-Way Associations: Configuring, Finding, Creating, Updating and Removing Associations

###Configuring the Model: a one-way association

Task: Configure an association where each User can be associated with one Profile

Let's say I want to be able to find a particular User, and also the Profile she is associated with. I could manage the relationship manually: i.e. find the User, find the Profile, then format the result. However, it would be a lot nicer to look up both a User and its associated Profile in one action.

To accomplish this, I'll configure the User model with a new attribute called Profile-- but this time, instead of specifying a primitive type like "string" or "integer", I'll configure the Profile attribute as an association. In addition to the association attribute, I can configure its association type (e.g. a model, representing one associated record or a collection, representing one to multiple records), and the association model (e.g. the model where the associated Records will come from).

>**Note:** It can be confusing that the _association type_, model or collection, uses the word model. Is this use of the word _model_ associated with say, the _User_ model? **No.** The _association type_ is simply letting Sails know whether the association is looking for no more than one _Record_ in the case of _model_ or in the case of _collections_ one or more _Records_.

The Profile association attribute represents a one-way relationship, with a single Record, between the User model and the Profile model. I could name this attribute 'foo', however, since it relates to the Profile model I'm going to stay consistent and name the association attribute -- Profile. What this means is that I can now associate the user, Nikola Tesla, with his Profile. More importantly, based upon this association I can access and manipulate Nikola Tesla's Profile at the same time I'm accessing his User Record.

Sails abstracts away a lot of the complexity of associations for me, and it supports both "schema-ful" and "schema-less" databases. The underlying implementation of associations is slightly different in each case, but since it can be helpful to examine what's going on under the covers, I'll provide a quick overview using terminology from the classic relational model.

Each Record contains a Primary Key which is an attribute whose value is guaranteed to be unique between Records. For example, the default Primary Key in MongoDB would look something like _id: ObjectId("52752fd8a61584b30c000001") versus PostgreSQL which would look something like id: 1.

In this project, I have a Record -- Nikola Tesla with an id: of 1. The id: is the Primary Key and this primary key becomes very useful when I want to associate Nikola Tesla with his Profile.

It really helps to see this change before and after the association is made.

Before I make the association, Nikola Tesla and his Profile exist independently of each other. However, a relationship will be created when I update Nikola's Profile association attribute with the Primary Key of his Profile Record.

Updating a Record's association: a one-way association

Task: Create an association between Nikola Tesla and his Profile.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.update({id: 1}).set({profile: 1}).exec(function(err, user){console.log(user); });

Note: I could have written this command without the .set() method with the same effect:

sails> User.update({id: 1}, {profile: 1}).exec(function(err, user){console.log(user); });

That's hard to read so let's look at the same code formatted in a more readable style:

User.update({id: 1})
  .set({profile: 1})
  .exec(function(err, user){
    console.log(user);
  });

####How does this work?

  1. I pass the .update method, the primary key of the user I want to update, in this case id: 1 (better known as Nikola Tesla).
  2. Next, I set the Profile association attribute (profile: ) to the primary key of Nikola's Profile (1).

So now that I've placed the primary key as the value of the Profile association attribute in the Nikola's Record, sails can deduce that the profile is associated with Nikola Tesla.

As you might expect, the Profile attribute in each user Record is now capable of referring to a particular instance of the Profile model. It's important to point out that the Profile attribute doesn't necessarily have to be set-- a user might not have a Profile! Equally important is that at this point a profile Record can belong to more than one User.

So now that I've created the association, what can I do with it?

Finding associated Records: using "populate" in a one-way association

Task: Find Nikola Tesla's profile using the .populate() method.

There are several methods I'll be using to populate (i.e. look up), add, and remove associated Records. Here is a general description of each method:

Association Methods
method description
`user.populate()` This method will return all of the Records associated with association attribute. For example, in a callback from the .find() method where the user is Nikola Tesla, user.populate('profile') will return Nikola Tesla's Profile Record.
`user.profile.add()` This method will make the necessary association of one Record to another Record. For example in a callback from the .find() method where the user is Nikola Tesla, user.profile.add(1), will add a Profile Record with the primary id of 1 to the association attribute of Nikola Tesla. Note: I will use the user.save() method to save the changes I made.
`user.profile.remove()` This method will remove the necessary association of one Record to another Record. For example in a callback from the .find() method where the user is Nikola Tesla, user.profile.remove(1), will remove a Profile Record with the primary id of 1 to the association attribute of Nikola Tesla. Note: I will use the user.save() method to save the changes I made.

The first of these methods is .populate which I can use to have sails return all Records of an association.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.findOne(1).populate('profile').exec(function(err,user){ console.log(user); });

That's hard to read so let's look at the same code formatted in a more readable style:

User.findOne(1)
  .populate('profile')
  .exec(function(err,user){
    console.log(user); 
  });

####How does this work?

  1. I pass the .findOne method, the primary key of the user I want to find, in this case id: 1 (better known as Nikola Tesa).
  2. I then chain .populate to look up the profile association attribute.
  3. Sails returns the following json:
{ 
  profile: 
   { aboutMe: 'I was born on July 10 1856 in Serbia.  I\'m an inventor, electrical engineer, mechanical engineer, physicist, and futurist best known for my contributions to the design of the modern alternating current (AC) electricity supply system.',
     gender: 'male',
     id: 1,
     createdAt: Thu Apr 24 2014 17:26:54 GMT-0500 (CDT),
     updatedAt: Thu Apr 24 2014 17:26:54 GMT-0500 (CDT),
     owner: null },
  name: 'Nikola Tesla',
  id: 1,
  createdAt: Thu Apr 24 2014 17:26:54 GMT-0500 (CDT),
  updatedAt: Fri Apr 25 2014 18:39:25 GMT-0500 (CDT) 
}

###Creating a new Profile and associating it with an existing User

Task: Create a new Profile for Neal Stephenson.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> Profile.create({aboutMe: "I'm an American author and game designer known for my work in speculative fiction. My novels have been variously categorized as science fiction, historical fiction, cyberpunk, and 'postcyberpunk.", gender: "male"}).exec(function(err, profile) { User.findOne(2).exec(function(err, user) {user.profile = profile.id; user.save(function(err){}); }); });

Here is the same code but in a more readable format:

Profile.create({aboutMe: "I'm an American author and game designer known for my work in speculative fiction. My novels have been variously categorized as science fiction, historical fiction, cyberpunk, and 'postcyberpunk.", 
  gender: "male"})
  .exec(function(err, profile) { 
    User.findOne(2).exec(function(err, user) {
      user.profile = profile.id; 
      user.save(function(err){}); 
    }); 
  });

####How does this work?

  1. First I create the new Profile using the .create method and pass in two attributes aboutMe and gender.
  2. Next, I find the User Neal Stephenson who I want to assocate with the new Profile via the .findOne method and passing in Neal Stephenson's Primary Key of 1.
  3. Next, I asign the Profile attribute of Neal Stepheson with the Profile id: returned from step one using user.profile = profile.id;
  4. Finally, I save the model instance of Neal Stephenson by using the .save method.

Creating a new User and associating it with an existing Profile

Task: Create a new user Aimee Mann and associate it with her Profile.

I could use the same strategy I used in the previous example, however, because the User model is aware of the Profile model through its association, I can also use an additional, less verbose, approach.

First, I'll set-up the existing Profile Record will use for this example:

I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> Profile.create(aboutMe: "I'm an American rock singer-songwriter, guitarist and bassist. In the 1980s, I sang in the Boston new wave band 'Til Tuesday until I left to begin a solo career in the early 1990s.", gender: "female"}).exec(function(err, profile){console.log(profile); });

Here is the same code but in a more readable format:

Profile.create(aboutMe: "I'm an American rock singer-songwriter, guitarist and bassist. In the 1980s, I sang in the Boston new wave band 'Til Tuesday until I left to begin a solo career in the early 1990s.", gender: "female"})
  .exec(function(err, profile) {
    console.log(profile); 
  });

Next, I'll add Aimee Mann and associate it with her Profile.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.create({name: 'Aimee Mann', profile: 3}).exec(function(err, user) { console.log(user); });

Here is the same code but in a more readable format:

User.create({name: 'Aimee Mann', profile: 3})
  .exec(function(err, user) {
    console.log(user);
  });

####How does this work?

I create the new User using the .create method and pass in Aimee Mann's name along with the primary key of the Profile I want to associate with Aimee (e.g. profile: 3).

###Creating a new User and new Profile and associating it with the new User

Task: Create a new User Orville Wright and his Profile adding the association to his Profile.

I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> Profile.create({aboutMe: "Along with my brother Wilbur, we created the first self-propelled heavier than air craft which initially flew on December 17, 1903.", gender: "male"}).exec(function(err, profile) { User.create({name: "Orville Wright"}).exec(function(err, user){ console.log("user.profile: ", user.profile);  console.log("profile.id: ", profile.id); user.profile = profile.id;  user.save(function(err){ if (err) {console.log(err); }});  });  });

Here is the same code but in a more readable format:

Profile.create({
    aboutMe: "Along with my brother Wilbur, we created the first self-propelled heavier than air craft which initially flew on December 17, 1903.",
    gender: "male"
}).exec(function (err, profile) {
    User.create({
        name: "Orville Wright"
    }).exec(function (err, user) {
        console.log("user.profile: ", user.profile);
        console.log("profile.id: ", profile.id);
        user.profile = profile.id;
        user.save(function (err) {
            if (err) {
                console.log(err);
            }
        });
    });
});

I could also have written this from the User model perspective:

sails> User.create({name: "Orville Wright", profile: {aboutMe: "Along with my brother Wilbur, we created the first self-propelled heavier than air craft which initially flew on December 17, 1903.", gender: "male" } }).exec(function(err, user) { console.log(user) });

Here is the same code but in a more readable format:

User.create({
    name: "Orville Wright",
    profile: {
        aboutMe: "Along with my brother Wilbur, we created the first self-propelled heavier than air craft which initially flew on December 17, 1903.",
        gender: "male"
    }
}).exec(function (err, user) {
    console.log(user)
});

Removing the association between a User and a Profile: a one-way associations

Task: Remove the association between Neal Stephenson and his Profile.

I can remove Neal Stephenson's association with his Profile by assigning null to Neal's Profile attribute.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.update(2).set({profile: null}).exec(console.log);

####How does this work?

  1. First I pass the primary key of Neal Stephenson (2) to the .update() method.
  2. Next, I'll set the Profile association attribute to null.

##Two-way Associations: Configuring, Finding, Creating, Updating and Removing

So far, I've been using a one-way association between the User and Profile models. Nikola Tesla is aware of his Profile record, however, his Profile is unaware of Nikola Tesla. This is easy to fix. I'll configure another one-way association in the opposite direction, this time between the Profile and User models.

Since I already explained how to use a one-way association, aren't two-way associations the same thing? Well, yes and no. Yes, in that the configuration of the Model is very similar, however, the way I use the create, update, and remove methods are different and worthy of some examples.

###Configuring the Model: a two-way association

Task: Create a new association between the Profile model and the User model.

Similar to how I configured the User model, I can configure the Profile model with a new association attribute called Owner. The User association attribute represents a one-way relationship between the Profile model and the User model. That is, I can make a Profile record "aware" of of a User record simply by updating this new Owner attribute.

Updating and finding records in a two-way association

Finding associated Records: using "populate" in a two-way association

Task: Find the Profile record of Neal Stephenson using the Profile association attribute and find the Neal Stephenson using the Owner association attribute.

Not surprisingly finding a User or Profile record is identical to the one-way association. Where things get interesting is in Creating, Updating, and Deleting Associations.

Create a new User and Profile Record while adding associations at the same time

Task: Create a new User, Boris Karloff, and his Profile and associate each of them.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.create({name: 'Boris Karloff', profile: {aboutMe: 'I am an actor born in 1887 best known for my portrayal of Frankenstein's monster and the voice of the animated classic 'The Grinch that stole Christimas', gender: 'male'}}).exec(function(err, user) {Profile.update(user.profile).set({owner: user.id}).exec(console.log); });

Here is the same code but in a more readable format:

User.create({name: 'Boris Karloff', profile: {aboutMe: 'I am an actor born in 1887 best known for my portrayal of Frankenstein's monster and the voice of the animated classic 'The Grinch that stole Christimas." , gender: 'male'}})
  .exec(function(err, user) {
    Profile.update(user.profile)
      .set({owner: user.id})
        .exec(console.log); 
  });

####How does this work?

  1. First I create the user Boris Karloff by passing the name of the new User I'm creating.
  2. I also pass in Boris Karloff's association attribute --profile as an object that contains the attributes of the Profile -- aboutMe and gender.
  3. Next, I update Boris's Profile in the callback using user.profile as the primary id of the Profile I want to update.
  4. Finally, I set the Profile's association attribute -- owner and pass in Boris Karloff's primary key using user.id.

###Updating a Record's association: a two-way association.

Task: Associate Charles Ponzi's with his Profile.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.update(5, { profile: { id: 5, owner: 5 } }).exec(console.log)

####How does this work?

  1. First I'll pass in Charles Ponzi's primary key (5) to the .update() method.
  2. Next, I'll pass in Ponzi's association attribute -- profile as a nested object containing the primary key of Ponzi's Profile (id: 5) as well as the primary key of Ponzi himself (owner: 5).

Removing the association between a User and a Profile: a two-way association

Task: Removing Charles Ponzi's user record with his Profile.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.update({id: 5}, {profile: {id: 5, owner: null}}).exec(function(err, user) { user[0].profile = null; user[0].save(); });

Here is the same code but in a more readable format:

User.update({id: 5}, {profile: {id: 5, owner: null}})
  .exec(function(err, user) { 
    user[0].profile = null; 
    user[0].save(); 
  });

####How does this work?

  1. First I'll pass in Charles Ponzi's primary key (5) to the .update() method.
  2. Next, I'll pass in Ponzi's association attribute --profile as a nested object containing the primary key of Ponzi's Profile (id: 5) as well as assigning the owner association attribute to null.

Note: It is very important that when passing a nested object in the .update method that I specify a primary key of what I want to update. If I don't pass the primary key, the .update method will create a new object.

###Validating Two-way Associations Even though I've set-up a two-way association between the User and Profile as well as the Profile and User, there is nothing that will prevent Charles Ponzi's Profile record from being associated with both Charles Ponzi and Nikola Tesla. Given Ponzi's past, I don't think Nikola Tesla would appreciate that. But as with most things in Sails there's an easy way to prevent that behavior.

Because Associations are attributes Sails validations apply.

###Unqiue Validation: Enforcing a one-to-one association Task: Prevent more than one User record being associated with a particular Profile.

In order to prevent more than one User being associated with a particular Profile, I'll make a slight configuration change in my User and Profile models.

By adding the unique validation attribute, only one Profile record may be associated with a particular User record. If I attempt to associate a Profile record to more than one User record, I receive the following error:

{
  error: "E_UNKNOWN",
  summary: "Encountered an unexpected error",
  status: 500,
  raw: "error: duplicate key value violates unique constraint "user_profile_key""
}

##One to Many Associations: Configuring, Finding, Creating, Updating and Removing

Up to this point, the associations I've used have been limited to single Records. For example, Nikola Tesla can have only one Profile. But what if I need to associate more than one Record with Nikola Tesla? In this section I'm going to explore associating multiple Records together. The first of these associations is a one-to-many relationship like:

  • a band has one to many members
  • a mother can have one to many children
  • a person can manage one to many projects

For my sailsPeople project I'm going to add a new model called Lead. Each User can have many Leads. Setting up this association is simple.

###Configuring the Model: a one-to-many association

Task: Create a model called Lead with a name attribute. Configure a Leads association attribute for the User model.

To define my Lead model, I'll open the command line and enter:

$ sails generate api lead

When I open sailsPeople/api/models/Lead.js, an empty model has been created for me. For now, I just want to track the name of the lead so I'll add the attribute name.

attributes: {
  name: 'string'
}

I'll open up the User model located in the sailsPeople/api/models/User.js file and add a new association attribute called Leads-- to represent a particular user Record's Leads. This time, however, instead of specifying an association type of model, I'll use a association type of collection.

Note: A collection is different than a model association type in that a collection can have one or more Records.

The User model should now look like this:

attributes: {
  name: {
    type: 'string'
  },
  profile: {
    model: 'profile'
    unique: true
  },
  leads: {
    collection: ‘lead’
  }  
}

Once again sails abstracts the complexity away from me, however, I find it helpful to dig a bit deeper to see what's going on behind the scenes. My sailsPeople project now has four models -- User, Profile and Lead. But wait, what's the fourth model? First, since we're not going to be discussing the Profile model in this section I'll concentrate on the two models that are going to share the association -- User and Lead. The fourth model is something called a Join or Junction model. This model will be the bridge between the User and the Lead and will contain an array of primary keys of each associated Record.

###Creating a new Lead and adding an association with an existing User

Task: Create a Lead with the name George Jetson and associate him with Nikola Tesla.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.findOne(1).exec(function(err,user){ user.leads.add({name: 'George Jetson'}); user.save(); });

Here is the same code but in a more readable format:

User.findOne(1)
  .exec(function(err,user){ 
    user.leads.add({name: 'George Jetson'}); 
    user.save();
  });

####How does this work?

  1. First I find Nikola Tesla by passing in his primary key (1) into the .findOne() method.
  2. Next, using dot notation (user.leads) I add the lead George Jetson by passing in an object {name: 'George Jetson'}as an argument to the .add() method. This has the effect of creating the Lead and associating the primary key of George Jetson with Nikola Tesla's leads association attribute.
  3. Finally, I save Nikola Tesla using the .save() method.

Note: Using the method just explained, I've created additional Leads -- Hero Protagonist, Guy Montag, Mustapha Mond, and Winston Smith, now associated with Nikola Tesla.

###Finding associated Records: using "populate" in a one-to-many association

Task: Find all of the Lead records associated with Nikola Tesla.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.findOne(1).populate("leads").exec(console.log);

####How does this work?

  1. First I pass in Nikola Tesla's primary key (1) to the .findOne() method.
  2. I then chain .populate() passing in the leads association attribute.

Sails returns the following json object:

 [ { leads: 
     [ { name: 'Geore Jetson',
         id: 1,
         createdAt: Thu May 01 2014 14:32:23 GMT-0500 (CDT),
         updatedAt: Thu May 01 2014 14:32:23 GMT-0500 (CDT) } ],
       { name: 'Hero Protagonist',
         id: 2,
         createdAt: Thu May 01 2014 14:23:10 GMT-0500 (CDT),
         updatedAt: Thu May 01 2014 14:23:10 GMT-0500 (CDT) },
       { name: 'Guy Montag',
         id: 3,
         createdAt: Thu May 01 2014 14:23:21 GMT-0500 (CDT),
         updatedAt: Thu May 01 2014 14:23:21 GMT-0500 (CDT) },
       { name: 'Mustapha Mond',
         id: 4,
         createdAt: Thu May 01 2014 14:23:32 GMT-0500 (CDT),
         updatedAt: Thu May 01 2014 14:23:32 GMT-0500 (CDT) },
       { name: 'Winston Smith',
         id: 5,
         createdAt: Thu May 01 2014 14:23:36 GMT-0500 (CDT),
         updatedAt: Thu May 01 2014 14:23:36 GMT-0500 (CDT) },
           profile: null,
    name: 'Nikola Tesla',
    createdAt: Thu May 01 2014 13:48:02 GMT-0500 (CDT),
    updatedAt: Thu May 01 2014 14:32:23 GMT-0500 (CDT),
    id: 1 } ]

###Removing the association between a User and a Lead: one-to-many associations

Task: Remove the association between Nikola Tesla and Guy Montag.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.findOne(1).exec(function(err, user) { user.leads.remove(3); user.save(); });

Here is the same code but in a more readable format:

User.findOne(1)
  .exec(function(err, user) { 
    user.leads.remove(3); 
    user.save(); 
  });

####How does this work?

  1. First I find Nikola Tesla by passing in his primary key (1) into the .findOne() method.
  2. Next, using dot notation (user.leads) I'll remove Guy Montag as a lead of Nikola Tesla by passing in Guy's primary key (3) as an argument to the .remove() method.
  3. Finally, I'll save the User -- Nikola Tesla using the .save() method.

##Many to Many Associations: Configuring, Finding, Creating, Updating and Removing

In addition to associating one Record to many other Records, sails also provides for many-to-many relationships like:

  • a student takes many classes; a class has many students.
  • an order can have many items -- an item can be in many orders
  • a doctor, sees many patients; a patient sees many doctors.

For my sailsPeople project I'm going to add a new model called sailsTeam. Each User can have many sailsTeams and each sailsTeam can have many Users.

###Configuring the Model: a many-to-many association

Task: Configure the User model so that each user record can be assocated with one or more sailsTeam records through an association attribute called sailsTeams. The association type will be collection. Configure the sailsTeam model so that each sailsTeam record can be associated with one or more user records through an association attribute called members. The association type will be collection.

I'll create model called -- SailsTeam to track the sales teams. Within this model I'll create a new association attribute called-- members to represent the Users that comprise the sales team. Finally, I'll configure the User model with a new association attribute called-- sailsTeams to represent which (if any) sailsTeam the User belongs.

Although sails abstracts the complexity away from me, I find it helpful to dig deeper in order to make a more informed decision of how I configure my associations. The number of models comprising this many-to-many relationship might be surprising. In this configuration we have two join or junctions models -- one for the relationship betwen Users to SailsTeam and between SailsTeam to Users. In a bit, I'll show how I can make one slight change to the configuration so that only one join/junction model is necessary, however, could there ever be a situation where I want a many-to-many association where my Models don't know about each other?

Suppose I have an application where a user can "friend" another user and/or designate that user as an "enemy". In this application I don't want one user to know that another user has made them an enemy. Therefore, I'll maintain the updates to the associations for each model.

Note: Creating and Removing associations are identical to one-to-many associations.

###Configuring the Model: a many-to-many association with via

Task: Configure the User and sailsTeam models so that changing the association in one model syncs with the other model.

So far, I'm responsible for keeping the User and sailsTeam models in sync. Instead, when a User is added or removed from the sailsTeam or vice versa I want each model to be updated with the change. To accomplish this I'll use a new attribute to associations called via. Currently, my SailsTeam model doesn't know about the existence of the User model. Using via indicates which attribute will be synced up between a given model on a change. So from the perspective of the User model, when Nikola Tesla is added to the sailsTeam, I want the members association attribute to be updated as well.

Once again, my sailsProject has three models -- the User model, the sailsTeam model, and the join/junction model that bridges the other two models together. Now both the User and sailsTeam models are aware of each other. Therefore, associations that are added or removed occur on both models simultaneously.

###Creating a new sailsTeam and add an association with an existing User

Task: Create a new sailsTeam with the name Mid-West and make Nikola Tesla a member.

Note: I created two sailsTeams prior to this example.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.findOne(1).exec(function(err, user) { user.sailsTeams.add({name: 'Mid-West'}); user.save(); });

Here is the same code but in a more readable format:

User.findOne(1)
  .exec(function(err, user) { 
    user.sailsTeams.add({name: 'Mid-West'}); 
    user.save(); 
  });

####How does this work?

  1. First I find Nikola Tesla by passing in his primary key (1) into the .findOne() method.
  2. Next, using dot notation (user.sailsTeams) I'll add a new sailsTeam by passing in a "nested" object {name: 'Mid-West'} as an argument to the .add() method. Since I haven't provided a primary id (e.g. id: 10), Sails will automatically create the sailsTeam and associate Nikola Tesla with it.
  3. Finally, I'll save the User -- Nikola Tesla using the .save() method.

###Removing an association between an existing sailsTeam and an existing User

Task: Remove Nikola Tesla from the Mid-West sailsTeeam.

First I'll open the Sails console by running the following command in my terminal:

$ sails console

Now, at the sails> prompt, I'll enter:

sails> User.findOne(1).exec(function(err, user) { user.sailsTeams.remove(3); user.save(); });

Here is the same code but in a more readable format:

User.findOne(1)
  .exec(function(err, user) { 
    user.sailsTeams.remove(3); 
    user.save(); 
  });

####How does this work?

  1. First I find Nikola Tesla by passing in his primary key (1) into the .findOne() method.
  2. Next, using dot notation (user.sailsTeams) I'll remove the Mid-West sailsTeam by passing in that sailsTeam's primary key (3) as an argument to the .remove() method.
  3. Finally, I'll save the User -- Nikola Tesla using the .save() method.

##Combining a one-to-many association with a one-way association using via

Salespeople don't want to fight over leads. Therefore I want to limit the associations so that a user can have multiple leads but only one user can be assocated with a particular lead at one time.

###Using via to limit a lead Record to one user Record

Task: Configure the Lead model with a new owner association attribute, using the model association type, with the associated model as user. Configure the User model's leads association attribute with the via attribute using owner.

First, I'll configure the Lead model found in the /sailsPeople/api/models/Lead.js file with a new association attribute called owner.

Note: If you already have Records from your Lead model, these Records will be deleted the next time you start sails using sails lift or sails console as part of the auto-migration of Sails.

Next, I'll configure the User model found in the /sailsPeople/api/models/User.js file with the via attribute and reference the owner association attribute of the Lead model.

So what have I accomplished? By syncing the one to many relationship between user and lead with the one-way relationship between lead and user I've guaranteed that a lead can only be "owned" by one user. I'll go through this step-by-step.

In my sailsPeople project I've created the following leads and users:

Let's see what happens when I associate Nikola Tesla with George Jetson:

Not surprisingly, the primary key of Nikola Tesa is added to the owner association attribute of George Jetson. Next, I'm going to associate the remainder of the leads to Nikola Tesla using the same process ending up with the following:

So what will happen if I associate the user Neal Stephenson with the lead George Jetson?

Because I've sync'd these two models using the via attribute, Sails has added the appropriate back-end configuration so that whether I add an association from the User model or the Lead model only one user will be associated with a particular lead.

Note: To prevent a user other than Nikola Tesla from changing a lead association, I could create a policy where only users that have an association with a lead, have the ablity to change the association. See the policies reference guide for information on how to configure policies.

sails101-associations-overview's People

Contributors

irlnathan avatar

Watchers

Vikranth Kanumuru avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.