Riding Rails 4 Along With Mongoid and Ruby 2.0
You’re not without knowing that Rails 4 is about to kick in in a few days (weeks?), are you? In a recent hobby project of mine I tested out MongoDB, together with Mongoid (the ORM that comes in replacement of ActiveRecord for querying the MongoDB database) and other pretty cool stuff. As I had a great time using it I wanted to give it a try using the all new and shiny Rails 4 and Ruby 2.
If you have a basic understanding of how the framework works, you have already been following some sort of “Getting Started with Ruby on Rails” tutorial (plenty of them out there) and now you want to try MongoDB and see how it feels, this tutorial is made for you.
Note: I am not going to list the new features of Rails 4, if you want to know more, check out this great post: digging-into-rails-4
All the resources and documentation stuff are at the end of the post, be sure to check them out, you’ll probably learn very interesting stuff and subtleties.
I ran into some compatibility issues when writing this tutorial (nothing to be worried about) but gems versions may now have evolved.
FYI: Ubuntu 12.10, rails 4.0.0.rc1, mongoid (github master, something between 3.1.4 and ?) and bson_ext 1.8.6
EDIT: as stated by Shunwen in the comments below, bson_ext seems to no longer be required as of mongoid version 3. Check it out here: http://mongoid.org/en/mongoid/docs/upgrading.html.
Application github repo: https://github.com/Raindal/rails4_mongoid
Finally, I am not going to give you any advice on why you should or should not use MongoDB, lots of topics are listing pros and cons compared to traditional relational databases such as MySQL. To give you some leads you could dig into, MongoDB brings to the table things like dynamic fields, embedding, map/reduce, indexing and so on.
But enough talking, let’s dive in!
Install MongoDB
First you’ll need to import the 10gen public GPG key (MongoDB is mainly maintained by 10gen), which is used to ensure package consistency and authenticity
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
Create a /etc/apt/sources.list.d/10gen.list file using the following command
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/10gen.list
Now you can update your repository
sudo apt-get update
And then install the package
sudo apt-get install mongodb-10gen
To check that it works, just issue a mongo
in your shell and you should see something like
MongoDB shell version: x.x.x
connecting to: test
>
Set up RVM
Now we are going to set up RVM to use Ruby 2.0.0. We only have to install the Ruby version we want and use it.
rvm install 2.0.0
rvm use 2.0.0
And finally we are going to create a gemset for our application, it will contain all the gems we need
rvm gemset create rails4_mongoid
Set up the application
First we need to install the Rails 4 gem.
gem install rails --version 4.0.0.rc1 --no-ri --no-rdoc
Now we can create the application and cd inside
rails new rails4_mongoid
cd rails4_mongoid/
For convinience, we are creating a .rvmrc file at the root of our application. The file will tell RVM to use the right version of Ruby with the right gemset when we cd inside the directory.
Just use your favorite text editor
nano .rvmrc
And paste this inside
1
|
|
This basically tells RVM to use Ruby 2.0.0 with the gemset rails4_mongoid and that it should create the gemset if it doesn’t already exist.
To make sure everything works as expected, you can go back and then cd again in the directory.
cd ..
cd rails4_mongoid
You should see something like this
Using /home/neil/.rvm/gems/ruby-2.0.0-p0 with gemset rails4_mongoid
Now, we have to edit the Gemfile to install the gems we want.
First, remove sqlite: delete these lines
1 2 |
|
Now we can add mongoid and bson_ext
1 2 |
|
At the time I’m writing these lines, mongoid 3.1.4 relies on ActiveModel 3.2 but we are using ActiveModel 4.0.0-rc1, so we need to use directly
the github repo that supports it.
bson_ext is a C extension which basically makes things faster with MongoDB.
Now, just run a
bundle update
If you run into errors of some kind (mine was related to bson_ext), try to update your gem version with
rvm rubygems latest --verify-downloads 1
And bundle update
again.
Configure Mongoid
Still in the root of the application, run the following command
rails g mongoid:config
This will generate a mongoid.yml file under config/.
We can edit the file to suit our needs.
Find the line that says # consistency: :eventual
and change it for
1
|
|
:eventual
will send reads to secondaries, :strong
sends everything to master.
As we are not using ActiveRecord, we need to get rid of it.
First in config/application.rb comment out the line
1
|
|
And add these lines right underneath the first one
1 2 3 4 |
|
The 3rd line is commented out because normally you would need it but Rails 4 has extracted the ActiveResource gem and now you need to specify its name in the Gemfile if you want to use it. But here we won’t use it.
There are some more ActiveRecord settings we have to get rid of out there.
In config/environments/development.rb, comment out this line
1
|
|
To check that everything is working like a charm, run a rails s
and then go check that the application is running on http://localhost:3000.
Now you should see the usual Welcome aboard message.
Now we’re talking!
Let’s write some code, finally! But first we will have to generate our first model. For that we are going to use the builtin scaffold command in order to generate our model, controller, views and everything else at the same time.
In a real project, unless you’re sure you’re going to use all the generated files, I personnaly don’t recommend using the scaffold command. You could quickly end up with a messy application. I would rather recommend using each command one by one:
rails g model xxx
,rails g controller xxx
etc.
For this example I’ll be using the usual Post > Comment stuff which is actually a perfect example for demonstrating the embedding capabilities of MongoDB.
Let’s generate our Post first, it will have 3 fields: a title, a body and it can be starred.
rails g scaffold post title:string body:string starred:boolean
This generated all the files we wanted.
Let’s first edit the model to add the created_at and updated_at fields that are not present by default.
We can also add an index on the starred field because we will often be querying on it with a where
clause for example (not in this application actually).
Your Post model should look like so
1 2 3 4 5 6 7 8 9 10 |
|
Now we have a command to enforce indexes on the database
rake db:mongoid:create_indexes
This will set an index on the starred field of the post collection.
Quick tip: use
rake -T
to see a list a available tasksWait… we didn’t create the database, did we? No we didn’t, the database is automatically created.
For some reason, Rails 4 uses the update()
method by default to update an object in the corresponding controller action which does not behave as one could expect (and basically does not update
the document). This may be due to mongoid… Anyway, let’s use update_attributes()
instead.
In the recently created controller, change the update action to look like this one
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
We actually do not need all the json stuff because we are not writing some sort of API but the scaffold
command generates it for us.
Now if you navigate to http://localhost:3000/posts you should be able to play with the posts. : )
Go ahead, create/update/destroy a few posts to see if everything is working as expected.
Embedding documents
A collection can be compared to a table.
A document can be compared to a row.
Here I’ve decided to show you how to embed documents in each other because this is one of the features that you will probably wind up using the most.
In MongoDB you can associate two documents with “foreign keys” although the concept does not really exists. So you can still use the usual stuff: has_many
, belongs_to
and so on.
But, there is no join is MongoDB, loading one document and its associated document will therefore execute two queries.
However you can set another type of relation: you can embed one document into another. Loading the parent document will also load all the children documents at the same time.
But, it will always instanciate all of the objects retrieved even if you do not need the children, plus, each document has a size limit: 16MB, if you have too many child
documents, the parent document’s size could exceed this limit.
Foreign key = 2 queries
Embedding = loading many objects all the time + parent document size limited
We suppose that when we load a post, we also want all of its associated comments and that there will not be thousands of comments.
So embedding seems to be a good choice.
Here is a design example of a MongoDB database with users that have posts and posts that have comments.
users collection
[
{
"_id": "5063114bd386d8fadbd6b004",
"name": "John Snow"
}
]
posts collection
[
{
"_id": "6563521bd386d6dadbd6b002",
"title": "Why King's Landing should belong to Daenerys",
"user_id": "5063114bd386d8fadbd6b004",
"comments": [
{
"_id": "4586521bd638d6dadbd7b003"
"body": "I totally agree with you, great post!"
},
{
"_id": "8526521bd654d6dadbd7b001"
"body": "I'm so sad since Drogo died..."
}
]
}
]
This being said, let’s get started, shall we?
rails g scaffold comment body:string
Now that our Comment model is generated, we can edit it to reflect this
1 2 3 4 5 6 7 8 |
|
This just tells MongoDB that comments are to be embedded in the corresponding post.
The inverse_of
option is required in order to tell Mongoid what the comment should be embedded through.
We can edit the Post model to reflect that association and add the embeds_many :comments
(line 5)
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now we need to edit the routes file to make posts and comments nested resources.
1 2 3 |
|
This creates urls like /posts/:post_id/comments.
Now we have to change our comments_controller.rb and all our comments’s views to reflect these nested resources: any comment we will be using now depends on a post.
Note: this part is not related to MongoDB or Mongoid. We would have gone through the same steps anyway with nested resources.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
|
What changed? Well, now we use the post passed in params to load, create, redirect comments.
See the load_post method, this is the root of everything else. We load a post using the :post_id that is sent and then we use this post everywhere.
Quick tip: issue a
rake routes
to see the routes you can use in the application
Now we need to change these routes in all the view files. Here I’ll show you only the lines that have changed.
1 2 3 4 5 6 7 |
|
1 2 3 |
|
1 2 3 |
|
1 2 3 4 |
|
1 2 3 4 |
|
Now return in your running application and create a new post.
Click on the show link to view the post you just created and just append comments to the url, like so: http://localhost:3000/posts/:post_id/comments.
That’s it! You should now be able to create comments and manage them with all the basic usual actions.
I hope this little introduction to Rails 4 with Mongoid helped you.
See you around!
Resources
- Why you should use RVM
- Digging into Rails 4
- Install MongoDB on Ubuntu
- MongoDB limits
- Mongoid installation
- Mongoid document field types
- Indexing with Mongoid
- If you don’t know what are the new “concerns” folders in Rails 4: Put chubby models on a diet with concerns
- Nested resources