geddy

Geddy guide

CLI

Geddy has a robust CLI tool to help you generate apps, run tests or scripting tasks in your app, or interact with your app in a console.

geddy

Running the geddy command with no arguments will run the geddy app in the current directory.

cd path/to/app
geddy
// will run the app in path/to/app

Options:

  • --environment, -e: Environment to use
  • --hostname, -b: Host name or IP to bind the server to (default: localhost)
  • --port, -p: Port to bind the server to (default: 4000)
  • --geddy-root, -g: /path/to/approot The path to the root for the app you want to run (default is current working directory)
  • --workers, -w: Number of worker processes to start (default: 1)
  • --debug, -d: Sets the log level to output debug messages to the console
  • --help, -h: Output this usage dialog
  • --version, -v: Output the version of Geddy that's installed

Examples

# Start Geddy on localhost:4000 in development mode or if the
# directory isn't a Geddy app it'll display a prompt to use "geddy -h"
geddy
# Start Geddy on port 3000
geddy -p 3000
# Start Geddy in production mode
geddy -e production
# Generate a users scaffolding using Jade templates
geddy -j scaffold user

geddy console

This command starts a REPL in the context of your application. It will load your application's environment, and you can interact with all its models.

Examples

# Start a REPL (in 'production' mode)
geddy console
# Start a REPL in 'development' mode
geddy console environment=development

geddy gen [command] [options] [arguments]

This is the generator script which you can use to create apps, resource scaffolds, or bare models and controllers.

Commands

  • gen app <name>: Create a new Geddy application
  • gen resource <name> [attrs]: Create a new resource. A resource includes a model, controller and route
  • gen scaffold <name> [attrs]: Create a new scaffolding. Scaffolding includes the views, a model, controller and route
  • gen secret: Generate a new application secret in config/secret.json
  • gen controller <name>: Generate a new controller including an index view and and a route
  • gen model <name> [attrs]: Generate a new model
  • gen auth[:update]: Creates user authentication for you, using Passport.
  • gen migration <name>: Generate an empty migration for SQL databases

For all of these commands, [attrs] is a list of attributes for the model, in the format of 'name:datatype' (e.g., foo:int).

Options

  • --realtime, -rt: When generating or scaffolding, take realtime into account
  • --jade, -j: When generating views this will create Jade templates (Default: EJS)
  • --handle, -H: When generating views this will create Handlebars templates (Default: EJS)
  • --mustache, -m: When generating views this will create Mustache templates (Default: EJS)
  • --swig, -s: When generating views this will create Swig templates (Default: EJS)

Examples

# Generate an app in a directory named 'foo'
geddy gen app foo
# Generate a users resource with the model properties name as a string and admin as a boolean
geddy gen resource user name admin:boolean
# Generate a users scaffolding user name as the default value to display data with
geddy gen scaffold user name:string:default

geddy jake [task] [options] [env vars]

This command runs a Jake task in the context of the current app. This allows you to run your tests or any other command-line tasks in the context of your application, with full access to your models.

Geddy also ships with a number of useful Jake tasks built in, e.g., the routes task, which displays all the routes in your app.

Options

See https://github.com/mde/jake for full documentation

Examples

# Run your app's tests in the app environment
geddy jake test
# Initialize the development database for your app
geddy jake db:init environment=development
# Show all routes
geddy jake routes
# Show all routes for the user resource
geddy jake routes[user]
# Show the index route for the user resource
geddy jake routes[user.index]

To run "geddy jake" in a different environment do

geddy jake environment=[myEnvironment]

Authentication

Geddy provides built-in authentication which integrates with Passport to allow auth against either local accounts or third-party social services like Facebook and Twitter.

Using the generator

To set up a new Geddy app with built-in authentication, create your application like normal, then run the geddy auth command inside, like so:

$ geddy app by_tor
$ cd by_tor
$ geddy auth

This will pull down Geddy-Passport using NPM, and install all the needed code into your app. This includes the needed Passport libraries, and the Geddy models and controllers for the local User accounts and the login process.

Danger, Warning, etc.

The geddy auth generator should only be used in a new Geddy app. If you run it inside an existing app, it may overwrite existing files that you wanted to keep.

If you need to add auth to an existing app, you can take a look at the Geddy-Passport project, which is itself a Geddy app scaffold, and use the pieces you need.

Configuring Passport

You'll need to add the settings for Passport in your config/secrets.json file. That includes the redirect locations for after an auth failure/success, and the OAuth keys for your app. The setting will look similar to this:

{
  "passport": {
    "successRedirect": "/",
    "failureRedirect": "/login",
    "twitter": {
      "consumerKey": "XXXXXX",
      "consumerSecret": "XXXXXX"
    },
    "facebook": {
      "clientID": "XXXXXX",
      "clientSecret": "XXXXXX"
    },
    "yammer": {
      "clientID": "XXXXXX",
      "clientSecret": "XXXXXX"
    }
  }
}

Local users

Local User accounts just go through the usual RESTful actions you'd get in a normal Geddy resource. Start at "/users/add" to create a new User. You can modify "/app/models/user.js" to add any other properties you want.

Login with third-party services

A successful login with a third-party service like Facebook or Twitter will create a linked local User account if one does not exist.

E-mail activation

By default, local users require activation via e-mail. This does not apply to authentication via third-party services.

When a user signs up, an e-mail will be sent to their account with an activation link. Users will not be able to authenticate until they activate. Geddy auth sends these e-mails using Nodemailer.

For this feature to work, you'll have to npm install nodemailer and set it up in your app config. You'll also need to set a hostname for your app (for the activation link) for this to work. You can also easily turn this feature off in the Users controller.

Authenticated users

After a user successfully authenticates, she will end up redirected to the successRedirect you've specified, and there will be two new items in the user's session:

  • userId -- the id for the local User account
  • authType -- the method of authentication (e.g., 'local', 'twitter')

Requiring authentication in your app

Use a before-filter, and redirect to the login page if there is no userId in the user's session. If there is a userId, that means the user is authenticated. There is a built-in reequireAuth function in the Passport helper-library, which does just this.

The User controller for local accounts is protected like this:

var passport = require('../helpers/passport')
  , cryptPass = passport.cryptPass
  , requireAuth = passport.requireAuth;

var Users = function () {
  this.before(requireAuth, {
    except: ['add', 'create']
  });

// Rest of controller omitted

This allows new accounts to be created, because the 'add' and 'create' actions are exempted, but only authenticated users can view or update existing users.

Responding

Geddy has APIs of different levels of granularity for responding to requests. From highest-level to lowest:

  • respondWith
  • respondTo
  • respond/redirect
  • output

In general, you'll be able to use the highest-level API and expect Geddy to do the right thing, but the lower-level APIs are there if you need to do more specific things with your responses.

respondWith

respondWith method in API reference

The respondWith method is the highest-level response API. It handles formatting your output in the correct way, but also allows more general format-specific behaviors like doing redirects, or adding needed headers.

The best example of this is handling a response for a REST create request. In the case of a request that wants back an HTML response, after creating the item you'll want to respond with a redirect for the item's HTML page. In the case of a request that wants back JSON, you'll want to respond with a 201/created, and include a 'Location' header with the URL for the newly-created item.

To use respondWith, specify the formats your controller can use by calling canRespondTo with the list of formats:

var SnowDogs = function () {
  this.canRespondTo(['html', 'json', 'js']);
};

That will allow your controller to respond with the built-in response-strategies for these formats.

Call respondWith with a Geddy model instance, or a collection of instances, like so:

var SnowDogs = function () {
  this.canRespondTo(['html', 'json', 'js']);

  this.index = function (req, resp, params) {
    var self = this;
    geddy.model.SnowDog.all(function(err, snowDogs) {
      if (err) {
        throw err;
      }
      self.respondWith(snowDogs);
    });
  };

  this.show = function (req, resp, params) {
    var self = this;
    geddy.model.SnowDog.first(params.id, function(err, snowDog) {
      if (err) {
        throw err;
      }
      if (!snowDog) {
        throw new geddy.errors.NotFoundError('SnowDog ' + params.id + ' not found.');
      }
      self.respondWith(snowDog);
    });
  };
};

When you throw an error, Geddy will still perform content-negotiation and respond with an error of the correct format. If the requests wants a JSON response, Geddy will respond with a nice, parseable JSON response that includes a statusCode, statusText, message, and stack, if one is available.

respondTo

respondTo method in API reference

The respondTo method is the next level down in API granularity. It allows you to specify your own response-strategies to use for the response. NOTE: calling respondTo will override any formats declared using canRespondTo.

Call respondTo with an object containing the various response-strategies you want to use for the response, like so:

var Users = function () {

  this.show = function (req, resp, params) {
    var self = this;
    geddy.model.User.first({username: 'foo'}, function (err, user) {
      if (err) {
        throw err;
      }
      self.respondTo({
        html: function () {
          self.redirect('/user/profiles?user_id=' + user.id);
        }
      , json: function () {
          self.respond(user, {format: 'json'});
        }
      });
    });
  };
};

Using respondTo also allows you to do more than simply output formatted content. You can perform redirects, set headers, etc.

If you want to create your own specific response-strategies, you can also create a custom responder.

respond and redirect

respond method in API reference

redirect method in API reference

The respond method is a lower-level API call that simply outputs content in the correct format for a request. The 'html' format will render the appropriate template for the request, and API-style formats like 'json' will simply output the data-payload you pass in with the desired format:

var Users = function () {

  this.show = function (req, resp, params) {
    var self = this;
    geddy.model.User.first({username: 'foo'}, function (err, user) {
      if (err) {
        throw err;
      }
      this.respond(user);
    });
  };
};

The respond method takes an 'options' object that allows you set specific properties like the layout, or format to respond with. If you don't pass a format-override, Geddy will figure out the right format based on the file-extension requested, and the formats your controller supports.

The redirect method is exactly what it sounds like -- a way to tell the browser to request a different URL from your application. You can pass it a location string, or an object referencing specific controller or action.

output

output method in API reference

The output method is the lowest-level API for responding to a request. You should only use this method when you know precisely what you want in the response (i.e., HTTP status-code, headers, and content). Here's an example:

var Users = function () {

  this.create = function (req, resp, params) {
    var self = this
      , user = geddy.model.User.create(params);
    if (!user.isValid()) {
      throw new geddy.errors.BadRequestError('Oops!');
    }
    user.save((function (err, data) {
      if (err) {
        throw err;
      }
      // Respond with a 201/created and no content
      this.output(201, {
        'Content-Type': 'application/json'
      , 'Location': '/users/' + user.id
      });
    });
  };
};

Custom responders

You can write your own Responder (with its own response-strategies) to use in your controller with respondWith.

The simplest possible example of a custom responder is just a function. Set it to your own custom responder in your controller like so:

this.responder = function (controller, content, opts) {
  // Redirect Web content
  if (opts.format == 'html') {
    controller.redirect('/web' + controller.request.url);
  }
  else {
    controller.respond(content, opts);
  }
};

Or you can subclass the built-in Geddy responder, and change its strategies, or its respond method. Its strategies live on its 'strategies' property:

var CustomResponder = function () {
  var builtIns = geddy.responder.strategies;
  this.strategies = {
    html: builtIns.html
  , json: builtIns.json
  , xml: function (content, opts) {
      // Do something special for XML responses
    }
  };
};
CustomResponder.prototype = Object.create(geddy.responder.Responder.prototype);

Strategies are invoked on your controller instance, so 'this' will be a reference to your controller.

Also, you can also override the responder's respond method:

CustomResponder.respond = function (controller, content, opts) {
  var strategies = this.strategies;
  if (opts.format == 'xml') {
    throw new Error('Nobody uses XML anymore, buddy.');
  }
  // Otherwise, we don't care what format, just output
  controller.respond(content, opts);
};

To use your subclassed responder, set it in your controller:

this.responder = new CustomResponder();

Caching responses

Geddy lets you cache responses at the action level, so you can build the response for a particular action once, and then serve it from cache for all subsequent requests.

In your controller constructor, call the cacheResponse method with an action or list of actions you want to cache.

Here's an example:

// Controller for the 'zooby' resource
var Zoobies = function () {

  // Build the index action response once, then serve from cache
  this.cacheResponse(['index']);

  this.index = function () {
    var resp = {};
    // Do some complicated logic here you only want to do once
    this.respond(resp, {
      format: 'html'
    });
  };
};

exports.Zoobies = Zoobies;

Response-caching is primarily aimed at caching simple content-responses, so it has some limitations.

GET only: Currently only works for GET reqeusts.

Doesn't handle multiple formats: Currently only works for a particular controller/action combination, and not per-format.

If you need to serve responses for multiple formats (e.g., both JSON and HTML) from the same action, this will not work for you. It will cache the response for the first format it gets a request for.

Caches per-worker: Responses are cached in the Node process, so workers have to cache responses individually.

This means that if you have a response that may change over time, you can end up with different workers serving different cached responses for the same action.

Errors

Geddy makes it easy to return errors, and gives you an easy way to customize the error-responses you return.

Returning an error

To return an error as your response, simply throw the error you want. Geddy includes the full range of HTTP errors (as subclasses of the base JavaScript Error object) on the geddy.errors namespace object:

  • BadRequestError
  • UnauthorizedError
  • ForbiddenError
  • NotFoundError
  • MethodNotAllowedError
  • NotAcceptableError
  • InternalServerError

If you throw a generic JavaScript Error object, Geddy will actually respond with a 500/InternalServerError.

Here's an example:

  this.show = function (req, resp, params) {
    var self = this;

    geddy.model.SnowDog.first(params.id, function(err, snowDog) {
      if (err) {
        throw err;
      }
      if (!snowDog) {
        throw new geddy.errors.NotFoundError();
      }
      else {
        self.respondWith(snowDog);
      }
    });
  };

Error formats

If you make an API-style request for JSON data, and an error occurs, it's stupid for the server to respond with a rendered HTML page. Why would you expect a different content-type from the one requested just because an error occurred?

When there's an error, Geddy still does the right thing with the format, and returns the error in the expected content-type.

For example, a 404/NotFoundError for a JSON response would look like this:

{ statusCode: '404',
  statusText: 'Not Found',
    message: 'Could not find page "/foo/bar/baz.json"',
      stack: 'Not Found\n    at ...'
}

The stack-trace, if one is available, will be included in the 'stack' property of the response.

Customizing errors

For HTML responses, Geddy makes it easy to customize error pages. Views for errors can be found in your app in 'app/views/errors'. To add a customized view for a particular type of error, add a view with the name of the error in 'snake_case' in that directory.

For example, for a customized 404 NotFoundError page, you can add a 'not_found.html.ejs' in the directory. Any error-types (e.g., BadRequestError) without a specific view in that directory will use the 'default' view.

All error pages use the 'errors' layout in 'app/views/layouts'.

Errors with your errors

When there's an error rendering your custom error page ('Error Inception'), Geddy fallback to a simple, low-fi error-page to display the rendering error. Fix the rendering error, and your nice, customized error will appear.

E-mail

Geddy provides e-mail support, allowing your app to do things like requiring e-mail activation for new accounts, or sending notifications to your users. Geddy uses Nodemailer for this feature.

Configure your mail support in your app's configuration (e.g., your development.js or production.js files). You can set the e-mail username that will be used in outgoing mails (will be used with the 'hostname' in your config), and the Nodemailer transport and options. See Nodemailer transports for more details on transports.

Here's an example of a configuration using Direct transport:

, mailer: {
    fromAddressUsername: 'noreply'
  , transport: {
      type: 'direct'
    , options: {
        debug: true
      }
    }
  }

And an example using SMTP transport:

, mailer: {
    fromAddressUsername: 'noreply'
  , transport: {
      type: 'smtp'
    , options: {
        host: 'smtp.gmail.com'
      , secureConnection: true // use SSL
      , port: 465 // port for secure SMTP
      , auth: {
          user: 'gmail.user@gmail.com'
        , pass: 'userpass'
        }
      }
    }
  }

Deployment

We use the following example as a reference. There will be some differences with other environments.

Windows Azure

Pre-requisites
  1. Install the azure-cli module.
    npm install -g azure-cli
  2. Download your Azure .publishsettings file. You will be asked to login with your Azure credentials. If you do not have an account you can create one for free. azure account download 3. Import your .publishsettings file azure account import [file] 4. Install Geddy. If you're new, you can start with the tutorial
Notes
  • Your Geddy app is deployed via Git, which will ignore anyhting specified in the .gitignore including the config/secrets.json file.

If you need something that requires the secret such as sessions, etc. you'll encounter errors about doing geddy secret when you deploy. In this case you'll need to remove secrets.json from your .gitignore file. In this case, values in the JSON file can be popluated from environment variables using EJS syntax, to avoid including that sensitive information in revision control. See: https://github.com/mde/geddy/issues/309

Now we need to create a server.js file which Windows Azure will pick up for running Geddy server:

var geddy = require('geddy');

geddy.start({
  port: process.env.PORT || '3000',
  // you can manually set the environment, or configure to use
  // the node_env setting which is configurable via iisnode.yml
  // after the site is created.
  environment: 'production'
  // To configure based on NODE_ENV use the following:
  //environment: process.env.NODE_ENV || 'development'
});

In the object we're giving to geddy.start you can use any other arguments you'd for the configuration files, these will override the ones loaded for the environment. For more information about this file you can go here

Open you .gitignore file and remove the line for config\secrets.json - note: This is insecure, on public repo's as it exposes your cookie's secret hash.

Now it's time to create a node site. Subsitute 'mysite' below with your site name.

azure site create mysite --git

After selecting a location add everything to git and push to Windows Azure

git push azure master

For more information about deploying and supporting Node Apps on Windows Azure Websites see the Command Line Tools How-To-Guide article.

To learn more about Node Websites in Windows Azure see this a href="http://www.windowsazure.com/en-us/develop/nodejs/tutorials/create-a-website-(mac)/"article

Nodejitsu

Pre-requisites
  1. Install the jitsu module
  2. Install Geddy. If you're new, you can start with the tutorial
  3. Create a Nodejitsu account(Not required: we'll go over creating one from the CLI)
  4. Have an app ready to be deployed
Notes
  • Nodejitsu reads the deployed .gitignore file, so if you have the config/secrets.json file in there(you should), then you'll encounter errors about needing to do geddy secret if you need the secrets for sessions, etc. To circumvent this, create a .npmignore file and include all the contents from the .gitignore except the config/secrets.json line. Nodejitsu ignores the .gitignore file only if a .npmignore file is included as well.

If you haven't already you'll need to sign up and log in to Nodejitsu which you can do from the jitsu executable.

jitsu signup
jitsu login

Now once you've created an account on Nodejitsu we need to prepare the application you have for deployment. First we'll edit(or create) a package.json file in the app's root directory

{
  "name": "node-example",
  "version": "0.0.1",
  "dependencies": {
    "geddy": "0.6.x"
  },
  "subdomain": "geddy-example",
  "scripts": {
    "start": "app.js"
  },
  "engines": {
    "node": "0.8.x"
  }
}

Here we have a subdomain key/value this tells Nodejitsu what subdomain to host the application on(e,g,. geddy-example.jit.su). We also have a start script pointing to app.js in the root directory, we'll go over what to put here in a second. Of course you should edit this to include anything else you want, like other dependences or an author.

Now we need to create a app.js file so that Nodejitsu can use it to boot the Geddy server, here's what it should look like

var geddy = require('geddy');

geddy.start({
  environment: 'production'
});

In the object we're giving to geddy.start you can use any other arguments you'd for the configuration files, these will override the ones loaded for the environment. For more information about this file you can go here

Now that our application is set up for deployment, we need to deploy it which is just a single command

jitsu deploy

Now you can go to http://geddy-example.jit.su and see your application!

Heroku

Pre-requisites
  1. Install heroku toolbelt
  2. Install Geddy. If you're new, you can start with the tutorial
  3. Be familiar with GIT, the basic geddy commands, and heroku's deployment models
  4. Have an app ready to be deployed.

Add a package.json file to your app's root directory

{
  "name": "node-example",
  "version": "0.0.1",
  "dependencies": {
    "geddy": "0.11.x"
  },
  "engines": {
    "node": "0.10.x",
    "npm": "1.3.x"
  }
}

Add a .env text file to your app's root directory. This is read by Foreman run booting the app locally.

NODE_ENV=development

Add a Procfile text file to your app's root directory. This is read by Heroku when booting the app.

web: geddy --environment $NODE_ENV

Now it's time to create a heroku app.

$ heroku create --stack cedar

Add the NODE_ENV environment variable to Heroku.

heroku config:set NODE_ENV=production

Add everything to git and push to Heroku.

$ git push heroku master
Database Add-Ons

Heroku gives you a database connection url, which you will need to parse.

First, add the database of your choice:

heroku addons:add mongohq:sandbox

This will give you a new environment variable that looks like this:

MONGOHQ_URL: mongodb://<user>:<pass>@hatch.mongohq.com:10034/app003132345

You have to use something like parse_url to parse the URL into individual options.

Edit your config/production.js to parse the URL:

// See `parse_url` above
var MONGO_PARSED = parse_url(process.env.MONGOHQ_URL);

var config = {
  detailedErrors: false
, debug: false
, hostname: "0.0.0.0"
, port: process.env.PORT || 4000
, model: {
    defaultAdapter: 'mongo'
  }
, db: {
    mongo: {
      username: MONGO_PARSED.user
    , dbname: MONGO_PARSED.path.substring(1)    // Get rid of the leading `/`
    , password: MONGO_PARSED.pass
    , host: MONGO_PARSED.host
    , port: parseInt(MONGO_PARSED.port)
    }
  }
, sessions: {
    store: 'cookie'
  , key: 'did'
  , expiry: 14 * 24 * 60 * 60
  }
};

module.exports = config;

Your app should now be configured for the database add-on.

Secrets

If your app uses sessions or auth, you'll need to push your secrets.json file to Heroku. To do this securely, you'll have to use environment variables.

First, open up secrets.json and add each secret into your .env file.

For example, if your config/secrets.json file looks like this:

{
  "passport": {
    "loginPath": "/login",
    "successRedirect": "/",
    "failureRedirect": "/login?failed=true",
    "twitter": {
      "consumerKey": "secret1",
      "consumerSecret": "secret2"
    },
    "facebook": {
      "clientID": "secret3",
      "clientSecret": "secret4"
    }
  },
  "secret":"secret5"
}

Your .env file should look something like this:

NODE_ENV=development
TWITTER_KEY=secret1
TWITTER_SECRET=secret2
FACEBOOK_ID=secret3
FACEBOOK_SECRET=secret4
GEDDY_SECRET=secret5

You'll have to run a command like the following to save the environment variables to Heroku:

heroku config:set TWITTER_KEY=secret1 TWITTER_SECRET=secret2 FACEBOOK_ID=secret3 FACEBOOK_SECRET=secret4 GEDDY_SECRET=secret5

Finally, replace the secrets in your secrets.json with EJS:

{
  "passport": {
    "loginPath": "/login",
    "successRedirect": "/",
    "failureRedirect": "/login?failed=true",
    "twitter": {
      "consumerKey": "<%= process.env.TWITTER_KEY %>",
      "consumerSecret": "<%= process.env.TWITTER_SECRET %>"
    },
    "facebook": {
      "clientID": "<%= process.env.FACEBOOK_ID %>",
      "clientSecret": "<%= process.env.FACEBOOK_SECRET %>"
    }
  },
  "secret":"<%= process.env.GEDDY_SECRET %>"
}

Now remove secrets.json from your .gitignore file and push it to Heroku.

For more information about deploying and supporting Node Apps on Heroku see the Getting Started with Node.js on Heroku article.

I18n

Geddy provides internationalization support out of the box, with localized error messages in the following languages:

  • Chinese (Simplified)
  • English (US)
  • German (Germany)
  • Japanese (Japan)
  • Portuguese (Brazil)
  • Spanish (Spain)

You can set a specific locale at the request level, or a default one for your entire app. The default locale if you don't set one is American English ('en-us').

Setting a locale for your app

Set a locale in your environment.js by setting the defaultLocale property on the i18n config option:

...
, i18n: {
    defaultLocale: 'ja-jp'
  }
...

Setting locale at the controller-level

You can set the desired locale at the controller level, rather than for the entire app. Inside your controller action:

this.i18n.setLocale('zh-cn');
Loading i18n data

Geddy will load i18n data according to the loadPaths property of your app's i18n config -- an array of paths to look in for JSON files containing i18n data. By default, it contains an entry for 'config/locales' in your app.

Geddy's i18n code loads the data into data structures based on the filename. (So the data in /foo/bar/en-uk.json gets loaded into the en-uk locale, etc.)

Data format

Internationalization data is loaded from JSON files in the following format:

{
  "model.validatesPresent": "「{name}」の入力が必要です。"
, "model.validatesAbsent": "「{name}」の入力は不要です。"
, "model.validatesConfirmed": "「{name}」と「{qual}」が一致しません。"
, "model.validatesFormat": "「{name}」のフォーマットが正しくありません。"
, "model.validatesExactLength": "「{name}」は{qual}文字でなければいけません。"
, "model.validatesMinLength": "「{name}」は{min}文字以上でなければいけません。"
, "model.validatesMaxLength": "「{name}」は{max}文字以内でなければいけません。"
, "model.validatesWithFunction": "「{name}」は有効ではありません。"
}

Using i18n text

Geddy controllers have an i18n property which has a getText method (shortcut alias t). Pass this method the desired key, and an optional data-object for parametric replacement, and an optional locale. If you don't pass a locale, it will fall back to the app's defaultLocale.

This i18n object is also available as a local variable in your templates.

Use it like this in a controller (assuming a defaultLocale of 'ja-jp'):

// Returns 「HOWDY」の入力が必要です。
this.i18n.t('model.validatesPresent', {name: 'HOWDY'});

// Returns "HOWDY" ist ein Pflichtfeld.
this.i18n.t('model.validatesPresent', {name: 'HOWDY'}, 'de-de');

Templates

Geddy's view layer provides a versatile set of templating languages and helpers to get you started quickly.

The view layer supports these four templating engines:

  • EJS (.ejs)
  • Jade (.jade)
  • Mustache (.mu, .ms, .mustache)
  • Handlebars (.hbs, .handlebars)
  • Swig (.swig)

To use a certain template engine just give the view a corresponding extension listed above.

When using the Geddy CLI to generate parts of your application you can use different template languages by giving an argument to the command, here are some examples:

geddy app --mustache my_app
geddy scaffold -m user


geddy app --jade my_app
geddy scaffold -j user


geddy app --handle my_app
geddy scaffold -H user

geddy app --swig my_app
geddy scaffold --swig user

Models

Model currently implements adapters for:

  • Postgres
  • MySQL
  • SQLite
  • Riak
  • MongoDB
  • LevelDB
  • In-memory
  • Filesystem

Defining models

Model uses a pretty simple syntax for defining a model. (It should look familiar to anyone who has used an ORM like ActiveRecord, DataMapper, Django's models, or SQLAlchemy.)

var User = function () {
  this.property('login', 'string', {required: true});
  this.property('password', 'string', {required: true});
  this.property('lastName', 'string');
  this.property('firstName', 'string');

  this.validatesPresent('login');
  this.validatesFormat('login', /[a-z]+/, {message: 'Subdivisions!'});
  this.validatesLength('login', {min: 3});
  this.validatesConfirmed('password', 'confirmPassword');
  this.validatesWithFunction('password', function (s) {
      // Something that returns true or false
      return s.length > 0;
  });

  // Can define methods for instances like this
  this.someMethod = function () {
    // Do some stuff
  };
};

// Can also define them on the prototype
User.prototype.someOtherMethod = function () {
  // Do some other stuff
};

User = model.register('User', User);
Abbreviated syntax

Alternatively, you can use the defineProperties method to lay out your model's properties in one go:

var User = function () {
  this.defineProperties({
    login: {type: 'string', required: true}
  , password: {type: 'string', required: true}
  , lastName: {type: 'string'}
  , firstName: {type: 'string'}
  });
}
Datatypes

Model supports the following datatypes:

  • string
  • text
  • number
  • int
  • boolean
  • date
  • datetime
  • time
  • object

Creating instances

Creating an instance of one of these models is easy:

var params = {
  login: 'alex'
, password: 'lerxst'
, lastName: 'Lifeson'
, firstName: 'Alex'
};
var user = User.create(params);

Validations

Validations provide a nice API for making sure your data items are in a good state. When an item is "valid," it means that its data meet all the criteria you've set for it. You can specify that certain fields have to be present, have to be certain length, or meet any other specific criteria you want to set.

Here's a list of supported validation methods:

  • validatesPresent -- ensures the property exists
  • validatesAbsent -- ensures the property does not exist
  • validatesLength -- ensures the minimum, maximum, or exact length
  • validatesFormat -- validates using a passed-in regex
  • validatesConfirmed -- validates a match against another named parameter
  • validatesWithFunction -- uses an arbitrary function to validate
Common options

You can specify a custom error message for when a validation fails using the 'message' option:

var Zerb = function () {
  this.property('name', 'string');
  this.validatesLength('name', {is: 3, message: 'Try again, gotta be 3!'});
};

You can decide when you want validations to run by passing the 'on' option.

var User = function () {
  this.property('name', 'string', {required: false});
  this.property('password', 'string', {required: false});

  this.validatesLength('name', {min: 3, on: ['create', 'update']});
  this.validatesPresent('password', {on: 'create'});
  this.validatesConfirmed('password', 'confirmPassword', {on: 'create'});
};

// Name validation will pass, but password will fail
myUser = User.create({name: 'aaa'});

The default behavior is for validation on both 'create' and 'update':

  • create - validates on .create
  • update - validates on .updateProperties

You can also define custom validation scenarios other than create and update. (There is a builtin custom 'reify' scenario which is uses when instantiating items out of your datastore. This happens on the first and all query methods.)

// Force validation with the `reify` scenario, ignore the too-short name property
myUser = User.create({name: 'aa'}, {scenario: 'reify'});

// You can also specify a scenario with these methods:
// Enforce 'create' validations on a fetch -- may result in invalid instances
User.first(query, {scenario: 'create'}, cb);
// Do some special validations you need for credit-card payment
User.updateProperties(newAttrs, {scenario: 'creditCardPayment'});
Validation errors

Any validation errors show up inside an errors property on the instance, keyed by field name. Instances have an isValid method that returns a Boolean indicating whether the instance is valid.

// Leaving out the required password field
var params = {
  login: 'alex'
};
var user = User.create(params);

// Prints 'false'
console.log(user.isValid());
// Prints 'Field "password" is required'
console.log(user.errors.password);

Saving items

After creating the instance, call the save method on the instance. This method takes a callback in the familiar (err, data) format for Node.

if (user.isValid()) {
  user.save(function (err, data) {
    if (err) {
      throw err;
    }
    console.log('New item saved!');
  });
}

Updating items

Use the updateProperties method to update the values of the properties on an instance with the appropriate validations. Then call save on the instance.

user.updateProperties({
  login: 'alerxst'
});
if (user.isValid()) {
  user.save(function (err, data) {
    if (err) {
      throw err;
    }
    console.log('Item updated!');
  });
}

Lifecycle events

Both the base model 'constructors,' and model instances are EventEmitters. They emit events during the create/update/remove lifecycle of model instances. In all cases, the plain-named event is fired after the event in question, the 'before'-prefixed event, of course happens before.

The 'constructor' for a model emits the following events:

  • beforeCreate
  • create
  • beforeValidate
  • validate
  • beforeUpdateProperties
  • updateProperties
  • beforeSave (new instances, single and bulk)
  • save (new instances, single and bulk)
  • beforeUpdate (existing single instances, bulk updates)
  • update (existing single instances, bulk updates)
  • beforeRemove
  • remove

Model-item instances emit these events:

  • beforeUpdateProperties
  • updateProperties
  • beforeSave
  • save
  • beforeUpdate
  • update

Model-item instances also have the following lifecycle methods:

  • afterCreate
  • beforeValidate
  • afterValidate
  • beforeUpdateProperties
  • afterUpdateProperties
  • beforeSave
  • afterSave
  • beforeUpdate
  • afterUpdate

If these methods are defined, they will be called at the appropriate time:

var User = function () {
  this.property('name', 'string', {required: false});

  // Lowercase the name before validating
  this.beforeValidate = function () {
    // `this` will refer to the model instance
    this.name = this.name.toLowerCase();
  };
};

Associations

Model has support for associations: including hasMany/belongsTo and hasOne/belongsTo. For example, if you had a User model with a single Profile, and potentially many Accounts:

var User = function () {
  this.property('login', 'string', {required: true});
  this.property('password', 'string', {required: true});
  this.property('confirmPassword', 'string', {required: true});

  this.hasOne('Profile');
  this.hasMany('Accounts');
};

A Book model that belongs to an Author would look like this:

var Book = function () {
  this.property('title', 'string');
  this.property('description', 'text');

  this.belongsTo('Author');
};

Add the hasOne relationship by calling 'set' plus the name of the owned model in singular (in this case setProfile). Retrieve the associated item by using 'get' plus the name of the owned model in singular (in this case getProfile). Here's an example:

var user = User.create({
  login: 'asdf'
, password: 'zerb'
, confirmPassword: 'zerb'
});
user.save(function (err, data) {
  var profile;
  if (err) {
    throw err;
  }
  profile = Profile.create({});
  user.setProfile(profile);
  user.save(function (err, data) {
    if (err) {
      throw err;
    }
    user.getProfile(function (err, data) {
      if (err) {
        throw err;
      }
      console.log(profile.id ' is the same as ' + data.id);
    });
  });
});

Set up the hasMany relationship by calling 'add' plus the name of the owned model in singular (in this case addAccount). Retrieve the associated items with a call to 'get' plus the name of the owned model in plural (in this case getAccounts). An example:

var user = User.create({
  login: 'asdf'
, password: 'zerb'
, confirmPassword: 'zerb'
});
user.save(function (err, data) {
  if (err) {
    throw err;
  }
  user.addAccount(Account.create({}));
  user.addAccount(Account.create({}));
  user.save(function (err, data) {
    if (err) {
      throw err;
    }
    user.getAccounts(function (err, data) {
      if (err) {
        throw err;
      }
      console.log('This number should be 2: ' + data.length);
    });
  });
});

A belongsTo relationship is created similarly to a hasOne: by calling 'set' plus the name of the owner model in singular (in this case setAuthor). Retrieve the associated item by using 'get' plus the name of the owner model in singular (in this case getAuthor). Here's an example:

var book = Book.create({
  title: 'How to Eat an Entire Ham'
, description: 'Such a poignant book. I cried.'
});
book.save(function (err, data) {
  if (err) {
    throw err;
  }
  book.setAuthor(Author.create({
    familyName: 'Neeble'
  , givenName: 'Leonard'
  }));
  book.save(function (err, data) {
    if (err) {
      throw err;
    }
    book.getAuthor(function (err, data) {
      if (err) {
        throw err;
      }
      console.log('This name should be "Neeble": ' + data.familyName);
    });
  });
});
'Through' associations

'Through' associations allow a model to be associated with another through a third model. A good example would be a Team linked to Players through Memberships.

var Player = function () {
  this.property('familyName', 'string', {required: true});
  this.property('givenName', 'string', {required: true});
  this.property('jerseyNumber', 'string', {required: true});

  this.hasMany('Memberships');
  this.hasMany('Teams', {through: 'Memberships'});
};

var Team = function () {
  this.property('name', 'string', {required: true});

  this.hasMany('Memberships');
  this.hasMany('Players', {through: 'Memberships'});
};

var Membership = function () {
  this.belongsTo('User');
  this.belongsTo('Team');
};

The API for this is the same as with normal associations, using the set/add and get, with the appropriate association name (not the model name). For example, in the case of the Team adding Players, you'd use addPlayer and getPlayer.

Named associations

Sometimes you need mutliple associations to the same type of model (e.g., I have lots of Friends and Relatives who are all Users). You can accomplish this in Model using named associations:

var User = function () {
  this.property('familyName', 'string', {required: true});
  this.property('givenName', 'string', {required: true});

  this.hasMany('Kids', {model: 'Users'});
};

The API for this is the same as with normal associations, using the set/add and get, with the appropriate association name (not the model name). For example, in the case of Kids, you'd use addKid and getKids.

Querying

Model uses a simple API for finding and sorting items. Again, it should look familiar to anyone who has used a similar ORM for looking up records. The only wrinkle with Model is that the API is (as you might expect for a NodeJS library) asynchronous.

Methods for querying are static methods on each model constructor.

Finding a single item

Use the first method to find a single item. You can pass it an id, or a set of query parameters in the form of an object-literal. In the case of a query, it will return the first item that matches, according to whatever sort you've specified.

var user;
User.first({login: 'alerxst'}, function (err, data) {
  if (err) {
    throw err;
  }
  user = data;
  console.log('Found user');
  console.dir(user);
});
Collections of items

Use the all method to find lots of items. Pass it a set of query parameters in the form of an object-literal, where each key is a field to compare, and the value is either a simple value for comparison (equal to), or another object-literal where the key is the comparison-operator, and the value is the value to use for the comparison.

In SQL adapters, you can pass a callback to the all method if you want the results buffered and returned all at once, or steam the results using events.

Using a callback

Pass your callback function as a final argument. Callbacks use the normal (err, data) pattern. Here's an example:

var users
  , dt;

dt = new Date();
dt.setHours(dt.getHours() - 24);

// Find all the users created since yesterday
User.all({createdAt: {gt: dt}, function (err, data) {
  if (err) {
    throw err;
  }
  users = data;
  console.log('Found users');
  console.dir(users);
});
Streaming results with events (SQL adapters only)

The all method returns an EventedQueryProcessor which emits the normal 'data', 'end', and 'error' events. Each 'data' event will return a single model-item.

NOTE: Do not pass a callback to the all method if you're streaming -- passing a callback will cause the results to be buffered internally. If you need something to happen when the stream ends, use the 'end' event.

var users
  , dt
  , processor;

dt = new Date();
dt.setHours(dt.getHours() - 24);

// Find all the users created since yesterday
processor = User.all({createdAt: {gt: dt});
processor.on('data', function (user) {
  console.log('Found user');
  console.dir(user);
});
processor.on('error', function (err) {
  console.log('whoops');
  throw err;
});
processor.on('end', function () {
  console.log('No more users');
});
Examples of queries

Here are a few more examples of queries you can pass to the all method:

// Where "foo" is 'BAR' and "bar" is not null
{foo: 'BAR', bar: {ne: null}}
// Where "foo" begins with 'B'
{foo: {'like': 'B'}}
// Where foo is less than 2112, and bar is 'BAZ'
{foo: {lt: 2112}, bar: 'BAZ'}
Comparison operators

Here is the list of comparison operators currently supported:

  • eql: equal to
  • ne: not equal to
  • gt: greater than
  • lt: less than
  • gte: greater than or equal
  • lte: less than or equal
  • like: like

A simple string-value for a query parameter is the same as 'eql'. {foo: 'bar'} is the same as {foo: {eql: 'bar'}}.

For case-insensitive comparisons, use the 'nocase' option. Set it to true to affect all 'like' or equality comparisons, or use an array of specific keys you want to affect.

// Zoobies whose "foo" begin with 'b', with no case-sensitivity
Zooby.all({foo: {'like': 'b'}}, {nocase: true}, ...
// Zoobies whose "foo" begin with 'b' and "bar" is 'baz'
// The "bar" comparison will be case-sensitive, and the "foo" will not
Zooby.all({or: [{foo: {'like': 'b'}}, {bar: 'baz'}]}, {nocase: ['foo']},

More complex queries

Model supports combining queries with OR and negating queries with NOT.

To perform an 'or' query, use an object-literal with a key of 'or', and an array of query-objects to represent each set of alternative conditions:

// Where "foo" is 'BAR' OR "bar" is 'BAZ'
{or: [{foo: 'BAR'}, {bar: 'BAZ'}]}
// Where "foo" is not 'BAR' OR "bar" is null OR "baz" is less than 2112
{or: [{foo {ne: 'BAR'}}, {bar: null}, {baz: {lt: 2112}}]}

To negate a query with 'not', simply use a query-object where 'not' is the key, and the value is the set of conditions to negate:

// Where NOT ("foo" is 'BAR' and "bar" is 'BAZ')
{not: {foo: 'BAR', bar: 'BAZ'}}
// Where NOT ("foo" is 'BAZ' and "bar" is less than 1001)
{not: {foo: 'BAZ', bar: {lt: 1001}}}

These OR and NOT queries can be nested and combined:

// Where ("foo" is like 'b' OR "foo" is 'foo') and NOT "foo" is 'baz'
{or: [{foo: {'like': 'b'}}, {foo: 'foo'}], not: {foo: 'baz'}}

Options: sort, skip, limit

The all API-call for querying accepts an optional options-object after the query-conditions for doing sorting, skipping to particular records (i.e., SQL OFFSET), and limiting the number of results returned.

Sorting

Set a 'sort' in that options-object to specifiy properties to sort on, and the sort-direction for each one:

var users
// Find all the users who have ever been updated, and sort by
// creation-date, ascending, then last name, descending
User.all({updatedAt: {ne: null}}, {sort: {createdAt: 'asc', lastName: 'desc'}},
    function (err, data) {
  if (err) {
    throw err;
  }
  users = data;
  console.log('Updated users');
  console.dir(users);
});
Simplified syntax for sorting

You can use a simplified syntax for specifying the sort. The default sort-direction is ascending ('asc'), so you can specify a property to sort on (or multiple properties as an array) if you want all sorts to be ascending:

// Sort by createdAt, ascending
{sort: 'createdAt'}
// Sort by createdAt, then updatedAt, then lastName,
// then firstName -- all ascending
{sort: ['createdAt', 'updatedAt', 'lastName', 'firstName']}
Skip and limit

The 'skip' option allows you to return records beginning at a certain item number. Using 'limit' will return you only the desired number of items in your response. Using these options together allow you to implement pagination.

Remember that both these option assume you have your items sorted in the desired order. If you don't sort your items before using these options, you'll end up with a random subset instead of the items you want.

// Returns items 501-600
{skip: 500, limit: 100}

Eager loading of associations (SQL adpaters only)

You can use the 'includes' option to specify second-order associations that should be eager-loaded in a particular query (avoiding the so-called N + 1 Query Problem). This will also work for 'through' associations.

For example, with a Team that hasMany Players through Memberships, you might want to display the roster of player for every team when you display teams in a list. You could do it like so:

var opts = {
  includes: ['players']
, sort: {
    name: 'desc'
  , 'players.familyName': 'desc'
  , 'players.givenName': 'desc'
  }
};
Team.all({}, opts, function (err, data) {
  var teams;
  if (err) {
    throw err;
  }
  teams = data;
  teams.forEach(function (team) {
    console.log(team.name);
    team.players.forEach(function (player) {
      console.log(player.familyName + ', ' + player.givenName);
    });
  });
});
Sorting results

Notice that it's possible to sort the eager-loaded associations in the above query. Just pass the association-names + properties in the 'sort' property.

In the above example, the 'name' property of the sort refers to the team-names. The other two, 'players.familyName' and 'players.givenName', refer to the loaded associations. This will result in a list where the teams are initially sorted by name, and the contents of their 'players' list have the players sorted by given name, then first name.

Checking for loaded associations

The eagerly fetched association will be in a property on the top-level item with the same name as the association (e.g., Players will be in players).

If you have an item, and you're not certain whether an association is already loaded, you can check for the existence of this property before doing a per-item fetch:

if (!someTeam.players) {
  someTeam.getPlayers(function (err, data) {
    console.dir(data);
  });
}

Migrations (SQL adapters only)

Migrations are a convenient way to make changes to your SQL database schema over time, consistently and easily. They use a simply JavaScript API. This means that you don't have to write SQL by hand, and changes to your schema can be database independent.

This is an example of a migration:

var CreateUsers = function () {
  this.up = function (next) {
    var def = function (t) {
          t.column('username', 'string');
          t.column('password', 'string');
          t.column('familyName', 'string');
          t.column('givenName', 'string');
          t.column('email', 'string');
        }
      , callback = function (err, data) {
          if (err) {
            throw err;
          }
          else {
            next();
          }
        };
    this.createTable('users', def, callback);
  };

  this.down = function (next) {
    var callback = function (err, data) {
          if (err) {
            throw err;
          }
          else {
            next();
          }
        };
    this.dropTable('users', callback);
  };
};

exports.CreateUsers = CreateUsers;

This migration will create a 'users' table a number of columns of string (varchar(256)) datatype.

An 'id' column will be added implicitly, as well as timestamp columns for the 'createdAt' and 'updatedAt' properties of data items. (These will be in snake-case in the database, e.g., 'created_at'.) These properties are automatically managed by Model.

The up method makes the change (in this case, creating the table), and the down method reverses the change. The down method is used to roll back undesirable changes.

Setting up your DB to use migrations

Inside your app, run geddy jake db:init to create the 'migrations' table. You have to do this before you can use migrations.

Creating a migration

Migrations live in the db/migrations folder in your application. The name is in the form YYYYMMDDHHMMSS_my_migration_name.js. Using these timestamps with migration names allows you to run migrations in the order in which they're created, even with different developers working independently, creating migrations at overlapping times.

To create a new migration, run the generator script:

$ geddy gen migration zerp_derp
[Added] db/migrations/20130708212330_zerp_derp.js

If you open the new migration file, you'll see a blank migration file ready to be filled in:

var ZerpDerp = function () {
  this.up = function (next) {
    next();
  };

  this.down = function (next) {
    next();
  };
};

exports.ZerpDerp = ZerpDerp;
Migrations API

createTable(name<string>, definition<function>, callback<function>)

Creates a new table. The definition function is used to define the columns on the new table.

// CREATE TABLE distributors (id string PRIMARY KEY, address varchar(256),
// created_at timestamp, updated_at timestamp);
this.createTable('distributors',
    function (t) { t.column('address', 'string'); },
    function (err, data) {});

dropTable(name<string>, callback<function>)

Drops an existing table.

// DROP TABLE IF EXISTS distributors;
this.dropTable('distributors', function (err, data) {});

addColumn(table<string>, column<string>, datatype<string>, callback<function>)

Adds a column to an existing table.

// ALTER TABLE distributors ADD COLUMN address varchar(30);
this.addColumn('distributors', 'address', 'string',
    function (err, data) {});

removeColumn(table<string>, column<string>, callback<function>)

Removes a column from an existing table.

// ALTER TABLE distributors DROP COLUMN address;
this.removeColumn('distributors', 'address',
    function (err, data) {});

changeColumn(table<string>, column<string>, datatype<string>, callback<function>)

Changes a column on an existing table from one datatype to another.

// ALTER TABLE distributors ALTER COLUMN address TYPE text;
this.changeColumn('distributors', 'address', 'text',
    function (err, data) {});

renameColumn(table<string>, column<string>, newColumn<string>, callback<function>)

Renames a column on an existing table.

// ALTER TABLE distributors RENAME COLUMN address TO city;
this.renameColumn('distributors', 'address', 'city',
    function (err, data) {});
Migrations for scaffolds

Using Geddy's scaffold-generators will also create the appropriate migration file for you.

For example, with the following generator command:

$ geddy gen scaffold frang asdf:string qwer:int

You'll end up with the following migration to run to create the corresponding table for your model:

var CreateFrangs = function () {
  this.up = function (next) {
    var def = function (t) {
          t.column('asdf', 'string');
          t.column('qwer', 'int');
        }
      , callback = function (err, data) {
          if (err) {
            throw err;
          }
          else {
            next();
          }
        };
    this.createTable('frang', def, callback);
  };

  this.down = function (next) {
    var callback = function (err, data) {
          if (err) {
            throw err;
          }
          else {
            next();
          }
        };
    this.dropTable('frang', callback);
  };
};

exports.CreateFrangs = CreateFrangs;
Migrations FAQ

Q: If I'm using Geddy-Passport for auth, how do I create the migrations for it?

A: People running the auth generator will now get the migrations installed as well, but if you've previously installed the auth code, you can grab the migrations from here: https://github.com/mde/geddy-passport/tree/master/db/migrations. They will create 'users' and 'passport' tables with the correct associations columns.

Q: How do I handle associations with my migrations?

A: Right now there is not great support for migrations in the CLI generators. You'll have to add the appropriate database-column entries into your migrations manually before you run them. Essentially, four steps: 1. Run the CLI scaffold generator to create your model-definition file, and your migration file. 2. Add the association (e.g., this.hasMany) into your model-definition file. 3. Add the appropriate database-column entry into your migration file. 4. Run the migration to create your database table.

Here's an example from geddy-passport, with a hasMany and a belongsTo. We'll start with a User model:

var User = function () {
  this.defineProperties({
    username: {type: 'string', required: true},
    password: {type: 'string', required: true},
    familyName: {type: 'string', required: true},
    givenName: {type: 'string', required: true},
    email: {type: 'string', required: true}
  });

  this.validatesLength('username', {min: 3});
  this.validatesLength('password', {min: 8});
  this.validatesConfirmed('password', 'confirmPassword');

  this.hasMany('Passports');
};

exports.User = User;

A User model has many Passports, and a Passport belongs to a User:

var Passport = function () {
  this.defineProperties({
    authType: {type: 'string'},
    key: {type: 'string'}
  });

  this.belongsTo('User');
};

exports.Passport = Passport;

This association will need a 'userId' property (a 'user_id' column) on the Passport. Here's the migration:

var CreatePassports = function () {
  this.up = function (next) {
    var def = function (t) {
          var datatype = geddy.model.autoIncrementId ? 'int' : 'string';
          t.column('authType', 'string');
          t.column('key', 'string');
          t.column('userId', datatype); // belongsTo User
        }
      , callback = function (err, data) {
          if (err) {
            throw err;
          }
          else {
            next();
          }
        };
    this.createTable('passports', def, callback);
  };

  this.down = function (next) {
    var callback = function (err, data) {
          if (err) {
            throw err;
          }
          else {
            next();
          }
        };
    this.dropTable('passports', callback);
  };
};

exports.CreatePassports = CreatePassports;

If you know what type of ids you're using, then you can skip the check for the 'userId' datatype -- just make it the same as the 'id' column on the owner object.

Q: What happens if I change the associations, do I then re-run the migrations?

A: Right now, you'll have to manage the association columns manually with addColumn and removeColumn. Better support for assocations in the CLI and in migrations is coming in the next version of Geddy.

Q: How can I take an older Geddy app that has all its models and turn them into a migrations-based thing?

A: The easiest thing to do would be to create a separate Geddy app, and use the generator scripts to create the migrations you want. Run those migrations in an empty DB, then import your data into that database using whatever tools your DB provides (e.g., pg_dump).

Architecture

Geddy is built on the same MVC principles that many popular frameworks are based on. Every Geddy app has its models, controllers, and views as well as config files and routes.


structure

├── app
│   ├── controllers
│   │   ├── application.js
│   │   └── main.js
│   ├── helpers
│   ├── models
│   └── views
│       ├── layouts
│       │   └── application.html.ejs
│       └── main
│           └── index.html.ejs
├── config
    ├── development.js
    ├── environment.js
    ├── init.js
    ├── production.js
    └── router.js
├── lib
├── log
├── node_modules
└── public

config

geddy.config

Geddy has built in configuration management. Global config options should go in your 'config/environments.js' file. Likewise, your production and development config options should go in their respective files

If you want to start up your app in a specific environment, use the -e option:

$ geddy -e production

logger

geddy.log[level]

Geddy automatically logs requests to an access log, and you can log anything you'd like to stdout or a file. It supports 9 different log levels from debug to emergency.

levels
  • access: outputs to the access log and stdout
  • debug: debug level logging
  • info: info level logging
  • notice: notice level logging
  • warning: warning level logging
  • error: error level logging, prints to stdout and stderr
  • critical: critical level logging
  • alert: alert level logging
  • emergency: emergency level logging
examples
geddy.log.debug('someting to debug')
// prints `something to debug` to the console


geddy.log.error('something went wrong')
// prints 'something went wrong' to stderr and the console

SSL

Geddy supports HTTPS via SSL/TLS or SPDY. To enable it, add the following to your configuration file. The configuration requires at least the key and cert options, but all of the options that are available for the https and spdy modules are supported.

To add SSL/TLS simply add the following lines to your config file:

// ...
, ssl: {
    key: 'config/key.pem',
    cert: 'config/cert.pem'
  }
// ...

To add support for SPDY add the follwing:

// ...
, spdy: {
    key: 'config/key.pem',
    cert: 'config/cert.pem'
  }
// ...

If you notice the following configuration options use a file name rather than the files contents, like the standard options for https and spdy, this is because Geddy handles that for you. If you also have to include a list of certificate authorities, use the ca option, but instead of giving it an array of the file names for each authority, you can also include a single file name pointing to a bundled certificate.

Here is a decent step-by-step guide to creating a self-signed SSL certificate: http://www.akadia.com/services/ssh_test_certificate.html