In our Rails 3 application we use mongo to store logging of critical actions. At first we did not store a separate timestamp, since the _id (which is a BSON::ObjectId contains a timestamp as well. Our model, simplified, looked like this:

  class Log
    include Mongoid::Document

    # mongo id's contain timestamps, 4 bytes = epoch
    def timestamp
      Time.at([id.to_s].pack("H8").unpack("N")[0])
    end
  end

This is all fine and dandy, but when we wanted to build some reporting, of course we were unable to filter and query based upon date.

Mongoid has an easy way to add timestamp fields, and will add the timestamp to all the newly created documents for you. Inside your model just add:

  include Mongoid::Timestamps::Created

We only need to track the creation-time, since we are not interested in any updates. Including Mongoid::Timestamps will add and maintain both created_at and updated_at.

Now all that remained was adding the created_at field to all existing data. We needed reporting, but also on the existing data. Luckily, the value of the field was known, using the timestamp hidden in the id. Secondly, building a script to add and populate the field was also not too hard. This gist was my inspiration, but unlike that script, I was able to use the higher level interface of Mongoid.

# To allow querying on time-ranges, we need to add the created_at field.
# Querying on the `timestamp` does not seem possible, which is not completely surprising
# as it is (if i understand correctly) a part of the `_id` field.
#
# As a one time operation, we iterate over all documents and add the created_at field
# This script has to be run in the Rails environment, please use :
#
#     rails runner script/convert_mongo_add_created_at.rb
#

COLLECTIONS = ["your-collection"] # Put a list of collection names here


def convert_collection(collection)
  skipped_docs = 0
  all_converted_docs = 0

  Audit::Log.all.each do |doc|
    unless doc.respond_to?(:created_at) && doc.created_at.present?
      doc[:created_at] = doc.timestamp
      doc.save
      all_converted_docs +=1
    else
      skipped_docs += 1
    end
  end

  puts "  added :created_at to #{all_converted_docs} documents [skipped: #{skipped_docs}]"
  puts "Converted #{collection}"
end

puts "Start conversion ..."
@db = Mongoid.database

COLLECTIONS.each do |collection|
  convert_collection collection
end