Giter VIP home page Giter VIP logo

resourceful's Introduction

Framework components for node.js and the browser

Example HTTP Server:

var flatiron = require('flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.http);

app.router.get('/', function () {
  this.res.writeHead(200, { 'Content-Type': 'text/plain' });
  this.res.end('Hello world!\n');
});

app.start(8080);

Example HTTPS Server:

var flatiron = require('flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.http, {
  https: {
    cert: 'path/to/cert.pem',
    key: 'path/to/key.pem',
    ca: 'path/to/ca.pem'
  }
});

app.router.get('/', function () {
  this.res.writeHead(200, { 'Content-Type': 'text/plain' });
  this.res.end('Hello world!\n');
});

app.start(8080);

Example CLI Application:

// example.js

var flatiron = require('flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli, {
  dir: __dirname,
  usage: [
    'This is a basic flatiron cli application example!',
    '',
    'hello - say hello to somebody.'
  ]
});

app.cmd('hello', function () {
  app.prompt.get('name', function (err, result) {
    app.log.info('hello '+result.name+'!');
  })
})

app.start();

Run It:

% node example.js hello
prompt: name: world
info:   hello world!

Installation

Installing NPM (Node Package Manager)

  curl http://npmjs.org/install.sh | sh

Installing Flatiron

  [sudo] npm install flatiron

Installing Union (Required for flatiron.plugins.http)

  npm install union

Usage:

Start With flatiron.app:

flatiron.app is a broadway injection container. To be brief, what it does is allow plugins to modify the app object directly:

var flatiron = require('flatiron'),
    app = flatiron.app;

var hello = {
  attach: function (options) {
    this.hello = options.message || 'Why hello!';
  }
};

app.use(hello, {
  message: "Hi! How are you?"
});

// Will print, "Hi! How are you?"
console.log(app.hello);

Virtually all additional functionality in flatiron comes from broadway plugins, such as flatiron.plugins.http and flatiron.plugins.cli.

app.config

flatiron.app comes with a config plugin pre-loaded, which adds configuration management courtesy nconf. app.config has the same api as the nconf object.

The literal store is configured by default. If you want to use different stores you can easily attach them to the app.config instance.

// add the `env` store to the config
app.config.use('env');

// add the `file` store the the config
app.config.use('file', { file: 'path/to/config.json' });

// or using an alternate syntax
app.config.env().file({ file: 'path/to/config.json' });

// and removing stores
app.config.remove('literal');

app.log

flatiron.app will also load a log plugin during the init phase, which attaches a winston container to app.log. This logger is configured by combining the app.options.log property with the configuration retrieved from app.config.get('log').

Create An HTTP Server with flatiron.plugins.http(options):

This plugin adds http serving functionality to your flatiron app by attaching the following properties and methods:

Define Routes with app.router:

This is a director router configured to route http requests after the middlewares in app.http.before are applied. Example routes include:

// GET /
app.router.get('/', function () {
  this.res.writeHead(200, { 'Content-Type': 'text/plain' });
  this.res.end('Hello world!\n');
});

// POST to /
app.router.post('/', function () {
  this.res.writeHead(200, { 'Content-Type': 'text/plain' });
  this.res.write('Hey, you posted some cool data!\n');
  this.res.end(util.inspect(this.req.body, true, 2, true) + '\n');
});

// Parameterized routes
app.router.get('/sandwich/:type', function (type) {
  if (~['bacon', 'burger'].indexOf(type)) {
    this.res.writeHead(200, { 'Content-Type': 'text/plain' });
    this.res.end('Serving ' + type + ' sandwich!\n');
  }
  else {
    this.res.writeHead(404, { 'Content-Type': 'text/plain' });
    this.res.end('No such sandwich, sorry!\n');
  }
});

app.router can also route against regular expressions and more! To learn more about director's advanced functionality, visit director's project page.

Access The Server with app.server:

This is a union middleware kernel.

Modify the Server Options with app.http:

This object contains options that are passed to the union server, including app.http.before, app.http.after and app.http.headers.

These properties may be set by passing them through as options:

app.use(flatiron.plugins.http, {
  before: [],
  after: []
});

You can read more about these options on the union project page.

Start The Server with app.start(port, <host>, <callback(err)>)

This method will both call app.init (which will call any asynchronous initialization steps on loaded plugins) and start the http server with the given arguments. For example, the following will start your flatiron http server on port 8080:

app.start(8080);

Create a CLI Application with flatiron.plugins.cli(options)

This plugin turns your app into a cli application framework. For example, [jitsu] (https://github.com/nodejitsu/jitsu) uses flatiron and the cli plugin.

Valid options include:

{
  "argvOptions": {}, // A configuration hash passed to the cli argv parser.
  "usage": [ "foo", "bar" ], // A message to show for cli usage. Joins arrays with `\n`.
  "dir": require('path').join(__dirname, 'lib', 'commands'), // A directory with commands to lazy-load
  "notFoundUsage": false // Disable help messages when command not found
}

Add lazy-loaded CLI commands with options.dir and app.commands:

Flatiron CLI will automatically lazy-load modules defining commands in the directory specified by options.dir. For example:

// example2.js
var path = require('path'),
    flatiron = require('./lib/flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli, {
  dir: path.join(__dirname, 'cmds')
});

app.start();
// cmd/highfive.js
var highfive = module.exports = function highfive (person, cb) {
  this.log.info('High five to ' + person + '!');
  cb(null);
};

In the command, you expose a function of arguments and a callback. this is set to app, and the routing is taken care of automatically.

Here it is in action:

% node example2.js highfive Flatiron 
info:   High five to Flatiron!

You can also define these commands by adding them directly to app.commands yourself:

// example2b.js
var flatiron = require('./lib/flatiron'),
    app = flatiron.app;

var path = require('path'),
    flatiron = require('./lib/flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli);

app.commands.highfive = function (person, cb) {
  this.log.info('High five to ' + person + '!');
  cb(null);
};

app.start();
% node example2b.js highfive Flatiron 
info:   High five to Flatiron!

Callback will always be the last argument provided to a function assigned to command

app.commands.highfive = function (person, cb) {
  this.log.info('High five to ' + person + '!');
  console.log(arguments);
}
% node example2b.js highfive Flatiron lol haha
info:    High five to Flatiron!
{
  '0': 'Flatiron',
  '1': 'lol',
  '2': 'haha',
  '3': [Function]
}

Define Ad-Hoc Commands With app.cmd(path, handler):

This adds the cli routing path path to the app's CLI router, using the director route handler handler, aliasing app.router.on. cmd routes are defined the same way as http routes, except that it uses (a space) for a delimiter instead of /.

For example:

// example.js
var flatiron = require('./lib/flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli, {
  usage: [
    'usage: node test.js hello <person>',
    '',
    '  This will print "hello <person>"'
  ]
});

app.cmd('hello :person', function (person) {
  app.log.info('hello ' + person + '!');
});

app.start()

When you run this program correctly, it will say hello:

% node example.js hello person
info:   hello person!

If not, you get a friendly usage message:

% node test.js hello
help:   usage: node test.js hello <person>
help:
help:     This will print "hello <person>"

Check CLI Arguments with app.argv:

Once your app is started, app.argv will contain the optimist-parsed argv options hash, ready to go!

Here's an example:

// example3.js
var flatiron = require('./lib/flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli);

app.start();

app.log.info(JSON.stringify(app.argv));

This prints:

% node example3.js
info:    {"_":[], "$0": "node ./example3.js"}

Awesome!

Add a Default Help Command with options.usage:

When attaching the CLI plugin, just specify options.usage to get a friendly default message for when there aren't any matching routes:

// example4.js
var flatiron = require('./lib/flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli, {
  usage: [
    'Welcome to my app!',
    'Your command didn\'t do anything.',
    'This is expected.'
  ]
});

app.start();
% node example4.js 
help:   Welcome to my app!
help:   Your command didn't do anything.
help:   This is expected.

Start The Application with app.start(callback):

As seen in these examples, starting your app is as easy as app.start! this method takes a callback, which is called when an app.command completes. Here's a complete example demonstrating this behavior and how it integrates with options.usage:

// example5.js
var path = require('path'),
    flatiron = require('./lib/flatiron'),
    app = flatiron.app;

app.use(flatiron.plugins.cli, {
  usage: [
    '`node example5.js error`: Throws an error.',
    '`node example5.js friendly`: Does not throw an error.'
  ]
});

app.commands.error = function (cb) {
  cb(new Error('I\'m an error!'));
};

app.commands.friendly = function (cb) {
  cb(null);
}

app.start(function (err) {
  if (err) {
    app.log.error(err.message || 'You didn\'t call any commands!');
    app.log.warn('NOT OK.');
    return process.exit(1);
  }
  app.log.info('OK.');
});

Here's how our app behaves:

% node example5.js friendly
info:   OK.

% node example5.js error
error:  I'm an error!
warn:   NOT OK.

% node example5.js
help:   `node example2b.js error`: Throws an error.
help:   `node example2b.js friendly`: Does not throw an error.
error:  You didn't call any commands!
warn:   NOT OK.

Read More About Flatiron!

Articles

Sub-Projects

Tests

Tests are written in vows:

  $ npm test

License: MIT

resourceful's People

Contributors

bmeck avatar chbm avatar chjj avatar coderarity avatar colinf avatar coreyjewett avatar ericchaves avatar fb55 avatar fent avatar gradus avatar indexzero avatar indutny avatar jcrugzz avatar jfhbrook avatar jkroso avatar juanghurtado avatar marak avatar mmalecki avatar nrw avatar pksunkara avatar richmarr avatar rrichardson avatar slaskis avatar ssevertson avatar tswast avatar xjamundx avatar yawnt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

resourceful's Issues

Resource.get(id, callback(err, obj)) never runs callback for non-existent ID

If Resource.get() is called, then the callback supplied is never fired for a non-existent ID. This is different then Resource.find() where an empty obj is returned if no matching objects could be found. I've included most of the relevant code, not sure how helpful this is.

var BlogPost = Resourceful.define('blogpost', function () {

this.use('couchdb', {
    uri: 'couchdb://zeus.iriscouch.com/blogposts'
    //uri: 'couchdb://127.0.0.1:5984/blogposts'
});

this.string('title');
this.string('subTitle');
this.string('body');
this.array('tags');
this.timestamps();

});

exports.getBP = function(id) {
var self = this;
if (arguments.length < 2) {
    BlogPost.all(function(err, docs) {
        if (err) throw err;
        self.res.end(JSON.stringify(docs) + '\n');
        winston.debug(docs);
        return docs;
    });
}
else {
    console.log('ID: ' + id);
    BlogPost.get(id, function(err, post) {
                    // None of this is ever ran
        if (err) throw err;
        self.res.end(JSON.stringify(post) + '\n');
        winston.debug(post);
        return post;
    });
}
};

Sidenote: If anyone has any good resources where I can learn to assemble little node packages with simple bug-cases and tests, I would be grateful.

Prefix properties

  this.string('_id').unique(true).prefix('user/');

Would compile down to:

  this.string('_id').unique(true).sanitize(function (val) {
    return !new RegExp('^user/(.*)').test(val) 
      ? 'user/' + val
      : val;
  });

Implement purging of memory database

Something like

resourceful.purge = function () {
  Object.keys(this.resources).forEach(function (key) {
    var resource = resourceful.resources[key];
    resource.connection.store = {};
  });
};

This will kill the attached memory store for every resource.

This is only useful for unit testing when you want to make sure your memory store is clean for your parallel unit tests.

It would be stupid to use this in production.

I'm not sure whether is "useful enough" to add to resourceful.

MongoDB support?

i was looking at resourceful and it seems pretty awesome..
i was wondering if there was any change to add a MongoDB support in addition to CouchDB
thx

Missing test coverage for schema validation and type validation on Create and Update

We need to add test coverage for basic type validation of Resource.create and Resource.update.

We should not test every specific type of validation in Resourceful, only the integration of the Revalidator validation API.

Here is an example of the type of coverage that is missing:

var resourceful = require('../lib/resourceful');

var User = resourceful.define('user', function () {
  //
  // Specify a storage engine
  //
  this.use('memory');
  this.string('name');

  //
  // Specify some properties with validation
  //
  this.string('email', { format: 'email', required: true })
});

var user = { email: 'INVALID_EMAIL@123' };
User.create(user, function (err, result) {
  /*
    [ { attribute: 'format',
        property: 'email',
        expected: 'email',
        actual: 'INVALID_EMAIL@123',
        message: 'is not a valid email' } ]
  */
  console.log(err, result)
});

var user = { email: '[email protected]' };
User.create(user, function (err, result) {
  /*
    null { _id: '1',
      name: undefined,
      email: '[email protected]',
      ctime: 1339644429540,
      mtime: 1339644429540,
      atime: undefined,
      resource: 'User'
  */
  console.log(err, result)
});

API Defect / Missing test coverage for Engines

The couchdb engine currently has an issue with multiple resource types existing in the same database. Since couchdb requires a unique key for every document in a database, the current behavior of using the resource _id as 1-1 to the couch _id is causing conflicts.

Edit: It looks like this affects all engines, not just couchdb.

I'm attempting to work out a solution now. Will update ticket accordingly.

Reproducible Case:

var resourceful = require('../lib/resourceful');
resourceful.use('couchdb', { database: "test2" });

//
// First, create two resources: Author and Article
//
var Author = resourceful.define('author');
Author.string('name');

var Article = resourceful.define('article');
Article.string('name');


//
// Second, create an Author called "bob" and an Article named "bob"
//
Author.create({
  _id: 'bob',
  name: "first"
}, function(err, author){
  Article.create({
    _id: 'bob',
    name: "second"
  }, function(err, article){
    //
    // Outputs: { error: 'conflict', reason: 'Document update conflict.' }
    //
    //
    // This is due to the fact that couchdb requires a unique _id for every document in a database
    //
    console.log(err);
  });
});

Hierarchy Relationship isn't working using couchdb engine

Trying your example using memory engine:

[ { _id: 'RomeoAndJuliet',
title: 'Romeo and Juliet',
author_id: 'WilliamShakespeare',
resource: 'Book' },
{ _id: 'Hamlet',
title: 'Hamlet',
author_id: 'WilliamShakespeare',
resource: 'Book' } ]
[ { _id: 'RomeoAndJuliet',
title: 'Romeo and Juliet',
author_id: 'WilliamShakespeare',
resource: 'Book' },
{ _id: 'Hamlet',
title: 'Hamlet',
author_id: 'WilliamShakespeare',
resource: 'Book' } ]`

And trying it using couchdb:

[]
[]

Edit: It seems that *_ids array in the document isn't updated when createChild is called

No function for modification of established properties

Documentation states:

If you want to access and modify an already defined property, you can do it this way:

Creature.properties['legs'].maximum(6);

Not sure if this is a documentation typo: "maximum = 6";

OR an unimplemented feature.

child.parent() not working properly

This may be related to being a memory store, I am unsure, but the following does not work.

resourceful.use('memory');
Author = resourceful.define('author', function () {
  this.property('name');
});
Book = resourceful.define('book', function () {
  this.property('title');
  this.parent('Author');
});
shakespeare = new Author({
  _id:'WilliamShakespeare',
  name:'William Shakespeare'
});
romeoAndJuliet = shakespeare.createBook({
  _id:'RomeoAndJuliet',
  title:'Romeo and Juliet'
});
hamlet = shakespeare.createBook({
  _id:'Hamlet',
  title:'Hamlet'
},function(err,hamlet){
  console.error('hamlet created', hamlet);
  hamlet.author(function(){
    console.error('author of hamlet result:',arguments)
  })
});
Book.byAuthor('WilliamShakespeare', function(){console.error(arguments) });

feature: browser support?

any plans to make resourceful available client-side? would love to use this in my browser code for creating/managing models and communicating with couchdb.

validation assert doesn't work

This code returns true on validation, should return alway false.

var rf = require('resourceful')

var Person = rf.define('person', function(){
  this.use('couchdb')
  this.number('age', {
    assert: function(val){
      return false
    }
  })
})

var peter = new Person()
peter.age = 17
var validation = peter.validate()
console.log(validation.valid)

When I can't use 'assert', how do I check values on complex conditions?

Broken `find` in couchdb

var resourceful = require('../lib/resourceful');

var Creature = resourceful.define('creature', function () {
  this.use('couchdb', {database: 'flatiron'});
  this.property('diet');
  this.property('vertebrate', Boolean);
  this.property('belly', Array);

  this.prototype.feed = function (food) {
    this.belly.push(food);
  };
});

Creature.find({diet: 'carnivor'}, function (err, c) {
  if (err) throw err;
  console.log(c);
});

Running the above code gives the following error


node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
TypeError: Cannot call method 'apply' of undefined
    at /home/pkumar/Coding/git/flatiron/resourceful/lib/resourceful/resource.js:160:29
    at Function.runBeforeHooks (/home/pkumar/Coding/git/flatiron/resourceful/lib/resourceful/resource.js:55:12)
    at Function._request (/home/pkumar/Coding/git/flatiron/resourceful/lib/resourceful/resource.js:106:8)
    at Function.find (/home/pkumar/Coding/git/flatiron/resourceful/lib/resourceful/resource.js:262:15)
    at Object.<anonymous> (/home/pkumar/Coding/git/flatiron/resourceful/examples/creature-function.js:14:10)
    at Module._compile (module.js:402:26)
    at Object..js (module.js:408:10)
    at Module.load (module.js:334:31)
    at Function._load (module.js:293:12)
    at Array.<anonymous> (module.js:421:10)

common.typeOf is not properly detecting regexp

Not sure if this is a bug in Resourceful, in google's V8 or else, but when I define a pattern for a string property an TypeError is raised by common.typeOf.

to reproduce the code just do something like:

var User = resourceful.define('User');
User.string('username').pattern(/^[a-zA-Z0-9]{3,15}$/);

Debugging the code it seems that the common.typeOf (common.js line 55) uses:

  var derived = typeof(value);

which outputs as "object" instead of "function".

Can anyone confirm this please?

Thanks in advance,

Eric

Documentation on engine interfaces?

Hi there. I'm just starting to write a MongoDB engine (don't worry; I'll issue a pull request), and I have some questions...

Have you folks started writing documentation on the interfaces that an engine is supposed to expose? The CouchDB and Memory engines are somewhat self-documenting, but I have questions about the nuances of put(), save(), and update() -- coming from a MongoDB point-of-view, some of these seem redundant, and I'm wondering if one can be a direct reference to another, or if there are enough subtle differences that they should be implemented differently? (E.g., what should happen when update() tries to update a record that isn't there; should it fail, or should it upsert?)

There doesn't seem to be a post() interface defined in either of the reference engines; for now, it seems like you have to go Resource.create() and allow it to create and assign a unique ID for you. This means that the example code (the one where you create a wolf and feed it a squirrel) using the new keyword and saving later won't work... would it be sensible to implement a post() interface which does not require an ID but assigns the ID to the object later? If so, I think the callback in Resource.prototype.save() should also check to see if a new ID has been returned and assign it to _id, just as with Resource.create(). (Note: I'm not sure how new records are saved to the database without IDs, because none of the engine's interface permit a null or undefined ID.)

In what situations is update() actually used; can it be used to send just a few key/value pairs to the engine and have it update just those values on the object with the given ID?

mmmmmmmmm... I think those are all my questions for now. Thanks for an awesome library; looking forward to using it!

feature: generated default properties

JSON Schema supports a "default" property, however, sometimes default properties should be generated programatically. Some examples include: time of creation, salt for hashing, expiration dates / deadlines / ttl / timeouts, etc. We need a means to provide initialization of these properties in an easy manner.

Right now there are hacky ways to do this by using undocumented internal methods. However, providing a stable public API is needed.

REST API engine

Implement an engine which would be able to talk to REST APIs.

Hierarchy relationships misnomer?

Was just reading wiki on Hierarchy relationships. Cool.

I object to the method name #parent(). It is not accurately descriptive of the relation. In the Shakespeare example given the method should be #belongs_to (keep reading, this isn't right either). As in:

Book = resourceful.define('book', function () {
  this.property('title');
  this.belongs_to('Author');
});

There is a #has_many implicit in the current implementation that perhaps should be explicit:

Author = resourceful.define('author', function () {
  this.property('name');
  this.has_many(Book);
});

Finally, since I'm picking on this example, I'm not sure #belongs_to is appropriate because a book can have multiple Authors.

I think this feature should be pulled (or at least renamed from #parent to #has_many) until some of the magic is stripped out and it follows relation modeling and terminology. Ruby on Rails' ActiveRecord might be a good case study for this.

How to add validations for generated properties

Hi guys,

I've created a generated property in my resource to encrypt password. basically what I'm doing is:

User.string('password').required(true);
User.property('password', 'string', {
  get:function () {
        console.log(this.properties.password);
    return this.properties.password;
  },
  set:function (value) {
        if (typeof value !== 'undefined' && value !== null){
            this.properties.password_salt = bcrypt.genSaltSync(12);
            value = bcrypt.hashSync(value, this.password_salt);
        }
    return this.properties.password = value;
  }
});

This way I can create a user calling User.create({username: 'user', password: '12345') and have it encrypted by immediately. What I'd like now is have this property required and also apply some schema validations like minimumLength. is this possible?

The required(true) as above does not seem to have any effect.

Thanks for any help,

Eric

A single connection for the entire data layer?

Hi,

I see for each model I define I need to open a new connection (the use method ends doing this).

Is there any way of using A SINGLE connection for each engine? I dont think is really efficient / good having a different connection for each model in the application!

ps. I'm a very beginner to these technologies so apologies if this is not a real issue or missunderstood by me.

Error when calling Couch views

It's likely this is user error, but I haven't figured it out yet. I'm getting this error when trying to access a CouchDB view I created.

TypeError: Object #<Object> has no method 'view'
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/engines/couchdb/view.js:87:14
    at EventEmitter.<anonymous> (/Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/resource.js:498:17)
    at EventEmitter.g (events.js:143:14)
    at EventEmitter.emit (events.js:64:17)
    at Function.emit (/Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/core.js:159:33)
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/resource.js:513:10
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/lib/resourceful/engines/couchdb/index.js:167:9
    at /Users/richardmarr/Projects/web-app/node_modules/resourceful/node_modules/cradle/lib/cradle.js:415:29
    at IncomingMessage.<anonymous> (/Users/richardmarr/Projects/web-app/node_modules/resourceful/node_modules/cradle/lib/cradle.js:253:72)
    at IncomingMessage.emit (events.js:81:20)

The view creation code looks like this:

User.filter( 'byTwitterId', {map:function(doc){
    if ( doc.resource == "User" && doc.auth && doc.auth.twitter ) emit( doc.auth.twitter.id, doc);
}});

...and the view lookup code looks like this:

User.byTwitterId({key:id},function(){})

Error when saving Creature in example code

Just pulled the latest changes to upgrade to 0.1.3 and bumped into the following error when trying to save the document.

TypeError: Cannot call method 'apply' of undefined
    at /Users/richardmarr/local/node/lib/node_modules/resourceful/lib/resourceful/resource.js:159:29
    at loop (/Users/richardmarr/local/node/lib/node_modules/resourceful/lib/resourceful/resource.js:51:9)
    at Function.runBeforeHooks (/Users/richardmarr/local/node/lib/node_modules/resourceful/lib/resourceful/resource.js:53:6)
    at Function._request (/Users/richardmarr/local/node/lib/node_modules/resourceful/lib/resourceful/resource.js:105:8)
    at Function.save (/Users/richardmarr/local/node/lib/node_modules/resourceful/lib/resourceful/resource.js:225:15)
    at Factory.save (/Users/richardmarr/local/node/lib/node_modules/resourceful/lib/resourceful/resource.js:533:22)
    at Object.<anonymous> (/Users/richardmarr/Projects/celeri.ac/web-app/dbtest.js:47:6)
    at Module._compile (module.js:432:26)
    at Object..js (module.js:450:10)
    at Module.load (module.js:351:31)

Code used:

var resourceful = require('resourceful');

resourceful.use('couchelastic',{
    host:'localhost',
        port:5984,
    cache:false,
    database:'test'
});
var Creature = resourceful.define('creature');      
Creature.property('diet'); // Defaults to String
Creature.property('vertebrate', Boolean)
Creature.property('belly', Array);

Creature.prototype.feed = function (food) {
    this.belly.push(food);
};

var wolf = new(Creature)({
    diet:      'carnivor',
    vertebrate: true
});
wolf.feed('squirrel');
wolf.save()

Changing this line (resource.js:159)...

that.connection.[method].apply(that.connection, args);

...to the line below seems to fix the problem, but I'm guessing it's not what was intended:

that.connection.connection[method].apply(that.connection.connection, args);

More than one engine for a single Resource

CouchDB and Elasticsearch are a great complement for each other, but to work best Elasticsearch can require knowledge of document types and specific behaviour for special fields in order to create the mappings and rivers required. It'd be great if you could manage both engines through the same Resource objects.

So for a single Resource, e.g. Creature you might want to:

  • Create document in CouchDB
  • Create changes filter in CouchDB
  • Create mapping for the Creature data in ES so that its fields are indexed according to specific rules
  • Create a river on the ES cluster to read the filtered changes stream from CouchDB

The problem is that currently Resourceful only allows one engine for a single Resource.

Not sure whether you guys see this as something best addressed by Resourceful core itself, by the client application, or by creating a hybrid Couch/ES engine. If there's consensus and nobody else wants to tackle it then I'm happy to have a crack.

Greater clarity on using CouchDB engine.

I tried using the CouchDB backend and ran into a few protocol problems. Basically, trying ...

var Article = resourceful.define('article', function () {
    this.use('couchdb', {
        uri: 'http://127.0.0.1:5984/articles',
        database: 'articles'
    });
});

... bombed with a node TypeError: undefined is not a function error. Trying to use the Http module???

Also, trying ...

uri: '127.0.0.1'

... or ...

uri: '127.0.0.1:5984'

... failed with { error: 'method_not_allowed', reason: 'Only GET,HEAD allowed' }

Only variations of the full uri...

uri: 'couchdb://127.0.0.1:5984/articles'

... worked. Likewise, the database property mentioned in the documentation seemed to have no effect at all ...

uri: 'couchdb://127.0.0.1:5984',
database: 'articles'

Perhaps a simple example in the documentation, or a pointer to the cradle documentation would be useful?

feature: attachment as stream

A feature that would really make this killer is to be able to pipe a stream to the a resourceful object and have it handled as an attachment.

something like this:

var wolfy = new Creature('Mr. Wolfy")
fs.readFile(file).pipe(wolfy.writeAttachment(attachmentName))

and then later:

Creature.get('Mr. Wolfy").readAttachment(attachmentName).pipe(fs.writeFile(file))

I'm not saying it needs to be exactly like that but you get the idea.
there will need to be some buffering in there some where, but that should be handled transparently.

to the best of my knowledge there is not a couch client that does this yet,
so this would be a new thing. (also you might be able to reproduce that with GridFS in mongo)

Sample code does not work

When I follow (or copy/paste) the example code from the readme file, I get an error that the 'belly' of the wolf is undefined:

wolf.feed('squirrel');
TypeError: Cannot call method 'push' of undefined
at Factory.feed ([object Context]:2:12)
at [object Context]:1:6
at Interface. (repl.js:171:22)
at Interface.emit (events.js:64:17)
at Interface._onLine (readline.js:153:10)
at Interface._line (readline.js:408:8)
at Interface._ttyWrite (readline.js:585:14)
at ReadStream. (readline.js:73:12)
at ReadStream.emit (events.js:81:20)
at ReadStream._emitKey (tty_posix.js:307:10)

Is there a line missing in the example code?

createParent() does not appear to update parent object with new child id

Only tested this with couch, not the memory store.

So -- having defined two resources, "parent" and "child", and having called Parent.child('child') and/or Child.parent('parent'), I'm finding that when I then call Parent.createChild(), the child object in the database is saved with the parent object's id, but the parent object is not updated with the new child object's id.

Given that I haven't looked at any of this code until an hour or two ago, I wonder if someone would scan over my proposed fix before I issue a formal pull request just to make sure that I'm on the same page and am not completely missing your intent. Here's the code:

Beginning on line 397 of resource.js...

//
// Parent.createChild(id, child, callback)
//
factory['create' + rstringc] = function (id, child, callback) {
  var key = factory.resource.toLowerCase() + '_id';
  var that = this
  var updateParent = function(err, savedChild) {
    factory.get(id, function(err, parentObject) {
      if (err) {
        callback(err)
      } else if (parentObject) {
        // add the new child id onto the parent object
        parentObject[rstring + '_ids'].push(savedChild._id || savedChild.id)
        parentObject.save(function(err) {
          callback.call(this, err, savedChild)
        })
      } else {
        throw new Error("could not find parent object")
      }
    })
  }
  if (child instanceof rfactory) {
    child[key] = id;
    child.save(updateParent);
  } else {
    var inheritance = {};
    inheritance[key] = id;
    child = resourceful.mixin({}, child, inheritance);
    rfactory.create(child, updateParent);
  }
};

feature: update handlers

adding update handlers to a resource would be very useful as they allow atomic operations on documents, consider this example.

you do toggle a state on a document, ie, rotate a list or flip a boolean.

in without an update handler, you must request the document, change it, then save it again - if the doc has changed in that time, you will need to request the document again -- there are no guarantees.

however, with an update handler, you can do an atomic update on the database, so there is no problem here.

it would be great to be able to define them on the resource and then use them by calling

resource.update(args, query, function (err, obj) {

})

Expose a constructor

Right now resourceful is a singleton. We should expose a Resourceful constructor (but require('resourceful') should still return a singleton).

`update` not returning resource `_id`

I have the following in my couchdb

{
   "_id": "wolf",
   "_rev": "1-7714a2a782c34ab672f44c841478ec9a",
   "diet": "herbivore",
   "vertebrate": true,
   "belly": [],
   "resource": "Creature"
}

and if I execute this code

var resourceful = require('../lib/resourceful');

var Creature = resourceful.define('creature', function () {
  this.use('couchdb', {database: 'flatiron'});
  this.property('diet');
  this.property('vertebrate', Boolean);
  this.property('belly', Array);

  this.prototype.feed = function (food) {
    this.belly.push(food);
  };
});

Creature.get('wolf', function (err, wolf) {
  console.log(wolf);
  wolf.update({diet: 'carnivore'}, function (err, creature) {
    console.log(creature);
  });
});

I get the following output

{ _id: 'wolf',
  diet: 'herbivore',
  vertebrate: true,
  belly: [],
  resource: 'Creature',
  _rev: '1-7714a2a782c34ab672f44c841478ec9a' }
{ _id: undefined,
  diet: 'carnivore',
  vertebrate: null,
  belly: [],
  resource: 'Creature',
  _rev: '2-16e53b06d3699c2fdaad5d8fb4ead10c' }

Hooks / Middleware support

Similar to Mongoose middlewares, in which we can provide functions that can execute before / after CRUD operations.
For example we could use to to send a notification or email out everytime a user is updated.

Engine option `uri` key should not be modified.

This prevents passing the same option object to this.use('engine', options) call when defining multiple models since the uri key is modified after the first call making it invalid for subsequent calls.

Using the resourceful-redis engine for example but I believe this applies to many other engine implementations as well since the baked-in couchdb engine do this and the README file also use uri as an example.

For example, say I define a model like this:

var r = require('resourceful-redis')
  , opts =
    { uri: 'redis://0.0.0.0:12345/'
    , namespace: 'bugtest' };

var User = r.define('user', function() {
  this.use('redis', opts);
  this.string('username');
  this.string('email');
});

var BlogPost = r.define('post', function() {
  this.use('redis', opts);
  this.string('title');
  this.string('content');
});

Notice the non-standard port part (12345 instead of 6379 for redis.)

It will fail with redis connection error on the this.use line for the BlogPost model since the opts variable has been modified (see this line) with the :12345 stripped from the uri (making it now tries to connect to the default port 6379 instead.)

Of course, I can do a global .use call on the resourceful object directly but that means I can only use one kind of engine for all my models.

Relationship type expected ['string',null] actual 'object'

When defining a relationship like this:

this.Article = resourceful.define('article', function () {
  this.property('name', String);
});

this.Category = resourceful.define('category', function () {
  this.property('name', String);
  this.parent('article')
});

it fails with a validation error when a Category is created alone a it has no linked Article (i.e. the article_id is null).

After some debugging i figured out that it was either of these problems:

  1. The relationships parent type is by default defined as: [String,null] where typeof null will be translated to 'object' which in turn makes it fail. Solution would be to change the type to ['string','null'] or [String,'null'].
  2. Revalidator (will post a issue/pull request there too) expects the types to be strings but does not enforce it so this section will always be ignored unless it's a string.

Display friendlier error when CouchDB is not running

probably no way around this, but maybe should be documented somewhere if not already.

When running the test suite without Couch running you get an obvious failure

  ♢ resourceful/engines/couchdb 

  In database "test" Defining resource "book"
    ✓ will be successful
  In database "test" Defining resource "author"
    ✓ will be successful
  In database "test" an Resource.all() request

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
    ✗ should respond with an array of all records 
        » expected null, got { 
      errno: 'ECONNREFUSED', 
      message: 'connect ECONNREFUSED', 
      syscall: 'connect', 
      stack: 'Error: connect ECONNREFUSED\n    at errnoException (net.js:670:11)\n    at Object.afterConnect [as oncomplete] (net.js:661:19)', 
      arguments: undefined, 
      type: undefined, 
      code: 'ECONNREFUSED' 
  } // net.js:670
TypeError: Cannot read property 'status' of undefined
    at Object.callback (/Users/klop/node/resourceful/lib/resourceful/engines/couchdb/index.js:53:27)
    at /Users/klop/node/resourceful/node_modules/cradle/lib/cradle/database/documents.js:45:18
    at Request._callback (/Users/klop/node/resourceful/node_modules/cradle/lib/cradle.js:181:18)
    at /Users/klop/node/resourceful/node_modules/cradle/node_modules/request/main.js:119:22
    at Request.<anonymous> (native)
    at Request.<anonymous> (events.js:67:17)
    at Request.emit (/Users/klop/node/resourceful/node_modules/vows/lib/vows.js:236:24)
    at ClientRequest.<anonymous> (/Users/klop/node/resourceful/node_modules/cradle/node_modules/request/main.js:207:10)
    at ClientRequest.<anonymous> (events.js:67:17)
    at ClientRequest.emit (/Users/klop/node/resourceful/node_modules/vows/lib/vows.js:236:24)

Document the hooks and events properly

I've started to write a patch for this but I'm not sure if I caught all of the cases.

Hooks:

  • create
  • destroy
  • filter
  • find - also invoked when calling Resource.all()
  • get
  • save - also invoked when creating a Resource
  • update
  • view

Events:

  • destroy
  • error - fired when validate fails, or when before/after hooks calls their callback with a truthy first param
  • filter
  • find - also fired when calling all()
  • get
  • init - fired... IDK
  • save - also fired on create()
  • update
  • view

Create is a tricky one:

Creature.on('error', function() {
    console.log(arguments)
});

Creature.on('save', function() {
    console.log('save')
});

Creature.before('create', function(instance, cb) {
    console.log('before create');
    cb();
});

Creature.after('create', function(err, instance, cb) {
    console.log('after create');
    cb();
});

Creature.before('save', function(instance, cb) {
    console.log('before save');
    cb();
});

Creature.after('save', function(err, instance, cb) {
    console.log('after save');
    cb();
});


Creature.create({diet: 'carnivore', vertebrate: true}, function() {
    console.log('i am callback')
});

Should output

before create
after create
before save
after save
save
i am callback

If the validation fails it should be

before create
after create
error
i am callback

Am I missing something?

Tests failing in Node 0.6.0

The current version of Resourceful (0.1.0) is failing tests with Node 0.6.0 and Vows 0.5.13.

It's complicated by an issue in Vows that hides the names of the failing tests:
vowsjs/vows#154

The suites that pass are:

  • resourceful/resource/cache
  • resourceful/events
  • resourceful/hooks
  • resourceful/memory/relationship

which leaves these that must be erroring:

  • resourceful
  • resourceful/resource/relationship
  • resourceful/resource/view
  • resourceful/engines/database

I'll add more detail when I have it.

Resource.update() behaves strangely when resourceful.cache = false

When using the cache I find that Resource.update() works fine:

    resourceful.cache = true;
    Wolf.get(42, function (err, wolf) {
      if (err) { throw new(Error)(err) }
      wolf.update({ fur: 'curly' }, function (e, wolf) {
        console.log(wolf.fur); // "curly"
      });
    });

When I disable cache I get what looks like the OK response from Couch instantiated as a Resource:

    resourceful.cache = false;
    Wolf.get(42, function (err, wolf) {
      if (err) { throw new(Error)(err) }
      wolf.update({ fur: 'curly' }, function (e, wolf) {
        console.log(wolf.fur); // "undefined"
        console.log(wolf); // " { _id: undefined, fur: undefined, resource: 'Wolf', ok: true, id: '18d001bda4786fb23738cd49e0000885', rev: '35-c314a9dad27908b45a33d04550a86af2' } }"
      });
    });

Happy to have a crack at fixing this, just not 100% sure where you guys would expect that fix to go as some of the possible approaches involve interface changes.

Missing `enum` data type

Current data types include:

  • String
  • Boolean
  • Array
  • Object
  • Number

We are missing type Enumerable.
http://en.wikipedia.org/wiki/Enumerated_type

Example:

//
// suits property will only accept values in the set
//
this.enum('suits', { set: ["clubs", "spades", "diamonds", "hearts"] })

//
// pets property will only accept values in the set ( should support deepEquals checks )
//
this.enum('pets', { 
  set: [{ name: "dog" }, { name: "cat" }, { name: "horse", outdoors: true }] 
})

Engines list

More of a question than an issue but is there an engine list somewhere?

From the look of it, it seems resourceful ony supports couchdb and an in memory store. Am I right?

Thanks,
Julien.

Support synchronous hooks

Currently all hooks get passed a callback which is necessary to support async hooks. Many use cases however, synchronous hooks would be useful.

This should work, but doesn't

  MyResource.on('get', function (_, obj) {
    // Do something
    return true;
  });

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.