2013-08-29 54 views
15

Ok, vẫn còn trong ứng dụng đồ chơi của tôi, tôi muốn tìm ra số dặm trung bình trên một nhóm đồng hồ đo của chủ xe. Điều này khá dễ dàng trên máy khách nhưng không mở rộng. Đúng? Nhưng trên máy chủ, tôi không thấy chính xác cách thực hiện nó.Truy vấn tập hợp trung bình trong Meteor

Câu hỏi:

  1. Làm thế nào để bạn thực hiện một cái gì đó trên máy chủ sau đó sử dụng nó trên máy khách?
  2. Làm cách nào để bạn sử dụng hàm tổng hợp $ avg của mongo để tận dụng chức năng tổng hợp được tối ưu hóa của nó?
  3. Hoặc cách khác để (2) làm thế nào để bạn làm một bản đồ/giảm trên máy chủ và làm cho nó có sẵn cho khách hàng?

Các gợi ý bởi @HubertOG là sử dụng Meteor.call, có ý nghĩa và tôi đã làm điều này:

# 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 

Điều đó dường như làm việc tốt, nhưng nó không phải vì như gần như tôi có thể cho biết Meteor.call là cuộc gọi không đồng bộ và answer sẽ luôn là số không trả lại. Xử lý các công cụ trên máy chủ có vẻ giống như một trường hợp sử dụng đủ phổ biến mà tôi phải có chỉ cần bỏ qua một cái gì đó. Điều đó sẽ là gì?

Cảm ơn!

Trả lời

28

Kể từ Meteor 0.6.5, API thu thập không hỗ trợ truy vấn tổng hợp vì không có cách nào (đơn giản) để cập nhật trực tiếp trên chúng. Tuy nhiên, bạn vẫn có thể tự viết chúng và làm cho chúng sẵn có trong một Meteor.publish, mặc dù kết quả sẽ là tĩnh. Theo tôi, làm theo cách này vẫn còn thích hợp hơn vì bạn có thể hợp nhất nhiều tập hợp và sử dụng API thu thập phía máy khách.

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); 
      } 
     ) 
    ); 
}); 

Ở trên là tổng hợp nhóm/số ví dụ. Một số điều cần chú ý:

  • Khi bạn làm điều này, bạn sẽ tự nhiên được làm một tập hợp trên server_collection_name và đẩy kết quả cho một bộ sưu tập khác nhau gọi là client_collection_name.
  • Đăng ký này sẽ không hoạt động và có thể sẽ được cập nhật bất cứ khi nào các đối số thay đổi, vì vậy chúng tôi sử dụng vòng lặp thực sự đơn giản chỉ đẩy tất cả kết quả ra.
  • Kết quả của tập hợp không có ObjectID Mongo, vì vậy chúng tôi tạo ra một số tùy ý của riêng mình.
  • Gọi lại tập hợp cần phải được bao bọc trong sợi quang. Tôi sử dụng Meteor.bindEnvironment tại đây nhưng cũng có thể sử dụng một số Future để kiểm soát mức độ thấp hơn.

Nếu bạn bắt đầu kết hợp kết quả của các ấn phẩm như thế này, bạn sẽ cần phải xem xét cẩn thận cách các id được tạo ngẫu nhiên tác động đến hộp hợp nhất.Tuy nhiên, việc triển khai thực hiện đơn giản này chỉ là một truy vấn cơ sở dữ liệu tiêu chuẩn, ngoại trừ việc thuận tiện hơn khi sử dụng với phía máy khách API của Meteor.

TL; DR phiên bản: Hầu như bất cứ lúc nào bạn đang đẩy dữ liệu ra khỏi máy chủ, publish thích hợp hơn là method.

Để biết thêm thông tin về các cách khác nhau để tập hợp, check out this post.

+2

Tôi không muốn để lại câu trả lời này mà không có một "cảm ơn bạn". Đó là một câu trả lời hoàn toàn tuyệt vời. Tôi đã bị lôi kéo vào một dự án khác tạm thời, nhưng Andrew, bạn rõ ràng đã đặt rất nhiều suy nghĩ vào điều này và tôi rất cảm kích. –

+0

@SteveRoss bạn được chào đón. Cảm ơn những lời tốt đẹp! –

+0

Kudo cho ví dụ tổng hợp tuyệt vời. Đó là người duy nhất làm việc cho tôi. Và bạn đã quản lý nó mà không cần gói, với MongoInternals, và trong một chức năng xuất bản ... đóng băng trên chiếc bánh nhung đỏ. Cảm ơn bạn đã chia sẻ! – AbigailW

1

Bạn có thể sử dụng Meteor.methods cho điều đó.

// 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

Vì vậy, tôi hạnh phúc đi cùng nhận giá trị đầu vào mới và vân vân. Tại thời điểm nào có một ghi thông qua máy chủ. Cuz những gì tôi thấy là điều này sẽ làm việc ngoại trừ các dữ liệu trên máy khách chưa thực hiện nó đến máy chủ. –

+0

Tôi đã sửa đổi câu hỏi gốc. –

+0

Bạn có thể sử dụng các phụ thuộc để tạo ra phản ứng khi cần. Tôi đã cập nhật câu trả lời của mình. Kết quả có thể là một chút quá phức tạp, tại thời điểm này tôi không chắc chắn làm thế nào để làm điều đó đơn giản trong trường hợp của bạn. –

1

Nếu bạn muốn phản ứng, hãy sử dụng Meteor.publish thay vì Meteor.call. Có một ví dụ trong số docs nơi họ xuất bản số lượng thư trong một phòng nhất định (ngay phía trên tài liệu cho this.userId), bạn sẽ có thể làm điều gì đó tương tự.

+0

Vì vậy, phản ứng có vẻ tốt bởi vì bất cứ khi nào ai đó cập nhật số dặm của họ, những thay đổi trung bình. Vì vậy, đó sẽ đề nghị xuất bản/đăng ký theo những gì bạn nói. Nhưng có vẻ như với tôi rằng pub/sub là nhiều hơn để trả về các bộ sưu tập được lọc hoặc ánh xạ so với các giá trị vô hướng như trung bình. Điều này có vẻ như nó chỉ nên là một dòng hoặc hai mã - bạn sẽ không nghĩ sao? –

+0

@SteveRoss Tôi đồng ý rằng một tính năng phổ biến như thế này nên dễ viết, nhưng Meteor không có hỗ trợ đặc biệt cho nó (chưa, phiên bản 6.5), vì vậy các lựa chọn thay thế đang sử dụng các phương thức/cuộc gọi hoặc xuất bản/đăng ký. Với ví dụ tương tự trong tài liệu, và lợi ích của phản ứng, tôi sẽ đi với xuất bản/đăng ký. –

+0

Không có gì sai khi xuất bản bộ sưu tập chỉ chứa một tài liệu duy nhất với giá trị vô hướng của bạn. Và ai biết được? Có lẽ một ngày bạn sẽ muốn nhiều hơn một trung bình ... và sau đó bạn có thể xuất bản nhiều tài liệu :) –

2

Tôi đã làm điều này với phương pháp 'tổng hợp'. (Ver 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); 
    } 
}); 

}