November 27, 2014
Hot Topics:

A Rails Cloud Implementation Using MongoDB and Heroku

  • January 25, 2010
  • By Mark Watson
  • Send Email »
  • More Articles »

Replacing PostgreSQL with MongoDB

This section walks you through replacing PostgreSQL with MongoDB a simple Rails app: Note Taker with Search. The code download for this article contains all the examples in the directory note_taker_mongodb, and you should extract them and work along with me through every example. As you will see, the implementation is not as easy as simply using ActiveRecord and a relational database. However, it's worth the effort, because document data stores are a natural fit for handling structured data and they tend to scale well. You usually sacrifice immediate consistency, though (i.e., data store write operations may not be available for reading for a small time interval).

To start the PostgreSQL-to-MongoDB replacement, make a copy of the note_taker_pg directory and name it note_taker_mongodb. Then, edit the .gems file to specify the gems:

mongo
mongo_ext
mongo_record


You will also need to install these gems on your system. I then edited the environment.rb file making two changes. The first is to specify required gems:

  config.gem 'mongo'
  config.gem 'mongo_record'


The next thing you need to address is how you will manage allowed user names and passwords. I considered storing these in MongoDB, but that is probably not a feasible scenario for a real application. While great for storing structured data, document-centric data stores like MongoDB and CouchDB should often be used with relational databases, in my opinion. You use the relational database to store information that is not deeply structured and/or needs transaction support. So, at the risk of slightly complicating this example, let's use PostgreSQL to store user names and passwords and MongoDB to store notes.

Set up your MongoDB connections to use two environment variables. If these environment variables are not set, then the connection defaults to localhost and uses the default MongoDB port. Here is the setup that I added to the bottom of environment.rb:

# setup for MongoDB
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Connection::DEFAULT_PORT
MongoRecord::Base.connection = Mongo::Connection.new(host,port).db('notes')
DB = MongoRecord::Base.connection
if ENV['MONGODB_USER'] && ENV['MONGODB_PASS']
  auth = DB.authenticate(ENV['MONGODB_USER'], ENV['MONGODB_PASS'])
  puts "Authenticated" if auth
  puts "Not authenticated" if !auth
end


I used four environment variables to specify the server information and the account name and password for authentication. This lets me easily switch between using a MongoDB on my laptop, using a commercial service like MongoHQ.com, or on one of my EC2 instances.

I have modified the Note class to use MongoDB instead of PostgreSQL:

class Note < MongoRecord::Base
  collection_name :notes
  fields 'user_id', :title, :content, :words


The MongoRecord::Base class supplies two class methods, collection_name and fields, that you use to specify the MongoDB collection name that you will use to:

  1. Store notes
  2. Define the fields (attributes) that you will store in each document stored in the specified MongoDB collection

The following code creates a connection to the collection 'notes'.

  def to_s
    "note: #{title} content: #{content[0..20]}..."
  end
  def Note.make user_id, title, content
    @collection ||= DB.collection('notes')
    @collection.insert({:_id => Mongo::ObjectID.new, :title => title,
                       'user_id' => user_id, :content => content,
                       :words => (title + ' ' + content).split.uniq})
  end


If multiple mongrels are used to run this web application (or, in the case of deploying to Heroku, multiple dynos), then each will create one connection. For each note, I collect all the words used in the note and add the list of words to the document.

The following code snippet shows how to use the word list in each note document to implement search:

  def Note.search query
    tokens = query.split
    score_hash = Hash.new(0)
    tokens.each {|token|
      Note.find(:all, :conditions => {:words => /^#{token}/i}).each { |note| score_hash[note] += 1 }
    }
    score_hash.sort {|a,b| a[1] <=> b[1]}
  end
end


I implemented a class method search that you will use in the Notes controller class. The class MongoRecord::Base provides a query API that is similar to that provided by ActiveRecord. The program looks for all note documents that contain words in the search query. Notice that the regular expression that prefix-matches words in the document. For example, if a document contains the word "container", then searches for "contain", "cont", etc. will all match.

Except for the changes I discussed in this section for MongoDB, this is a plain scaffold Rails application.

MongoDB on Your Own Server or a Hosted MongoDB Service?

A good alternative to running MongoDB on your own server is to avoid installing MongoDB all together and use a commercial service like MongoHQ.com.

I usually run MongoDB on my MacBook and on two of my servers. (I always keep MongoDB always running on my MacBook; it starts as a service.) I change the following environment variables to switch between available servers:

export MONGO_RUBY_DRIVER_HOST=xxxxxxx.com
# export MONGO_RUBY_DRIVER_PORT  # not used: use default port
export MONGODB_PASS=password
export MONGODB_USER=notessclient


The following interactive session shows how to customize the configuration variables for a Heroku-deployed Rails application:
$ heroku config:add MONGO_RUBY_DRIVER_HOST=xxxxxxx.com MONGODB_PASS=password MONGODB_USER=notessclient
Adding config vars:
  MONGO_RUBY_DRIVER_HOST => xxxxxxx.com
  MONGODB_PASS => password
  MONGODB_USER => notessclient
Restarting app...done.


Wrapup

I enjoy doing "bare metal" deployments to leased servers or VPS solutions like Amazon EC2, RimuHosting, Slicehost, etc. That said, sometimes it simply does not make economic sense to create custom deployments and administer your own servers. In those cases, three options are available:

  1. Use your own servers
  2. Use cloud deployment servers
  3. Use a hybrid of cloud services and your own servers

I hope this article provided you with the information necessary to make a wise deployment choice. You also have a few deployment and MongoDB tricks in your toolbox now.

Code Download

For Further Reading

About the Author

Mark Watson is a consultant living in the mountains of Central Arizona with his wife Carol and a very feisty Meyers Parrot. He specializes in web applications, text mining, and artificial intelligence development. He is the author of 16 books and writes both a technology blog and an artificial intelligence blog.


Tags: Ruby on Rails, Ruby, cloud computing, Heroku, cloud services



Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel