Nodal 0.6 released!

Nodal 0.6: Tests as First-Class Citizens, Deep Joins and More

Nearly 3 weeks since our last release (0.5), we're proud to officially announce the launch of Nodal 0.6. We think 0.6 offers a great step up from 0.5, with tools that will enable both hobbyists and enterprise developers to begin thinking about really working with Nodal on our path to a stable 1.0 release.

With this update we're pleased to announce the introduction of; Tests as First-Class Citizens, Deep Joins, Cascading Deletes, Database Seeds, Template Hierarchies, Postgres JSON support and more!

If you want to get a brief introduction to Nodal first, visit Nodal on Github.

Included in 0.6

Tests as First-Class Citizens

Testing is now front-and-center in new Nodal applications with Mocha integrated by default and Travis-CI ready to go. You'll notice your test/tests directory pre-populated. Run npm test after beginning a new Nodal project and you'll see a few passing tests.

Example Test: Controller

All of your tests should fit in the test method of every Nodal.mocha.Test class. You use standard mocha syntax here, but no need to use describe, as it wraps around your class using the class name as the descriptor.

We see here the Test#endpoint() method, which mocks an HTTP request to a provided endpoint on your application (follows your route). This is for writing full integration tests that will make sure routing, controller, model and view logic are all working properly together.

You can also import any part of your project and test it individually, as you would normally using Mocha.

class IndexControllerTest extends Nodal.mocha.Test {

    test(expect) {

      it('Should return an HTTP 200', done => {

        this.endpoint('/').get((status, headers, body, json) => {

          expect(status).to.equal(200);
          done();

        });

      });

    }

  }

Create new tests from the command line with nodal g:test TestName.

Deep Joins

Nodal now supports deep (or nested) joins for multiply-nested Models. What this means, is given the hierarchy;

Picture.joinsTo(User, {multiple: true});
Post.joinsTo(User, {multiple: true});
Comment.joinsTo(Post, {multiple: true});
Like.joinsTo(Comment, {multiple: true});

That can be visualized like so;

[User] -> has multiple -> [Post]
  | has multiple            | has multiple
  v                         v
[Picture]                 [Comment]
                            | has multiple
                            v
                          [Like]

You can perform something complex like;

Picture
  .join('user__posts__comments__likes')
  .end((err, pictures) => {

    // get joined user
    pictures[0].joined('user');
    // get posts of joined user
    pictures[0].joined('user').joined('posts');
    // get comments of joined posts of joined user
    pictures[0]
      .joined('user')
      .joined('posts')[0]
      .joined('comments');
    // it's turtles all the way down
    pictures[0]
      .joined('user')
      .joined('posts')[0]
      .joined('comments')[0]
      .joined('likes');

    this.respond(err || pictures);

  });

And if you choose to output an API response;

{
  meta: {...},
  data: [
    {
      user: {
        posts: [
          {
            comments: [
              {
                likes: [...]
              }
            ]
          }
        ]
      }
    }
  ]
}

So;

// get 1st picture, user from pic, 2nd post from user,
//   4th comment from post
response.data[0].user.posts[1].comments[3]

Cascading Deletes

Related to deep joins, you can now do Model#destroyCascade and ModelArray#destroyCascade that will destroy all dependent (child) models given a parent.

User.find(1, (err, user) => {

  user.destroyCascade(err => {
    // Destroy everything!
  });

});

Database Seeds

You can now modify config/seed.json to set environment (NODE_ENV) specific seeds. Each environment configuration takes an Object of model names, which each expects an array of objects representing values to insert.

Any beforeSave() logic will be triggered for each Model.

Seeds can executed using nodal db:seed.

For example,

{
  "test": {
    "User": [
      {
        "email": "[email protected]",
        "password": "password",
        "username": "test"
      }
    ],
    "Post": [
      {
        "body": "Hello, world."
      },
      {
        "body": "Post #2"
      }
    ]
  }
}

Template Hierarchies

Application#template now takes multiple parameters to specify parent / child template relationships. The first argument is always the parent, and you can keep specifying nested children as long as you'd like.

For example, in the get() method of a Controller...

get() {

  this.render(
    this.app.template('layout.html', 'content.html').generate(
      this.params,
      {}
    )
  );

}

In layout.html...

<div>This is the parent</div>
<div>{{= this.child() }}</div>
<div>After child.</div>

In content.html...

This is the <em>child</em>, nested in parent

Will output as:

<div>This is the parent</div>
<div>This is the <em>child</em>, nested in parent</div>
<div>After child.</div>

... and More!

We've also refactored Middleware to happen before a Controller is executed, added Renderware for right before a Controller outputs data to a client, rethought scheduled tasks, and added PG json support.

Thanks for checking us out, and we really hope you enjoy Nodal's progress. We're looking forward to a hyper-productive year, and would love for you to join us.

Keep up to date at nodaljs.com as well as Nodal on GitHub.