docs/DatabaseAndModels/DefiningAndUsingModels.md

Summary

Maintainability
Test Coverage
# Defining and using models

A model is defined as file under the `src/models` path. It must export a table schema definition and may optionally export definitions for virtual fields, model methods and document instance methods.


## Schema 

At a bare minimum our model must export a `schema`:

```javascript
// file: <project folder>/src/models/user.js

const ProfileSchema = {
  displayName: { 
    type: String, 
    required: true 
  },
};

exports.schema = {
  username: { 
    type: String, 
    required: true,
  },
  password: { 
    type: String, 
    required: true,
  },
  profile: { 
    type: ProfileSchema, 
    required: false,
  },
  lastLogin: { 
    type: Date, 
    required: false,
  },
};
```

_Note: Thinodium uses the [simple-nosql-schema](https://github.com/hiddentao/simple-nosql-schema) module to validate schemas. Check it out to find out about the different types and options possible for each field defined in the schema_.

The above schema specifies four fields for the database table - `username`, `password`, `profile` and `lastLogin` - with the first two fields being mandatory for any new rows that are to be inserted into the table. The `profile` field value is a nested document which must contain atleast a `displayName` key. The `lastLogin` field is of type `Date` meaning that in the database it is stored as a date timestamp.

Using this model is simple:

```javascript
// file: <project folder>/src/controllers/index.js

exports.add = function*() {
  let user = yield this.App.models.User.insert({
      username: 'test@test.com',
      password: 'password',
      profile: {
      displayName: 'test user'
      }
  });
  
  this.body = user.username;    // test@test.com
}
```

We can fetch all rows at any time:

```javascript
// ...continuing from previous example...

let allUsers = yield this.App.models.User.getAll();

/* allUsers.length === 1 */

console.log( allUsers[0].username );      /* test@test.com */
```


Every database table row has a unique `id` field (the primary key) which is auto-generated by the database engine. This can be used to quickly fetch an item we've previously inserted:

```javascript
// ...continuing from previous example...

// reload user from db
let addedUser = yield this.App.models.User.get(user.id);
```

We can update a user's attributes:

```javascript
// ...continuing from previous example...

// update username
user.username = 'test2@domain.com';
yield user.save();
```

We can also remove the user.

```javascript
// ...continuing from previous example...

yield user.remove();

let addedUser = yield this.App.models.User.get(user.id);

/* addedUser === null */
```

_Note: Read the [Thinodium docs](https://hiddentao.github.io/thinodium) for the full model API._

## Indexes

To speed up lookups on heavily-used tables we can and should add _indexes_:

```javascript
// file: <project folder>/src/models/user.js

exports.schema = {
  username: { 
    type: String, 
    required: true,
  },
  password: { 
    type: String, 
    required: true,
  },
  roles: { 
    type: [String], 
    required: false,
  },  
};

exports.indexes = {
  {
    name: 'username',
  },
  {
    name: 'roles',
    options: {
      multi: true,
    },
  },    
};
```

We've asked for two indexes - one on the `username` field and one on the `roles` field with the additonal setting that it should be treated as being able to hold multiple values (because `roles` specifies an array of strings). 

From now on lookup by username and/or role will be quicker than before.

_Note: More complex compound indexes are supported - check out Waigo's built-in models for real examples._

## Primary key

By default the `id` field (which is auto-defined by the database engine) is assumed to the primary key. But you can override this setting using the `pk` export:

```javascript
// file: <project folder>/src/models/user.js

exports.pk = 'username';
```

## Model methods

Model methods are the methods available on the model object itself. There are a number of built-in methods - `get()`, `insert()`, etc - provided by Waigo. But it is a good idea to add custom methods as a convenience and in order to control access.

For example, let's say we wish to hash a new user's password before inserting it. We will provide an `add()` method to add a user, and a `load()` method to fetch a user given username and password:

```javascript
// file: <project folder>/src/models/user.js

const waigo = require('waigo'),
    _ = waigo._;    // lodash

exports.schema = {
  username: { 
    type: String, 
    required: true,
  },
  password: { 
    type: String, 
    required: true,
  },
};

exports.modelMethods = {
  add: function*(username, password) {
    this._logger.info(`Adding user: ${username}`);
    
    return this.insert({
      username: username,
      password: someHashingFunction(password),
    });
  },
  
  load: function*(username, password) {
    this._logger.info(`Loading user: ${username}`);
    
    password = someHashingFunction(password);
    
    let ret = yield this.rawQry().filter(function(user) {
      return user('password').eq(password).and(user('username').eq(username));
    }).run();
    
    this.wrapRaw(_.get(ret, '0'));
  }
};
```

The `_logger()` method we used inside the model methods gives us access to a [logger](../Logging/) associated with the Model - it is auto-created by Waigo. We can also access the `App` object at any time using the `_App()` method, though in this case we didn't need to do this. 

The `rawQry()` method is available on all Waigo models and enables the raw querying mode for our underlying database table. In this case it's the same as executing a query using [rethinkdbdash](https://github.com/neumino/rethinkdbdash), the RethinkDB database library that is being used at a lower level.

The `wrapRaw()` method wraps raw database data (as returned by using `rawQry()`) in our models' document instances (see [Thinodium docs](https://hiddentao.github.io/thinodium)).

We would use this model as follows:

```javascript
yield App.models.User.add('test@test.com', 'password');

// let's try loading, but with a wrong password
let user = yield App.models.User.load('test@test.com', 'wrongpassword');

/* user === null */

// let's try loading with right password
user = yield App.models.User.load('test@test.com', 'password');

console.log(user.password);  /* F24FA5B... some hash */
```

Notice above that `user.password` returns the hashed version of the password. Thus we wouldn't be able to manually compare a password which the user has input with the the one in the documented returned by the model.

We can work around this using document methods.

## Document methods

Document methods are extra methods to enable on the document instances returned by our Model's data retrieval methods.

_Note: Waigo automatically adds `_App()` and `_logger()` methods to every document instance, same as for Model objects_.

Let's add a `checkPassword()` document method which lets us check whether a given password matches the password stored for a user:

```javascript
// file: <project folder>/src/models/user.js

const waigo = require('waigo'),
    _ = waigo._;    // lodash

exports.schema = {
  username: { 
    type: String, 
    required: true,
  },
  password: { 
    type: String, 
    required: true,
  },
};

exports.docMethods = {
  checkPassword: function*(password) {
    return this.someHashingFunction(password) === this.password;
  },
};
```

_Note: The context (`this`) of a document method is always the document instance itself._

How we might use it:

```javascript
let user = yield App.models.User.get('user id');

if (user.checkPassword(inputPassword)) {
  // password correct!
} else {
  // password incorrect!
}
```

_Note: Read the [Thinodium docs](https://hiddentao.github.io/thinodium) for the list of built-in Document instance methods._

**Virtual fields**

Virtual fields are document instance fields which appear to work like normal fields, except that they are actually represented by getter and setter functions, and consequently are not part of the model's schema and are not stored in the database.

Virtual fields act as convenient accessors for information derived from the real document data. For example, let's say we store a user's birth date. We may add an `age` virtual field which automatically calculates their current age:

```javascript
// file: <project folder>/src/models/user.js

const moment = require('moment'),
  waigo = require('waigo'),
  _ = waigo._;    // lodash

exports.schema = {
  username: { 
    type: String, 
    required: true,
  },
  dob: { 
    type: Date, 
    required: true,
  },
};

exports.docVirtuals = {
  age: {
    get: function() {
      return moment().diff(this.dob, 'years');
    }
  }
};
```

And here is how you would use it:

```javascript
let user = yield App.models.User.insert({
  username: 'test',
  dob: moment('1982-02-02', 'YYYY-MM-DD').toDate(),
});

console.log( user.age );
/* 34 */

// let's try setting the field
user.age = 2; /* throws Error */
```

Notice how we were unable to set `age`. Virtual fields can be set if we add setters to them:

```javascript
exports.docVirtuals = {
  age: {
    get: function() {
      return moment().diff(this.dob, 'years');
    },
    set: function(val) {
      // weird thing to do, I know!
      this.dob = moment().subtract(val, 'years').toDate();
    }
  }
};
```

_Note: The getters and setters of virtual functions must by synchronous non-generator functions._

When using the above setter keep in mind that we need to explicitly `save()` the document in order for the `dob` field changes to take effect:

```javascript
const oldDob = user.dob;

console.log( user.age );
/* 34 */

user.age = 25;

let diff = moment(user.dob).diff(oldDob, 'years');

console.log( diff );
/* 9 */

// save it!
yield user.save();
```