Страницы

воскресенье, 21 сентября 2014 г.

MongoDB ObjectId by date

Продолжение темы предыдущего поста - зачем мне все это? К примеру для того, чтобы вытащить документы коллекции MongoDB по дате создания. Возьмем коллекцию messages, созданную в одном из предыдущих постов. Отдельное поле, содержащее дату или timestamp отсутствует, однако дату создания документа можно получить по ObjectId.

Вспомним как выглядят документы коллекции messages:

Установим модуль mongodb, после чего напишем простенький модуль подключения к демону mongod по имени db.js:
var Db = require('mongodb').Db;
var Server = require('mongodb').Server;
var db;

module.exports.connect = function() {
  if (db) return db;
  db = new Db('test', new Server('127.0.0.1', '27017', {auto_reconnect: true}), {w: 1});
  db.open(function(err) { if (err) throw err; });
  return db;
};

Редактируем исходный код приложения, созданного в предыдущем посте: после middleware, которое просто выводит в консоль прилетевший в GET запросе параметр date, добавим маршрут '/messages', по которому будем отдавать документы одноименной коллекции, а в случае обнаружения параметра date отдавать документы старше прилетевшей в параметре даты.

Приведу код файла app.js целиком:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(function(req, res, next) {
  var date = req.query.date, timestamp, fromTimestamp;
  if (!date) return next();
  timestamp = Date.parse(date);
  fromTimestamp = new Date(timestamp);
  console.log('req.method: %s\ndate: %s\ntimestamp: %s\nfromTimestamp: %s\n', req.method, date, timestamp, fromTimestamp);  
  next();
});

var db = require('./db').connect();
var hex = require('mongodb').ObjectID.createFromHexString;
app.get('/messages', function(req, res, next) {
  var query = {};
  if (req.query.date) query._id = {$lt: hex(Math.floor(Date.parse(req.query.date)/1000).toString(16) + '0000000000000000')};
  db.collection('messages').find(query).sort({_id: -1}).toArray(function(err, messages) {
    if (err) return next(err);
    res.json(messages);
  });
});

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

module.exports = app;

Напомню, что изначально приложение было создано с помощью express-generator за пару секунд.

Запускаем приложение, открываем в браузере адрес http://localhost:3000 и, как и ожидалось, получаем приветствие фреймворка Express.

Открываем http://localhost:3000/messages и получаем все документы коллекции messages:

Сорри, на скриншот весь массив не поместился :).

Возьмем _id одного из полученных документов, создадим ObjectId, вытащим из него дату создания и приведем к стандарту UTC:

Таким образом документы старше выбранного можно получить по адресу http://localhost:3000/messages?date=Sun, 31 Aug 2014 11:21:31 GMT:

Теперь на скриншоте весь массив :).

Профит очевиден, и получен с помощью одной единственной строки кода:
...
if (req.query.date) query._id = {$lt: hex(Math.floor(Date.parse(req.query.date)/1000).toString(16) + '0000000000000000')};
...

That's all folks :).