LanguagesRuby / RailsA Rails Cloud Implementation Using MongoDB and Heroku

A Rails Cloud Implementation Using MongoDB and Heroku

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

When you have mastered how to use the Heroku platform to deploy and manage Rails web applications, you can choose which database or data store to use on the backend. Using a simple Rails app, Note Taker with Search (see the previous article in this series, “Deploying a Rails Application to Heroku“), I will demonstrate how to use the non-relational MongoDB datastore. Why MongoDB? Next to the PostgreSQL relational database (and occasionally the PostGIS geospatial/geolocation extensions), MongoDB is the data storage and management tool that I most often use (with the possible exception of the Sesame RDF data store).


The nosql-databases.org web site provides links to most non-relational data store systems (leaving out only RDF data stores). Check it out if you want to explore other options besides MongoDB. (My next article in this series covers another good NoSQL alternative: CouchDB.) In a nutshell, NoSQL data stores trade off immediate data consistency for greater data storage capacity and resiliency in the face of network partitioning (the “CAP Theorem”). The following two PDFs make a good case for why this tradeoff is sometimes a good design decision:



Save these papers for reference and, after working through the rest of this article, read through them for a better understanding of NoSQL data stores.


The minimal Heroku deployment option (5 megabytes of data storage and one compute unit) is free. For larger deployments, deploying on Heroku is definitely more expensive than managing your own Amazon EC2 instances. For many projects, however, the higher deployment costs can be more than offset by the reduction in development and administration costs. I do a lot of “bare metal” EC2 deployments, and I can attest to the cost and time savings of Heroku because “bare metal” EC2 deployments usually require me to:



  • Manage installation of Ruby, Rails, PostgreSQL, memcached, etc. (easy to do)
  • Manage persistent data stores by attaching EC2 volumes before starting any services that require persistent disk storage (not so easy, and time consuming to set up)
  • Manage the automatic binding of EC2 instances to Elastic IP addresses
  • Bring more servers online quickly when I get traffic spikes (difficult to do)
  • Back up EBS volumes and database dumps to secure S3 storage (easy to do, but does take a little time to set up)

The use case I cover in this article (using MongoDB data stores) requires an external server, but you can find some third-party services that provide managed MongoDB services. I prefer to run MongoDB on my own managed EC2 instance. Note that if you decide to mix Rails deployments on Heroku with your own services running on EC2, you will lose some of the hassle-free admin and deployment advantages of using Heroku.

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.



Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories