2013-08-29 116 views
15

好的,仍然在我的玩具應用程序中,我想了解一組車主的里程錶的平均里程數。這在客戶端很容易,但不能擴展。對?但在服務器上,我不完全知道如何完成它。流星中的平均彙總查詢

問題:

  1. 如何實現在服務器上的東西,然後用它在客戶端上?
  2. 如何使用mongo的$ avg聚合函數來利用其優化的聚合函數?
  3. 或者(2)您如何在服務器上執行map/reduce並將其提供給客戶端?

通過@HubertOG的建議是使用Meteor.call,這是有道理的,我這樣做:

# Client side 
Template.mileage.average_miles = -> 
    answer = null 
    Meteor.call "average_mileage", (error, result) -> 
    console.log "got average mileage result #{result}" 
    answer = result 
    console.log "but wait, answer = #{answer}" 
    answer 

# Server side 
Meteor.methods average_mileage: -> 
    console.log "server mileage called" 
    total = count = 0 
    r = Mileage.find({}).forEach (mileage) -> 
    total += mileage.mileage 
    count += 1 
    console.log "server about to return #{total/count}" 
    total/count 

這似乎做工精細,但它並不因爲靠近我可以告訴Meteor.call是異步調用,並且answer將始終爲空返回。處理服務器上的東西似乎是一個常見的用例,我必須忽略某些東西。那會是什麼?

謝謝!

回答

28

從Meteor 0.6.5開始,集合API不支持聚合查詢,因爲沒有(直接的)方式對它們進行實時更新。但是,您仍然可以自己編寫它們,並使其在Meteor.publish中可用,但結果將是靜態的。在我看來,這樣做還是可取的,因爲您可以合併多個聚合並使用客戶端收集API。

Meteor.publish("someAggregation", function (args) { 
    var sub = this; 
    // This works for Meteor 0.6.5 
    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db; 

    // Your arguments to Mongo's aggregation. Make these however you want. 
    var pipeline = [ 
     { $match: doSomethingWith(args) }, 
     { $group: { 
      _id: whatWeAreGroupingWith(args), 
      count: { $sum: 1 } 
     }} 
    ]; 

    db.collection("server_collection_name").aggregate(  
     pipeline, 
     // Need to wrap the callback so it gets called in a Fiber. 
     Meteor.bindEnvironment(
      function(err, result) { 
       // Add each of the results to the subscription. 
       _.each(result, function(e) { 
        // Generate a random disposable id for aggregated documents 
        sub.added("client_collection_name", Random.id(), { 
         key: e._id.somethingOfInterest,       
         count: e.count 
        }); 
       }); 
       sub.ready(); 
      }, 
      function(error) { 
       Meteor._debug("Error doing aggregation: " + error); 
      } 
     ) 
    ); 
}); 

以上是示例分組/計數聚合。值得注意的一些事情:

  • 當你這樣做,你會很自然地對server_collection_name做一個彙總和推動的結果,所謂client_collection_name不同的集合。
  • 此訂閱不會實時生效,並且每當參數更改時可能會更新,因此我們使用一個非常簡單的循環,將所有結果推出。
  • 聚合的結果沒有Mongo ObjectIDs,所以我們生成一些我們自己的任意的。
  • 聚合的回調需要用光纖封裝。我在這裏使用Meteor.bindEnvironment,但也可以使用Future進行更多低級別控制。

如果您開始合併這些出版物的結果,您需要仔細考慮隨機生成的ID如何影響合併框。但是,直接實現這只是一個標準的數據庫查詢,除了與Meteor APIs客戶端一起使用更方便。

TL; DR版本:幾乎任何時候你推出來自服務器的數據,一個publish最好的method

有關不同聚合方式的更多信息,請參閱check out this post

+2

我不想離開這個答案沒有「謝謝你。」這是一個非常棒的答案。我暫時離開了另一個項目,但安德魯,你明顯地想到了這一點,我非常感激。 –

+0

@SteveRoss不用客氣。感謝您的客氣話! –

+0

優秀聚合示例的榮譽。這是唯一一個爲我工作的人。而且你沒有包裝,MongoInternals和發佈功能就可以做到這一點......爲紅色天鵝絨蛋糕錦上添花。感謝你的分享! – AbigailW

1

您可以使用Meteor.methods

// server 
Meteor.methods({ 
    average: function() { 
    ... 
    return something; 
    }, 

}); 

// client 

var _avg = {      /* Create an object to store value and dependency */ 
    dep: new Deps.Dependency(); 
}; 

Template.mileage.rendered = function() { 
    _avg.init = true; 
}; 

Template.mileage.averageMiles = function() { 
    _avg.dep.depend();    /* Make the function rerun when _avg.dep is touched */ 
    if(_avg.init) {     /* Fetch the value from the server if not yet done */ 
    _avg.init = false; 
    Meteor.call('average', function(error, result) { 
     _avg.val = result; 
     _avg.dep.changed();   /* Rerun the helper */ 
    }); 
    } 
    return _avg.val; 
}); 
+0

所以我快樂地獲得新的輸入值等下去。在什麼時候有一個寫入到服務器。因爲我看到的是,除了客戶機上的數據還沒有傳送到服務器之外,這是可行的。 –

+0

我修改了原來的問題。 –

+0

您可以使用依賴關係在需要的地方創建反應性。我已經更新了我的答案。結果可能有點過於複雜,在這一刻我不確定如何在你的情況下更簡單地做到這一點。 –

1

如果你想要的反應,使用Meteor.publish代替Meteor.call。在docs中有一個例子,他們發佈給定房間的消息數量(正好在this.userId的文檔上方),您應該可以做類似的事情。

+0

所以,反應性似乎很好,因爲每當有人更新他們的里程時,平均變化。所以這將建議根據你所說的發佈/訂閱。但在我看來,pub/sub更像是返回過濾或映射集合,而不是像平均值這樣的標量值。這似乎應該只是一行或兩行代碼 - 你不覺得嗎? –

+0

@SteveRoss我同意這樣一個共同的特徵應該很容易編寫,但Meteor沒有特別的支持(然而,版本6.5),所以替代方案使用方法/調用或發佈/訂閱。考慮到文檔中的類似示例以及反應性的好處,我將使用發佈/訂閱。 –

+0

使用標量值發佈僅包含單個文檔的集合沒有任何問題。 誰知道?也許有一天,你會想要不止一個平均值......然後你可以發佈多個文件:) –

2

我用'聚合'方法做了這個。 (版本0.7.x)

if(Meteor.isServer){ 
Future = Npm.require('fibers/future'); 
Meteor.methods({ 
    'aggregate' : function(param){ 
     var fut = new Future(); 
     MongoInternals.defaultRemoteCollectionDriver().mongo._getCollection(param.collection).aggregate(param.pipe,function(err, result){ 
      fut.return(result); 
     }); 
     return fut.wait(); 
    } 
    ,'test':function(param){ 
     var _param = { 
      pipe : [ 
      { $unwind:'$data' }, 
      { $match:{ 
       'data.y':"2031", 
       'data.m':'01', 
       'data.d':'01' 
      }}, 
      { $project : { 
       '_id':0 
       ,'project_id'    : "$project_id" 
       ,'idx'      : "$data.idx" 
       ,'y'      : '$data.y' 
       ,'m'      : '$data.m' 
       ,'d'      : '$data.d' 
      }} 
     ], 
      collection:"yourCollection" 
     } 
     Meteor.call('aggregate',_param); 
    } 
}); 

}