|
|
.ME |
|
Using MongoDB from a Grails application is easy and a lot of fun! After reading the manual I wanted to find out more about the schemaless and dynamic nature of MongoDB. The way to do that is use the Low Level API. The one that comes with the MongoDB plugin is based on GMongo. Before you can try this you have to have MongoDB running (read here how to do that) and have a Grails application with the MongoDB plugin present (read here how to do that).
// connect to a MongoDB instance and use 'school'
def mongodb = mongo.getDB("school")
//clear all existing data
mongodb.getCollection("classes").drop();
//insert 3 documents where each document represents a class
mongodb.classes << [
[ name: 'Sixth Grade', year: 2011,
teacher: [name: 'A. Burrows', age: 35],
students:[
[name:'Sam', age: 11],
[name:'Pat', age: 11],
[name:'John', age: 12]
]
],[ name: 'Seventh Grade', year: 2011,
teacher: [name: 'H. Sedin', age: 37],
students:[
[name:'Sven', age: 12],
[name:'Randy', age: 12],
[name:'Fred', age: 14]
]
],[ name: 'Eighth Grade', year: 2011,
teacher: [name: 'M. Malhotra', age: 33],
students:[
[name:'Wanda', age: 16],
[name:'Jesse', age: 16]
]
]
]
// list name and year of classes
render "<h1>classes:</h1>"
mongodb.getCollection("classes").find().each {
render "${it.name} (${it.year})<br />"
}
// list name and age of teachers
render "<h1>teachers</h1>"
mongodb.getCollection("classes").find().each {
render "${it.teacher.name} (age: ${it.teacher.age})<br />"
}
// list name and age of students
render "<h1>students</h1>"
mongodb.getCollection("classes").find().each {
it.students.each { student ->
render "${student.name} (age: ${student.age})<br />"
}
}
// list avg age of students per grade
def classes = mongodb.getCollection("classes");
// set the map headers to be returned
def key = new BasicDBObject()
key.put("name", true);
key.put("year", true);
key.put("students", true);
key.put("age", true);
key.put("avg", true);
// filter the map
def cond = new BasicDBObject();
// only include students with the age 4 or higher
cond.put("students.age", new BasicDBObject('$gte', 4))
// set the initial values before starting the calculations
def initial = new BasicDBObject();
initial.put("students", 0);
initial.put("age", 0);
initial.put("avg", 0);
// set the reduce (javascript) function
def reduce = """
function(obj,prev) {
for (key in obj.students){
if (Number(obj.students[key].age) >= 1) {
prev.students >= 1 ? prev.students += 1 : prev.students = 1;
prev.age += Number(obj.students[key].age);
}
}
}"""
// set the finalalize (javascript) function
def finalize = """
function(prev) {
if (prev.age > 0 && prev.students > 0){
prev.avg = prev.age / prev.students;
}
}"""
// execute the group command
def result = classesCollection.group(
key, cond, initial, reduce, finalize
)
// output the results
render "<h1>age of students per grade/year</h1>"
result.each {
render """
${it.name} had
${it.students} students
with an avg age of ${it.avg.round(2)}
(in: ${it.year as int})
"""
}
There are a couple of things you have to keep in mind when using the MongoDB Plugin of Grails:
1. Conditions examples are often written like this:
def cond = new BasicDBObject();
cond.put(“students.age”, new BasicDBObject(”$gte”, 4))
but in Groovy/Grails this fails as ”$..” will result in a GString… you should use ’$..’ instead, like this:
def cond = new BasicDBObject();
cond.put(“students.age”, new BasicDBObject(’$gte’, 4))
2. Conditions, is it AND or is it OR:
When we want all students where age >= 10 or age <= 20…
WRONG WAY
this will return all ages as one of the conditions will always be TRUE
def cond = new BasicDBObject();
cond.put(“age”, new BasicDBObject(‘$gte’, 10));
cond.put(“age”, new BasicDBObject(‘$lte’, 20));
RIGHT WAY
this will return all students with age 10..20
def cond = new BasicDBObject();
cond.put(“age”, new BasicDBObject(‘$gte’, 10).append(‘$lte’, 20));
Links that may help!
http://www.mongodb.org/display/DOCS/SQL+to+Mongo+Mapping+Chart
http://devcheatsheet.com/tag/mongodb/
http://www.ibm.com/developerworks/java/library/j-javadev2-12/index.html