Jan 05, 2011 / rails ~ mongodb ~ mongomapper
MongoMapper Extract Superclass Refactoring

I was recently wanting to do an Extract Superclass refactoring in a Ruby on Rails solution which uses MongoDB and the glorious MongoMapper. Being somewhat new to MongoDB and MongoMapper, I was a bit scared about this refactoring. I also had collections full of data that I didn't want to screw up. I knew it would require massaging those documents, and also playing to the way MongoMapper handled inheritance.

Fortunately, it was a piece of cake.

Step 1: Change Ruby Classes

Step one was to do the Extract super class in the Ruby MongoMapper classes. Starting with two separate classes:

  class Dog
    include MongoMapper::Document
    key :name, String
    key :bones, Integer
  end

  class Cat
    include MongoMapper::Document
    key :name
    key :fur_balls, Integer
  end

...you can extract the superclass to give this:

  class Animal
    include MongoMapper::Document
    key :name, String
  end

  class Dog < Animal
    key :bones, Integer
  end

  class Cat < Animal    
    key :fur_balls, Integer
  end

Step 2: Update Mongo DB

Now, what about Mongo? If you're in development and can drop/re-create your database then great, just run your boot script. But what about if you need to keep the existing stuff? You need to somehow merge the collections into a single hierarchy. It turns out to be pretty easy.

Firstly, logging into the mongo shell and looking at the collections will give this

  > mongo -ublah -pblah some.server.com:27085/my-db
  > show collections
  cats
  dogs

So, first off we need to create a collection for Animals.

 > db.createCollection("animals")

Now, since Cat and Dog are a subclass of animals, we need to move them into the animals collection, and also let MongoMapper know that they are a subclass. This is done like so in your mongodb shell.

  var cursor = dogs.find(); 
  while(cursor.hasNext()){
    var doc = cursor.next();
    doc["_type"] = "Dog";
    db.animals.insert(doc);
  }

  var cursor = cats.find(); 
  while(cursor.hasNext()){
    var doc = cursor.next();
    doc["_type"] = "Cat";
    db.animals.insert(doc);
  }

Basically, this just loops through the dogs and cats adding them to the animals collection. But, we also add the type field needed by MongoMapper so it knows what Ruby class to hydrate the MongoDB JSON structure into.

Step 3: Done!

You should probably tidy up and drop your old "cat" and "dog" collections too.

  > db.dogs.drop()
  > db.cats.drop()

TaDa! We're done.


You may also like...