View our GitHub

Please visit sails-docs on GitHub to view documentation on your mobile device.

Edit Page

Custom Generators

Overview

Sails is all about automating repetitive tasks to make your programming easier and Generators are no exception. Generators are command line utilities within Sails that automate the generation of files through templates within your Sails projects. In fact, Sails core uses generators to create Sails projects. So when you type...

~/ $ sails new myProject

...sails is using generators to build up the initial folder structure of a Sails app like this:

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

Other examples of generators in Sails core (meaning they are built into Sails) include:

  • sails-generate-adapter
  • sails-generate-backend
  • sails-generate-controller
  • sails-generate-frontend
  • sails-generate-model
  • sails-generate-new
  • sails-generate-views
  • sails-generate-views-jade
  • Although not a stand-alone module there's one other generator accessed via sails generate api

To begin the process of generating a generator you can use sails-generate-generator.

Note: The idea of creating a generator by invoking a generator may seem like some kind of demented infinite loop but trust us it will not create a worm hole to an evil alternate universe.

Creating a Generator

First we need a Sails project. If you haven't already created one go to your terminal and type:

~/ $ sails new myProject

cd into myProject or from any existing Sails project and create a generator from the terminal named awesome by typing:

~/ $ sails generate generator awesome

You'll know the generator was created if you see the message: info: Created a new generator!.

Enabling the Generator

To enable the generator you need to tell Sails about it via \myProject\.sailsrc. If you were using an existing generator you would link to an npm module in .sailsrc and then just install it with npm install. Since you're developing a generator, you'll link to it directly. To create the link go back to the terminal and cd into the awesome generator folder and type:

~/ $  pwd

The pwd command will return a fully resolved path to the generator (e.g. /Users/irl/sails_projects/myProject/awesome).

Copy the path and then open myProject/.sailsrc. Within the modules property add an awesome key and paste the path to the awesome generator as the value.

Note: you can name the generator anything you want, for now let's stick with awesome:

{
  "generators": {
    "modules": {
        "awesome": "/Users/irl/sails_projects/myProject/awesome"
    }
  }
}

Note: Whatever name you give your generator in the .sailsrc file will be the name you'll use from the terminal command-line to execute it.

Lastly, you'll need to do an npm install from the terminal in order to install the necessary modules that were added to the generator's package.json file.

Using the Generator

Back at the terminal type: sails generate awesome example. Let's take a look at what was generated.

What did the Generator create?

Open up your project in a text editor you'll notice that a folder called hey_look_a_folder was created and a file named example was also created:

/**
 * This is just an example file created at Wed Jun 04 2014 17:35:59 GMT-0500 (CDT).
 *
 * You can use underscore templates, see?
 */

module.exports = function () {
  // ...
};

The folder and file illustrate the power of the generator not only to create elements but to use arguments from the command-line to influence their content. For example, the file name, example, used an element from the command line argument sails generate awesome example.

Basic generator configuration

All of the configuration for the awesome generator is contained in \myProjects\awesome\Generator.js. The main parts of Generator.js are the before() function and the targets dictionary.

Note: We refer to the JavaScript object that uses {} as a dictionary.

Configuring the before() function

Let's take a closer look at myProject/awesome/Generator.js:

...
before: function (scope, cb) {

    // scope.args are the raw command line arguments.
    if (!scope.args[0]) {
      return cb( new Error('Please provide a name for this awesome.') );
    }

    // scope.rootPath is the base path for this generator
    if (!scope.rootPath) {
      return cb( INVALID_SCOPE_VARIABLE('rootPath') );
    }

    // Attach defaults
    _.defaults(scope, {
      createdAt: new Date()
    });

    // Decide the output filename for use in targets below:
    scope.filename = scope.args[0];

    // Add other stuff to the scope for use in our templates:
    scope.whatIsThis = 'an example file created at '+scope.createdAt;

    // When finished, we trigger a callback with no error
    // to begin generating files/folders as specified by
    // the `targets` below.
    cb();
  },
  ...

Each generator has access to the scope dictionary, which is useful when you want to obtain the arguments that were entered when the generator was executed.

In your default awesome generator a new key, createdAt: was created in the scope. We'll take a look at this dictionary within a template momentarily.

...
// Attach defaults
    _.defaults(scope, {
      createdAt: new Date()
    });
...

Next, the arguments used when executing the awesome generator (e.g. sails generate awesome <theargument>) are available in an array from scope.args. In our default awesome generator a filename property was added to the scope and assigned the value of the first element of the scope.args array (e.g. example):

...
scope.filename = scope.args[0];
...

Finally, another property (e.g. scope.whatIsThis) was added to the scope dictionary.

...
scope.whatIsThis = 'an example file created at '+scope.createdAt;
...

####Configuring the targets dictionary

Now, let's take a look at the targets dictionary in myProject\awesome\Generator.js to better understand how the folder (e.g. hey_look_a_folder) and file (e.g. example) were generated.

...
targets: {

    // Usage:
    // './path/to/destination.foo': { someHelper: opts }

    // Creates a dynamically-named file relative to `scope.rootPath`
    // (defined by the `filename` scope variable).
    //
    // The `template` helper reads the specified template, making the
    // entire scope available to it (uses underscore/JST/ejs syntax).
    // Then the file is copied into the specified destination (on the left).
    './:filename': { template: 'example.template.js' },

    // Creates a folder at a static path
    './hey_look_a_folder': { folder: {} }

  },
...

The template and folder helpers look a lot like routes. These helpers perform the actions that their names indicate.

The template helper

Not surprisingly the template helper creates files based upon a template. Remember, that the scope dictionary is accessible to the templates.

...
'./:filename': { template: 'example.template.js' },
...

The left-hand side specifies the path and filename where as the right dictates which template the generator will use to create the file. Notice you're using the filename from the scope.filename assignment that was based upon the the first element of scope.args in the before() function. The templates can be found in myProject\awesome\templates. In the awesome generator you're using example.template.js:

/**
 * This is just <%= whatIsThis %>.
 *
 * You can use underscore templates, see?
 */

module.exports = function () {
  // ...
};

Note: the scope property whatIsThis which as you may recall uses the createdAt: property created in the before function.

The folder helper

The folder helper generates folders.

...
'./hey_look_a_folder': { folder: {} }
...

The left-hand side specifies the path and name of the folder. The right-hand side specifies any optional parameters. For example, by default, if a folder already exists at that location an error will be displayed: Something else already exists at ::<path of folder>. If you want the generator to overwrite an existing folder you have two options. You can alter the folder helper to overwrite the existing folder by specifying force: true in the options parameters:

...
'./hey_look_a_folder': { folder: { force: true} }
...

You can also use the --force parameter from the command-line when executing the generator which will configure all helpers to overwrite:

~/ $ sails generate awesome test --force

Using a generator within a generator

To leverage the work of other programmers, generators were designed to be used by other generators. This is where the scope dictionary being passed down to all generators becomes really powerful.

For example, Sails core has a generator called sails-generate-model. Since it's built into Sails core, there's no installation necessary. To add it to our awesome generator example is simple. Within the myProject\awesome\Generator.js include it by inserting ./': ['model'],

...
targets: {

    // './:filename': { template: 'example.template.js' },

    './': ['model'],

    // './hey_look_a_folder': { folder: {} }

  },
...

Note: By using ./ as the path, any models will be placed in the \api\models folder from whatever folder the generator was executed.

That's it! Now let's create a model from within the awesome generator. From the terminal type:

~/ $ sails generate awesome user name:string email:email

If you take a look in myProject\api\models you'll see a new file named User.js has been created that contains the model attributes specified earlier.

/**
* User
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {

  attributes: {

    name : { type: 'string' },

    email : { type: 'email' }
  }
};

Bonus: Publishing your generator to npmjs.org

To publish the awesome generator to npmjs.org go into the myProject\awesome\package.json file and change the name, author and any other meta information (e.g. licensing).

From within the myProject\awesome folder at the terminal type:

~/ $ npm publish

Note: If don't already have an NPM account, go to (npmjs.org)[https://www.npmjs.org/] and create one.

To unpublish the module, type:

~/ $  npm unpublish` --force

Change the myProject\.sailsrc to:

{
  "generators": {
    "modules": {
      "awesome": "whatever you named the module in package.json"
    }
  }
}

From the awesome generator folder within the terminal type:

~/ $ npm install

And you're all set!

Is something missing?

If you notice something we've missed or could be improved on, please follow this link and submit a pull request to the sails-docs repo. Once we merge it, the changes will be reflected on the website the next time it is deployed.