I’m having difficulty creating a MapReduce function in Ruby with MongoDB and mongoid. I’m using the Ruby and MongoDB Web Development Guide, and the content doesn’t seem to be written for Mongoid (even though it was written post-rails 3.2).
Here is a sample JSON document:
> db.books.find()
{ "_id" : ObjectId("50ceb8551d41c8ade7000041"),
"author_id" : ObjectId("50ceb72c1d41c8ade700003d"),
"category_ids" : [ ], "name" : "Great Expectations",
"votes" : [
{ "username" : "Gautam", "rating" : 9 },
{ "username" : "Tom", "rating" : 3 },
{ "username" : "Dick", "rating" : 7 }
]
}
Anyway, I’m having trouble with the mongoid MapReduce. I can run this fine in MongoDB:
> var reduce = function(key, values) {
var result = {rating: 0};
values.forEach(function(value) {
result.rating += value.rating;
});
return result;
};
> var map = function() {
this.votes.forEach(function(x) {
emit(x.username, {rating: x.rating});
});
};
> var results = db.books.mapReduce(map, reduce, {out: "mr_results"});
> results
{
"result" : "mr_results",
"timeMillis" : 28,
"counts" : {
"input" : 2,
"emit" : 6,
"reduce" : 3,
"output" : 3
},
"ok" : 1,
}
> results.find()
{ "_id" : "Dick", "value" : { "rating" : 10 } }
{ "_id" : "Gautam", "value" : { "rating" : 15 } }
{ "_id" : "Tom", "value" : { "rating" : 10 } }
But when I run the following in the Rails Console, I get this:
1.9.3-p327 :292 > map = "function() {
1.9.3-p327 :293"> this.votes.forEach(function(x) {
1.9.3-p327 :294"> emit(x.username, {x.rating});
1.9.3-p327 :295"> });
1.9.3-p327 :296"> }
1.9.3-p327 :297"> "
=> "function() {\nthis.votes.forEach(function(x) {\nemit(x.username, {x.rating});\n});\n}\n"
1.9.3-p327 :298 > reduce = "function(key, values) {
1.9.3-p327 :299"> var result = {rating: 0};
1.9.3-p327 :300"> values.forEach(function(value) {
1.9.3-p327 :301"> result.rating += value.rating;
1.9.3-p327 :302"> });
1.9.3-p327 :303"> return result;
1.9.3-p327 :304"> }
1.9.3-p327 :305"> "
=> "function(key, values) {\nvar result = {rating: 0};\nvalues.forEach(function(value) {\nresult.rating += value.rating;\n});\nreturn result;\n}\n"
1.9.3-p327 :306 > results = Book.where(votes: 1).map_reduce(map,reduce).out(inline: 1)
=> #<Mongoid::Contextual::MapReduce
selector: {"votes"=>1}
class: Book
map: function() {
this.votes.forEach(function(x) {
emit(x.username, {x.rating});
});
}
reduce: function(key, values) {
var result = {rating: 0};
values.forEach(function(value) {
result.rating += value.rating;
});
return result;
}
finalize:
out: {:inline=>1}>
1.9.3-p327 :307 > results.find()
=> #<Enumerator: #<Mongoid::Contextual::MapReduce
selector: {"votes"=>1}
class: Book
map: function() {
this.votes.forEach(function(x) {
emit(x.username, {x.rating});
});
}
reduce: function(key, values) {
var result = {rating: 0};
values.forEach(function(value) {
result.rating += value.rating;
});
return result;
}
finalize:
out: {:inline=>1}>
:find>
Or, if I change the out result to “mr_results” this is what happens:
1.9.3-p327 :383 > results = Book.map_reduce(map,reduce).out("mr_results")
NoMethodError: undefined method `update_values' for "mr_results":String
from /home/BSP/.rvm/gems/ruby-1.9.3-p327/gems/mongoid-3.0.14/lib/mongoid/contextual/map_reduce.rb:134:in `out'
from (irb):383
from /home/BSP/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands/console.rb:47:in `start'
from /home/BSP/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands/console.rb:8:in `start'
from /home/BSP/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
How can I make this output the expected results from the book votes?
The
outfunction expects a Hash, not a string: