6

Grails makes it easy to get a domain object by ID (handy for building a REST API).

A controller to retrieve a resource can be as simple as:

MetricController.groovy

import grails.converters.JSON

class MetricController {

   def index() {
      def resource = Metric.get(params.id)
      render resource as JSON
      }

   }


When using the Grails plugin for MongoDB GORM (compile ":mongodb:1.2.0"), the default id type of Long needs to be changed to type String or ObjectId.

Metric.groovy

import org.bson.types.ObjectId

class Metric {
   static mapWith = "mongo"
   ObjectId id
   String title
   }


However, doing a .get(1) will now result in:

Error 500: Internal Server Error
URI
/bow/rest/metric/1
Class
java.lang.IllegalArgumentException
Message
invalid ObjectId [1]


I took a guess and changed the controller to use findById:

def resource = Metric.findById(new ObjectId(new Date(), params.id.toInteger()))

That fixed the error, but it fails to find the object (always returns null).

For example, using the id of "-1387348672" does not find this test object:

{ "class" : "Metric",
  "id" : { "class" : "org.bson.types.ObjectId",
      "inc" : -1387348672,
      "machine" : 805582250,
      "new" : false,
      "time" : 1371329632000,
      "timeSecond" : 1371329632
    },
  "title" : "Test"
}


The ObjectId.inc field may not even be the correct field to use for the resource ID.

So, what is the simplest way to retrieve a domain object by ID when using MongoDB?

Dem Pilafian
  • 5,625
  • 6
  • 39
  • 67

2 Answers2

4

When a domain object is persisted in MongoDB, it is stored as a document with an ObjectId as a unique 12 bytes BSON primary key. For example, if you have a domain object Product like

import org.bson.types.ObjectId
class Product {
    ObjectId id
    String name

    static mapWith = "mongo"
}

then the persisted entity in MongoDB would look like below if you save with a name as "TestProduct".

//db.product.find() in mongodb
{ 
  "_id" : ObjectId("51bd047c892c8bf0b3a58b21"), 
  "name" : "TestProduct", 
  "version" : 0 
}

The _id becomes your primary key for that document. To get this document you need the primary key ObjectId. Speaking from a RESTful context, you atleast need the 12 bytes hexcode 51bd047c892c8bf0b3a58b21 as part of the request.

So in the above case, you can fetch that particular document by doing something like

Product.get(new ObjectId("51bd047c892c8bf0b3a58b21"))
Product.findById(new ObjectId("51bd047c892c8bf0b3a58b21"))

Have a look at the API for ObjectId which would make clear how to retrieve a document.

When you retrieve the document as JSON, then it just shows the ObjectId class with its elements.

{
    "class": "com.example.Product",
    "id": {
        "class": "org.bson.types.ObjectId",
        "inc": -1280996575,
        "machine": -1993569296,
        "new": false,
        "time": 1371341948000,
        "timeSecond": 1371341948
    },
    "name": "TestProduct"
}
dmahapatro
  • 49,365
  • 7
  • 88
  • 117
  • 1
    Knowing that the ID should be the 12-byte hex BSON key got me pointed in the right direction. It turns out to be quite easy to generate ID strings from ObjectIDs... just use `.toString()`. And then you can pass the ID string directly to `.get()` as long as the ID string is formatted correctly (`[0-9a-f]{24}`). – Dem Pilafian Jun 17 '13 at 23:34
3

For completeness, here's the domain with a controller to get a resource by ID string (instead of ObjectID).

Example:
Metric.get("51bf8ccc30040460f5a05579")

Domain

import org.bson.types.ObjectId

class Metric {

   ObjectId id
   String   title

   static mapWith = "mongo"

   def out() {
      return [
         id:    id as String,  //convert Mongo ObjectId to 12-byte hex BSON
         title: title
         ]
      }

   }

The out() method is used to render the resource showing its ID string (not its ObjectID).

Controller

import grails.converters.JSON

class MetricController {

   def index() {
      def resource = Metric.read(params.id)
      render resource.out() as JSON
      }

   }


Example JSON response for /rest/metric/51bf8ccc30040460f5a05579

{ "id": "51bf8ccc30040460f5a05579", "title": "Accounts" }
Dem Pilafian
  • 5,625
  • 6
  • 39
  • 67