使用其他字段的数据创建ID

时间:2016-04-14 16:43:48

标签: javascript node.js mongodb mongoose mongodb-query

我是mongodb的新手,我使用mongoose来验证和订购数据(如果这不起作用,我可以将其更改为MySQL)。 该应用程序将是一个电子商店,购买与电影,游戏,分机相关的商品。

我的架构如下:

var productSchema = {
    id: {
        type: String,
        required: true
    },
    name: {
        type: String,
        required: true
    },
    img: {
        type: String,
        required: true
    },
    price: {
        type: Number,
        required: true
    },
    stock: {
        type: Number,
        required: true
    },
    category: {
        object: {
            type: String,
            required: true
        },
        group: {
            type: String,
            required: true  
        },
        name: {
            type: String,
            required: true
        }
    }
};

这就是我想要做的事情:

如果我在类别中有以下数据:

  • category.object =" ring"
  • category.group ="电影"
  • category.name ="戒指之王"

我希望id由类别中每个字段的首字母和数字(最后添加的项目的编号加1)组成。在这种情况下,它将是RMLOTR1。

我现在正在做什么

我同时添加了大量数据,因此每次执行此操作时,我都会创建一个函数来迭代所有添加的项目并执行我想要的但是...

我的问题是

是否有内置的方法可以使用mongodb或mongoose执行此操作,同时添加数据并创建ID?我知道我可以做虚拟,但我希望存储数据。

附加功能

  • 如果使用mongodb无法做到这一点,有没有办法用MySQL做到这一点?
  • 这种事情是否被视为正确/错误的方法?

1 个答案:

答案 0 :(得分:1)

你基本上是在"保存"上寻找一个"pre" middleware钩子。通过在集合中创建新文档来触发事件。这将检查当前文档内容并提取"字符串"从值中创建你的"前缀" _id的值。

还有另一部分,其中"前缀"当已经存在该特定"前缀"的值时,需要添加数字计数器。使其与众不同。在用于"Generate an auto-incrementing sequence field"的MongoDB中有一种常见的技术,它基本上涉及保持"计数器"每次访问时都收集并递增值。

作为一个完整且独立的演示,您将如下技术组合在一起:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/warehouse');

var counterSchema = new Schema({
  "type": { "type": String, "required": true },
  "prefix": { "type": String, "required": true },
  "counter": Number
});

counterSchema.index({ "type": 1, "prefix": 1 },{ "unique": true });

counterSchema.virtual('nextId').get(function() {
  return this.prefix + this.counter;
});

var productSchema = new Schema({
  "_id": "String",
  "category": {
    "object": { "type": String, "required": true },
    "group": { "type": String, "required": true },
    "name": { "type": String, "required": true }
  }
},{ "_id": false });

productSchema.pre('save', function(next) {
  var self = this;

  if ( !self.hasOwnProperty("_id") ) {

    var prefix = self.category.object.substr(0,1).toUpperCase()
      + self.category.group.substr(0,1).toUpperCase()
      + self.category.name.split(" ").map(function(word) {
        return word.substr(0,1).toUpperCase();
      }).join("");

    mongoose.model('Counter').findOneAndUpdate(
      { "type": "product", "prefix": prefix },
      { "$inc": { "counter": 1 } },
      { "new": true, "upsert": true },
      function(err,counter) {
        self._id = counter.nextId;
        next(err);
      }
    );
  } else  {
    next();  // Just skip when _id is already there
  }
});

var Product = mongoose.model('Product',productSchema),
    Counter = mongoose.model('Counter', counterSchema);

async.series(
  [
    // Clean data
    function(callback) {
      async.each([Product,Counter],function(model,callback) {
        model.remove({},callback);
      },callback);
    },
    function(callback) {
      async.each(
        [
          {
            "category": {
              "object": "ring",
              "group": "movies",
              "name": "lord of the rings"
            }
          },
          {
            "category": {
              "object": "ring",
              "group": "movies",
              "name": "four weddings and a funeral"
            }
          },
          {
            "category": {
              "object": "ring",
              "group": "movies",
              "name": "lord of the rings"
            }
          }
        ],
        function(data,callback) {
          Product.create(data,callback)
        },
        callback
      )
    },
    function(callback) {
      Product.find().exec(function(err,products) {
        console.log(products);
        callback(err);
      });
    },
    function(callback) {
      Counter.find().exec(function(err,counters) {
        console.log(counters);
        callback(err);
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
)

这为您提供如下输出:

[ { category: { name: 'lord of the rings', group: 'movies', object: 'ring' },
    __v: 0,
    _id: 'RMLOTR1' },
  { category:
     { name: 'four weddings and a funeral',
       group: 'movies',
       object: 'ring' },
    __v: 0,
    _id: 'RMFWAAF1' },
  { category: { name: 'lord of the rings', group: 'movies', object: 'ring' },
    __v: 0,
    _id: 'RMLOTR2' } ]
[ { __v: 0,
    counter: 2,
    type: 'product',
    prefix: 'RMLOTR',
    _id: 57104cdaa774fcc73c1df0e8 },
  { __v: 0,
    counter: 1,
    type: 'product',
    prefix: 'RMFWAAF',
    _id: 57104cdaa774fcc73c1df0e9 } ]

要首先了解Counter架构和模型,您基本上定义了一些您将要查找"唯一的"键,并附加一个数字字段到"增量"在比赛中。为方便起见,这只有两个字段组成了唯一的组合,并定义了复合索引。如果需要,这也可能是一个复合_id

另一个便利是virtual的{​​{1}}方法,它只是对"前缀"进行连接。和"计数器"值。这里也是最好的做法,包括" type"因为您的nextId模型可用于服务"计数器"用于多个集合源。因此,我们在Counter模型的上下文中访问时使用"product",以区别于其他模型,您可能还会保留类似的序列计数器。只是一个值得关注的设计点。

对于实际的Product模型本身,我们希望附加" pre save"中间件钩子以填充Product内容。因此,在确定"前缀"的字符部分之后,操作然后关闭并查找"前缀"使用"产品"在_id模型集合中组合数据。

.findOneAndUpdate()的功能是寻找符合"计数器"中标准的文件。收集,然后在找到文件的地方,它将增加"使用Counter更新运算符的当前counter值。如果找不到该文档,则"upsert"选项意味着将创建一个新文档,并且无论如何都会相同地增加"增加"也将在新文件中发生。

这里的$inc选项意味着我们想要"修改"要返回的文档(新的或更改的),而不是在应用"new"之前文档的样子。结果是"计数器"每次访问时,值始终会增加。

一旦完成并且$inc的文档要么为其匹配的键增加或创建,那么您现在可以使用一些内容来分配Counter中的_id {1}}模型。如前所述,您可以在此处使用Product以获取带有附加计数器值的前缀。

因此,只要您的文档始终由模型中的virtual方法创建,或者使用.create()然后使用new Product()方法创建,那么附加到您的&#的方法34;模型"在你的代码中始终执行。

请注意,由于您希望在.save()中使用此功能,因此作为主键,这是"不可变的"并且无法改变。因此,即使稍后更改了引用的字段中的内容,也无法更改_id中的值,因此,如果已设置_id值,此处的代码不会尝试。