Почему во время $save() генерируется новая запись в mongoDB со строкой _id?

ЗНАЧИТЕЛЬНЫЙ стек новичка здесь. Наверное глупый вопрос задал.

В качестве упражнения я пытался реализовать прототип SPA, который показывает серию карточек задач на экране (вроде Trello).

На данный момент каждая карточка имеет 4 поля:

  • _id: идентификатор объекта
  • содержимое: строка
  • рабочий процесс: строка
  • состояние: строка

Я использую MongoDB для базы данных (ввел некоторые тестовые данные с помощью Robomongo), на моем компьютере установлен node.js, а также Express.js< /сильный>.

Мой файл server.js выглядит следующим образом:

var express = require('express'), 
    cards = require('./routes/cards');

var app = express();

app.configure(function() {
    app.use(express.logger('dev'));
    app.use(express.bodyParser());
});

app.get('/cards', cards.findAll);
app.get('/cards/:id', cards.findById);
app.post('/cards', cards.addCard);
app.put('/cards/:id', cards.updateCard);

app.listen(3000);
console.log('Listening on port 3000...');

Мои routes/cards.js на стороне сервера выглядят следующим образом:

    var mongo = require('mongodb');
var Server = mongo.Server,
    Db = mongo.Db,
    BSON = mongo.BSONPure;

var server = new Server('localhost', 27017, {auto_reconnect: true});
var db = new Db('mindr', server);
db.open(function(err, db) {
    if(!err) {
        console.log("Connected to 'mindr' database");
        db.collection('cards', {strict:true}, function(err, collection) {
            if (err) {
                console.log("The 'cards' collection doesn't exist.");
            }
        });
    }
});

exports.findById = function(req, res) {
    var id = req.params.id;
    console.log('Retrieving card: ' + id);
    db.collection('cards', function(err, collection) {
        collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) {
            res.send(item);
        });
    });
};

exports.findAll = function(req, res) {
    db.collection('cards', function(err, collection) {
        collection.find().toArray(function(err, items) {
            res.send(items);
        });
    });
};

exports.addCard = function(req, res) {
    var newCard = req.body;
    console.log('Adding card: ' + JSON.stringify(newCard));
    db.collection('cards', function(err, collection) {
        collection.insert(newCard, {safe:true}, function(err, result) {
            if (err) {
                res.send({'error':'An error has occurred'});
            } else {
                console.log('Success: ' + JSON.stringify(result[0]));
                res.send(result[0]);
            }
        });
    });
}

exports.updateCard = function(req, res) {
    var id = req.params.id;
    var card = req.body;
    console.log('Updating card: ' + id);
    console.log(JSON.stringify(card));
    db.collection('cards', function(err, collection) {
        collection.update({'_id':new BSON.ObjectID(id)}, card, {safe:true}, function(err, result) {
            if (err) {
                console.log('Error updating card: ' + err);
                res.send({'error':'An error has occurred'});
            } else {
                console.log('' + result + ' document(s) updated');
                res.send(card);
            }
        });
    });
}

exports.deleteCard = function(req, res) {
    var id = req.params.id;
    console.log('Deleting card: ' + id);
    db.collection('cards', function(err, collection) {
        collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) {
            if (err) {
                res.send({'error':'An error has occurred - ' + err});
            } else {
                console.log('' + result + ' document(s) deleted');
                res.send(req.body);
            }
        });
    });
}

Когда я получаю карты из БД в своем контроллере AngularJS, все идет нормально. Все карты корректно отображаются на экране. Это код, который получает карты:

var mindrApp = angular.module('mindrApp', ['ngResource'])

mindrApp.controller('WorkflowController', function ($scope, $resource) {
    var CardService = $resource("http://localhost:3000/cards/:cardId", {cardId:"@id"});
    $scope.cards = CardService.query();
});

На каждой карточке есть несколько кнопок, которые можно использовать для изменения состояния карточки на следующее состояние, доступное в рабочем процессе (как определено доступными действиями текущего состояния).

При нажатии кнопки идентификатор карты и следующее состояние передаются функции в контроллере:

<div class="btn-group btn-group-xs">
    <button type="button" class="btn btn-default" 
        ng-repeat="currentAction in currentState.actions | filter:{default:true}" 
        ng-click="processCard(currentCard._id, currentAction.next)">
        {{currentAction.name}}
    </button>
</div> 

А это функция processCard в контроллере:

$scope.processCard = function(id, nextState) {
    var currentCard = CardService.get({cardId: id}, function(){
        currentCard.state = nextState;
        currentCard.$save();
    });
};

Происходит следующее: когда я нажимаю кнопку, вместо изменения состояния текущей карты создается новая карта с полем id типа String. Это вывод сервера:

Retrieving card: 52910f2a26f1db6a13915d9f
GET /cards/52910f2a26f1db6a13915d9f 200 1ms - 152b
Adding card: {"_id":"52910f2a26f1db6a13915d9f","content":"this is some content for this really cool card","workflow":"simple","state":"completed"}
Success: {"_id":"52910f2a26f1db6a13915d9f","content":"this is some content for this really cool card","workflow":"simple","state":"completed"}
POST /cards 200 1ms - 150b

Любая идея, почему это происходит? Почему он вызывает функцию addCard на сервере вместо вызова функции updateCard?


person Francesco Gallarotti    schedule 25.11.2013    source источник


Ответы (2)



Итак, я понял это. У меня были две проблемы:

1) вместо обновления существующего элемента в базе данных он создавал новый с тем же идентификатором, но в строковом формате вместо использования формата ObjectId.

2) каждый раз, когда я вызывал $update, он не добавлял идентификатор к пути, а всегда помещал его в /cards.

Итак, вот решения каждой из проблем.

1) Это действительно хак, который предполагает, что ВСЕ идентификаторы находятся в формате ObjectId. Мне не нравится это решение, но пока оно работает, и я придерживаюсь его. Все, что мне нужно было сделать, это добавить строку, которая преобразует card._id обратно в формат ObjectId, в функцию updateCard внутри файла cards.js на стороне сервера.

exports.updateCard = function(req, res) {
    var id = req.params.id;
    var card = req.body;
    console.log('Updating card: ' + id);
    console.log(JSON.stringify(card));
    card._id = new BSON.ObjectID.createFromHexString(card._id); // HACK!
    db.collection('cards', function(err, collection) {
        collection.update({'_id':new BSON.ObjectID(id)}, card, {safe:true}, function(err, result) {
            if (err) {
                console.log('Error updating card: ' + err);
                res.send({'error':'An error has occurred'});
            } else {
                console.log('' + result + ' document(s) updated');
                res.send(card);
            }
        });
    });
}

2) Это было исправление из двух частей. Во-первых, мне пришлось изменить файл services.js, чтобы явно сказать, что я хочу использовать обновление через PUT:

    var mindrServices = angular.module('mindrServices', ['ngResource']);
    mindrServices.factory("Card", ["$resource",
    function($resource) {
        return $resource("http://localhost:3000/cards/:cardId", {cardId:"@id"},
            {
                query: {method: "GET", isArray:true},
                update: {method: "PUT"}
            }
        );
    }]);

Затем я исходил из того, что простой вызов currentCard.$update() получит идентификатор из вызывающего экземпляра, вместо этого я должен явно передать идентификатор следующим образом:

var mindrControllers = angular.module('mindrControllers', []);
mindrControllers.controller('CardsController', ["$scope", "Card", 
    function ($scope, Card) {
        $scope.cards = Card.query();
        console.log("cards populated correctly...");

        $scope.processCard = function(currentCard, currentAction) {
            console.log("processCard: card[" + currentCard._id + "] needs to be moved to [" + currentAction.next + "] state... ");
            currentCard.state = currentAction.next;
            currentCard.$update({cardId: currentCard._id}); // passing the ID explicitly
        }

Это вывод, который я получаю на стороне сервера:

    Updating card: 52910eb526f1db6a13915d9c
{"_id":"52910eb526f1db6a13915d9c","content":"this is some content for this really cool card","workflow":"simple","state":"backlog"}
    1 document(s) updated
    PUT /cards/52910eb526f1db6a13915d9c 200 4ms - 111b
person Francesco Gallarotti    schedule 26.11.2013