如何将行为附加到Meteor集合的最佳方式?

22
在Meteor中,当您从数据库检索记录时,它只是一条记录。因此,如果我有一个名为“Dogs”的集合,一只狗可能会有“fur: 'brown'”或“breath: 'stinky'”,但它没有一个“bark()”方法。
显然,我可以创建一些函数来期望一个“dog”作为参数,然后对该“dog”执行操作。我甚至可以将所有这些函数封装到单个构造函数中。我不太喜欢这种方法,但如果有人有一种干净而明智的方法来做到这一点,我会听取意见。
我另一个想法是将“dog”包装在“Backbone.Model”中。这可能很有趣,因为“fetch”和“save”可以重新定义为执行“find”和“insert”或“update”,并且您也可以在那里定义所有行为,但我已经阅读到这种类型的事情通常是被反对的。
我应该如何做?Meteor.Model是否正在官方开发中?其他人是如何解决这个问题的?
编辑
对于在采纳答案后一年之后来到这个问题的人:目前我正在使用Exygy的minimongoid mrt包,它对haihappen的版本有一些改进,而后者在被接受的答案链接的博客文章中提到。
我目前正在为代码库做出贡献,以使结果集更类似于关系。希望其他人也能从中受益,并感到倾向于贡献有用的功能。
编辑
另一个答案建议在创建集合时使用transform属性。虽然我肯定更喜欢不需要自己构建的东西,但这个功能增加了很多可能性,我希望任何正在为Meteor开发ORM的团队都能在核心上利用这个功能。

这篇博客文章解释了如何使用transform属性。

此外,minimongoid现在作为Meteor包可用,我仍在使用它。它支持验证和声明关系。我还添加了一些功能到这个包中,所以如果一个棋盘有很多棋子,board.pieces().create(attributes)将使用给定的attributes持久化一个新的piece记录,并自动与board关联。在我看来,这是最全面的解决方案之一。


2
值得一提的是 collection-behaviors 包。 - Dan Dascalescu
@DanDascalescu,感谢您提供的链接!虽然它不完全符合我的需求,但在其他方面对我非常有用。非常感谢! - Samo
6个回答

28
你可以使用Collection中的transform参数来重载对象并添加自定义函数。
var Dogs = new Meteor.Collection("dogs", 
{
    transform:function(entry)
    {
        entry.bark = function(){ console.log(this.barkSound);};
        return entry;
    }
});

接下来:

var aDogID = new Dogs.insert({barkSound: "ruff"})
Dogs.find(aDogID).bark(); // "ruff"

奖励:如果您因任何原因想要使用与Andrew Ferk提出的类似概念,只需使用_.defaults(object, *defaults)函数即可。

var defaults = {
             barkSound: "ruff",
             bark: function() {
                        console.log(this.barkSound);
                    }
            }

Dogs = new Meteor.Collection("dogs",
        {
            transform: function(object) {
                return _.defaults(object, defaults);
            }
        });

这看起来像是正确的、官方的做法。我会采用这个方案。 - Randy L
这是一个很棒的解决方案,谢谢!!!现在完全改变了我的Meteor代码。我怎么在文档中错过了这个??? - Vincil Bishop
2
在以下主题上发布更多示例:http://www.okgrow.com/posts/2014/05/19/meteor-transform/ - Shwaydogg

4

天文学是对流星模型这个老问题的新回答。作者计划添加更多特性,并得到了其他知名Meteor包作者的支持。唯一的缺点是它可能有点前沿。

天文学也是高度模块化的,是在Meteor包/模块化风格下构建的一个典范示例。


4

这是重写Meteor.Collection以支持对象方法的开始。

Meteor.Kollection = Meteor.Collection;
Meteor.Kollection.extend = function(constructor) {
  var parent = this;
  var child = function() {
    Meteor.Kollection.apply(this, arguments);
    constructor.apply(this, arguments);
  };

  _.extend(child, parent);

  function __proto__() { this.constructor = child; };
  __proto__.prototype = parent.prototype;
  child.prototype = new __proto__;

  return child;
};

Meteor.Collection = Meteor.Kollection.extend(function(name, options) {
  if (options && options.defaults) {
    this.defaults = options.defaults;
  }
});

Meteor.Collection.prototype.applyDefaults = function(attrs) {
  return _.defaults(attrs, this.defaults);
};

Meteor.Collection.prototype.create = function(attrs) {
  if (typeof attrs !== "object") attrs = {};
  return this.applyDefaults(attrs);
};

Meteor.Collection.prototype.findOne = function(selector, options) {
  var object = Meteor.Kollection.prototype.findOne.apply(this, arguments);
  return this.applyDefaults(object);
};

你可能会注意到新的collection.create方法,以及collection.findOne已被覆盖。我想所有的collection.*方法都需要被覆盖,但这是一个开始。
那么你可以用它做什么呢?
var Dogs = new Meteor.Collection("dogs", { defaults: {
  barkSound: "ruff",
  bark: function() {
    console.log(this.barkSound);
  }
}});

if (Meteor.isClient) {
  var regularDog = Dogs.create();
  regularDog.bark(); // ruff

  var smallDog = Dogs.create({
    barkSound: "yip"
  });
  smallDog.bark(); // yip

  Dogs.insert(smallDog, function(error, id) {
    Dogs.findOne(id).bark(); // yip
  });
});

我不确定这是如何发生的,但当一个对象中的函数被插入时,它们会被移除。因此,我们可以直接将方法应用于对象。首先,通过传递带有 defaults 属性的对象来创建集合。该属性可以包括属性或方法。要为给定的集合创建新对象,请使用 collection.create(attrs),其中 attrs 是一个选项参数,包括附加或覆盖的属性和方法。

3

虽然可能正在开发官方模型系统,但现在您可以做一些事情:

Mario Uhler创建了一个类似于activerecord的不错的coffeescript模型:https://coderwall.com/p/_q9b1w

Tom Coleman也创建了一个社区包,非常有用的模型:https://github.com/tmeasday/meteor-models,您可能需要meteorite将其添加为一个包。

当然,正如您建议的那样,可以使用Backbone。我个人使用js原型,但并不是每个人都习惯使用它们,我只是使用它们以便于过渡,当Meteor的模型系统出现时,也很容易在客户端和服务器之间共享,而无需添加太多软件包。


谢谢您的回复。如果可以的话,您能否提供一个简单的例子来展示如何利用js原型来解决这个问题? - Samo
我一会儿就会更新,我只需要稍微清理一下原型。 - Tarang
我决定选择Minimongoid,它非常适合我的需求 :-) - Samo
2
我不明白为什么集合的转换参数不足够。 - Randy L
感谢 @the0ther,很抱歉我没有早点看到这个。我已经更新了 OP,并添加了一些关于 transform 的注释,绝对值得注意。 - Samo

2

在Meteor方面,这是一个相对较旧的问题,但我认为dburles:collection-helpers可以实现Flavien Volken建议的内容。也许这对最近进来的任何人都有用。


谢谢,我很感激能够了解新技术。我仍在使用 minimongoid(https://atmospherejs.com/kaptron/minimongoid),它支持像 has_onebelongs_to 这样的声明。 - Samo

0
除了@Flavien Volken的回答之外,您还可以将参数传递给添加到集合中的方法。
Car = new Mongo.Collection('car', {
    transform: function(entry) {
        entry.is_watched = function(userId) {
            var is_watched = false;
            if (entry.watchList) {
                for (var i in entry.watchList) {
                    if (entry.watchList[i].userId == userId) {
                        is_watched = true;
                        break;
                    }
                }
            }
            return is_watched;
        };
        return entry;
    }
});

在模板中(传递已登录用户的ID):

{{# if is_watched currentUser._id }}
    Watched
{{ else }}
    Not watched
{{/ if }}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接