如何使用Mongoose从Schema方法访问嵌入文档的属性(类似DBRef)?

时间:2012-05-11 11:12:48

标签: node.js mongoose

我正在用express和mongoose / mongodb构建一个nodejs应用程序。

为了管理用户角色(用户可能有0-n个角色),我决定实现一个User模式和一个单独的Role模式,并将它们绑定在一起,如DBRef并使用mongoose populate功能从一个到另一个容易。我之所以选择这种结构是因为我认为这是回答以下问题的最佳方式:“给我一个有X角色的用户列表”或“给我一个Y角色即将过期的用户列表”。

这些是我的架构的角色和用户的简化版本:

RoleSchema = new Schema {
  _user: { type: Schema.ObjectId, ref: 'User' }
  type: String
  expiration: { type: Date, default : '0' }
}

UserSchema = new Schema {
  email: { type: String, index: { unique: true }}      
  roles : [{ type: Schema.ObjectId, ref: 'Role' }]
}

通过这种方式,我可以在我的快速脚本中创建/获取/填充用户和角色而不会出现问题。当我想向用户添加角色时,我创建角色,保存它,然后将其推送到用户的角色数组,然后保存用户。因此,在mongoDB中发生的事情是每个模式都有自己的集合,并且它们通过id字段指向彼此。

现在,我接下来要做的是在我的架构上实现布尔类型方法来检查与角色相关的问题,以便我可以做一些事情,比如

if(user.isActiveSubscriber())

我认为只要在我的用户架构中添加一个方法就可以实现这一点,如下所示:

UserSchema.method "isActiveSubscriber", ->
  result = false
  now = new Date()
  @roles.forEach (role) ->
    result = true  if role.type is "SUBSCRIBER" and role.expiration > now
 result

我的问题是角色出现了空属性。我认为这是有道理的,因为我的用户集合只有角色的id,但实际的属性存储在另一个集合中。

所以,这里是问题:

a)有没有办法从我的用户架构方法中加载角色属性? 我尝试在方法中调用populate,但是得到了一个错误,即用户对象不知道任何填充方法。我也试过从方法内部做一个Role.findById()但也得到一个错误(尝试使用Role和RoleSchema)

b)如果没有办法......我应该简单地添加代码来检查我的脚本吗?我讨厌将这种逻辑与应用程序逻辑/流程混合在一起。有更好的选择吗?

c)将这些收藏分成两部分是不是一个坏主意?我是否完全忽略了NoSQL的观点?如果角色只是作为用户集合的一部分存储的嵌入式文档数组会更好吗?

2 个答案:

答案 0 :(得分:1)

让我回答你的问题:

a)您可以做的是在通话中加载角色。假设你做了

Role = mongoose.model('Role', RoleSchema)

你只需要运行

Role.where('_id').in(user.roles).run( (err, res) ->
    /* you have roles in res now */
)

这是一个异步操作,它要求将回调传递给您的方法,即

UserSchema.method "isActiveSubscriber", (callback) ->
    now = new Date()
    if @roles.length is 0
        return callback(false)
    if @roles[0].type
        /* roles are loaded,
           maybe there is some other fancy way to do the check */
        result = false
        @roles.forEach (role) ->
            if role.type is "SUBSCRIBER" and role.expiration > now
                result = true
        callback(result)
    else
        /* we have to load roles */
        Role.where('_id').in(@roles).run( (err, res) =>
            /* you have roles in res now */
            /* you may want to populate the field,
               note => sign in this function definition */
            @roles = res 
            result = false
            res.forEach (role) ->
                if role.type is "SUBSCRIBER" and role.expiration > now
                    result = true
            callback(result)
        )

现在,对于用户user,您可以像这样调用它

user.isActiveSubscriber( (result) ->
    /* do something with result */
)

问题是操作是异步的,它会强制你的代码中有额外的回调嵌套(如果你想检查说100个用户的角色会很痛苦,你需要一些异步调用处理库,如{{3} })。因此,无论何时加载用户并使用您向我们展示的代码,我都会建议填充此字段。你可以添加静态方法(也许你甚至可以覆盖默认的find方法?虽然我不确定)。

b + c)另一种选择是将角色存储为集合中的字符串,并在应用程序中对可能的角色进行硬编码。这将更容易实现,但添加/删除新角色将是$%^& *的痛苦。你这样做的方式(即通过引用)很好,我认为你唯一需要的是在搜索用户时填充字段,一切都会好的。

答案 1 :(得分:0)

采用的方法属于mongoose如何使用populate实现类似DBRef的行为的范围。然而有些事似乎已经错过了。不确定这些细节是否已经注意并且为了简洁起见未示出。将完成整套步骤:

RoleSchema = new Schema {
   type: String
   expiration: { type: Date, default : '0' }
}

UserSchema = new Schema {
   email: { type: String, index: { unique: true }}      
   _roles : [{ type: Schema.ObjectId, ref: 'Role' }] // array of pointers for roles
}

Role = mongo.model('Role', 'RoleSchema');
User = mongo.model('User', 'UserSchema');

var life_sub = new Role({ type: 'LIFE_SUBSCRIBER', .... });
var daily_sub = new Role({ type: 'DAILY_SUBSCRIBER', .... });
var monthly_sub = new Role({ type: 'MONTHLY_SUBSCRIBER', .... });

var jane = new User({email: 'jane@ab.com', _roles: [daily_sub, monthly_sub]});

根据需要添加用户。接下来查找指定的用户和相应的角色集使用:

User
 .find({email:'jane@ab.com'})
 .populate('_roles[]')
 .run(function(err, user)

现在user是一个指针数组,对应于指定用户的角色。遍历数组并查找用户的每个角色以及相应的到期日期。与Date.now()比较以确定特定用户的角色已过期。

希望这有帮助。