hardscrabble

Didn't Ricky Gervais get in trouble for saying that? On MongoDB

11 Jan 2014

Remember when Ricky Gervais got in trouble for using the word “Mong” a lot to mean “stupid” and a lot of people were mad at him because it’s an offensive thing to say? Anyway, I’ve been using MongoDB and Mongoid at work and I sometimes worry that the very name is kind of rude, but other than that I mostly like it.

My only database experience prior to this was with personal / school projects exclusively using SQLite3 and mostly with ActiveRecord as the interface to that database. Not coincidentally, this is the default Rails stack.

I want to make a simple rails project, a To Do List app becase duh, twice, once with ActiveRecord and once with Mongoid, and just see what kind of observations I have. OK I’m going to do that now. I’m going to use Rails 4.0.2.

First, with ActiveRecord

Getting started by running these commands:

Then editing my freshly generated models to look like so:

# app/models/task.rb
class Task < ActiveRecord::Base
  belongs_to :category
end

and

# app/models/category.rb
class Category < ActiveRecord::Base
  has_many :tasks
end

That should be enough that we can now run bin/rails console and create some data:

# we're in the rails console now
c = Category.new #=> #<Category id: nil, name: nil, created_at: nil, updated_at: nil>
c.name = "Things to buy" #=> "Things to buy"
c.save #=> true
c #=> #<Category id: 1, name: "Things to buy", created_at: "2014-01-11 22:10:47", updated_at: "2014-01-11 22:10:47">
c.tasks #=> #<ActiveRecord::Associations::CollectionProxy []>
c.tasks.build(name: "Buy some spinach") #=> #<Task id: nil, name: "Buy some spinach", complete: nil, category_id: 1, created_at: nil, updated_at: nil>
Task.count #=> 0
c.save #=> true
Task.count #=> 1
c.tasks #=> #<ActiveRecord::Associations::CollectionProxy [#<Task id: 1, name: "Buy some spinach", complete: nil, category_id: 1, created_at: "2014-01-11 22:22:24", updated_at: "2014-01-11 22:22:24">]>
Task.all.class #=> ActiveRecord::Relation::ActiveRecord_Relation_Task

So that whole way works fine. It’s nice.

Again, with Mongoid

The Mongoid Installation page is kind of intimidating:

There are a few things you need to have in your toolbox before tackling a web application using Mongoid.

  • A good to advanced knowledge of Ruby.
  • Have good knowledge of your web framework if using one.
  • A thorough understanding of MongoDB.

This may seem like a “thank you Captain Obvious” moment, however if you believe that you can just hop over to Mongoid because you read a blog post on how cool Ruby and MongoDB were, you are in for a world of pain.

Mongoid leverages many aspects of the Ruby programming language that are not for beginner use, and sending the core team into a frenzy tracking down a bug for a common Ruby mistake is a waste of our time, and all of the other users of the framework as well.

When I started using MongoDB I was kind of freaked because it promised to be so different. But honestly it’s not that different. In a SQL database you need to write your queries using SQL, right? That stuff is so hard to write and inscrutable to read, at least for this blogger. MongoDB queries are written in JavaScript. That’s a major upgrade in my book, even if it were otherwise the same underlying technology. And then, if you’re using Mongoid, you can make another great upgrade by writing your queries in Ruby.

Of the three prerequisite bullet points listed on that page, I think I only disagree with the third one. If you’re completely unfamiliar with MongoDB but fairly familiar with Rails, Ruby, and ActiveRecord I think you can make the leap. That’s what I did. I started out by using Mongoid as if it were ActiveRecord, which is mostly totally possible. Then you can keep exploring the docs and branch out if you want.

One setup prerequisite is that you have MongoDB installed on your system. I think I have it on my laptop. I actually have no idea if I have it. Let’s find out.

Getting started by running these commands:

Now, before I generate my models I need to indicate that this project is going to use Mongoid. To do this I’m going to add the following to my Gemfile:

# Use MongoDB as the database
gem "mongoid", "4.0.0.alpha2"

I’m using the alpha version of Mongoid 4 because… well, I’m sticking with the basics, so why not?

Now let’s continue with these commands:

This is the step in the ActiveRecord version where we ran bin/rake db:migrate. We don’t have to do that now. No migration files have been created. The database is flexible. How does that make you feel? If that sounds totally awesome, MongoDB might be for you. If that freaks you out, probably not.

Let’s take a look at the model files that were just created for us:

# app/models/task.rb
class Task
  include Mongoid::Document
  include Mongoid::Timestamps
  field :name, type: String
  field :complete, type: Mongoid::Boolean
  belongs_to :category # actually this line isn't from the generator, I just added it
end

And:

# app/models/category.rb
class Category
  include Mongoid::Document
  include Mongoid::Timestamps
  field :name, type: String
  has_many :tasks # same as the belongs_to above
end

First kind of interesting difference: instead of your models inheriting functionality from ActiveRecord via subclassing, our models now include functionality from the Mongoid::Document module. Among other things, this gives the class a field method. We use this to define the attributes that this model should have. Unlike ActiveRecord models, which know the attributes they can have by introspecting on their corresponding table in the database2, Mongoid models typically have a list right in the class definition of the name and type of its attributes. Later, when we call category.name = "Homework", Mongoid is dynamically figuring out what we mean based on our list of fields; it gives categories a name= and name method. This centralization appeals to me on an organizational level.

And an interesting similarity: has_many and belongs_to work pretty much exactly the same way. If you’re used to thinking relationally, you can use that here too.

So let’s try to create some data and see how that goes. Into the bin/rails console let’s go.

category = Category.new #=> #<Category _id: 52d1d2036d61633a9b000000, created_at: nil, updated_at: nil, name: nil>
category.name = "Homework" #=> "Homework"
category.save #=> true
category._id #=> BSON::ObjectId('52d1d29c6d61633b1b000000')
category.id #=> BSON::ObjectId('52d1d29c6d61633b1b000000')
category.tasks #=> []
category.tasks.class #=> Mongoid::Relations::Targets::Enumerable
category.tasks.build(name: "Study Mongoid") #=> #<Task _id: 52d1d62d6d61633c05000000, created_at: nil, updated_at: nil, name: "Study Mongoid", complete: nil, category_id: BSON::ObjectId('52d1d29c6d61633b1b000000')>
Task.count #=> 0
category.save #=> true
Task.count #=> 0
category.tasks.last.save #=> true
Task.count #=> 1
category.tasks.create(name: "Study Rails 4") #=> #<Task _id: 52d1d8596d61633cba040000, created_at: 2014-01-11 23:48:41 UTC, updated_at: 2014-01-11 23:48:41 UTC, name: "Study Rails 4", complete: nil, category_id: BSON::ObjectId('52d1d7696d61633cba010000')>
Task.count #=> 2
Task.all.class #=> Mongoid::Criteria

First of all: I guess I did have MongoDB installed and running on my system, who knew?

OK so functionally it’s almost exactly the same right? But there are some interesting differences:

These two Rails apps are available here: https://github.com/hardscrabble/comparing_mongoid_and_active_record

This is a really brief introduction to the basics of Mongoid. I want to dig in more. My laptop battery is at 6%. More to come.

  1. Confession: On my first attempt at this I skipped this flag and it created the application with ActiveRecord and I was trying to replace it and then I was like, hmm, maybe there’s a better way to do this. Unfortunately, there’s no rails new --database=mongodb like there’s a rails new --database=postgresql, among others 

  2. probably my favorite phrase I learned at Flatiron School was “introspecting on the database”