In this article, which intends to be the follow-up of Introduction to CouchDB, we will focus on the steps necessary to relate this document-oriented database to a written application using Ruby on Rails.
We will learn to know CouchRest; a valid alternative to ActiveRecord capable of interfacing with CouchDB without invalidating the MVC pattern that underlies this Web framework.
Although the entire architecture is far from mature, the potential that it already expresses is such that it justifies its implementation and study; we will soon see a strengthening of these technologies, which will become de-facto standards for the resolution of a whole series of problems linked to inhomogeneous data.
CouchRest: CouchDB on Ruby
CouchRest offers a series of methods for communicating with CouchDB using Ruby. To install it, simply type:
<pre class=”brush: php; html-script: true”>
gem install jchris-couchrest -a http://gems.github.com
</pre>
At this point, performing an irb session and typing:
<pre class=”brush: php; html-script: true”>
>> require ‘rubygems’
=> false
>> require ‘couchrest’
=> true
</pre>
we will be able to experiment some of the features of this interesting gem; for example by running:
<pre class=”brush: php; html-script: true”>
>> db = CouchRest.database! (“http: // localhost: 5984 / travels”)
=> # <CouchRest :: Database: 0x1013f0438 …
</pre>
we will associate the dbdatabase connection handler with the variable travel(the exclamation point, we have called ‘database!’ and not ‘database’, make sure that the database is created in case it does not exist).
As it is easy to notice, the identifier of the connection is specified by an HTTP url, this because CouchDB communicates in JSON through a RESTful interface over HTTP protocol; the CRUD operations are, therefore, as we shall see, comparable in fact to the classic management of a Rails resource.
Once the connection handler has been obtained it becomes very easy to perform the classic actions of saving, modifying, and recovering the saved data ; let’s make an example:
<pre class=”brush: php; html-script: true”>
>> attributes = {“type” => “Travel”, “destination” => “Madrid”, “price_per_person” => 1200, “updated_at” => Time.now}
=> {“updated_at” => …
>> result = db.save_doc (attributes)
=> {“rev” => “2-1128495433”, “id” => “8efc1ef0a44584a02b2dbbf77cbf1f37”, “ok” => true}
>> record = db.get (result [‘id’])
=> {“_rev” => “1-1128495433”, “updated_at” => “2009/10/11 14:36:40 +0000”, “destination” => “Madrid”, “_id” => “8efc1ef0a44584a02b2dbbf77cbf1f37 “,” price_per_person “=> 1200,” type “=>” Travel “}
>> record [“destination”] = “Barcelona”
=> “Barcelona”
>> result = db.save_doc (record)
=> {“rev” => “3-1888613028”, “id” => “8efc1ef0a44584a02b2dbbf77cbf1f37”, “ok” => true}
</pre>
Here’s what happens in these few lines of code:
The instruction save_doctells CouchDB the need to save a document; if within the hash of the attributes there is not the key _id(as in this case) the document is created, otherwise the document with id corresponding to that of the key _idis updated, provided that the value of the key _revis the same on CouchDB that in the document hash (this is to prevent consistency errors generated by concurrent write / read operations).
The get statement retrieves from the database the document with the same id as the one passed as a parameter. In this case, the hash for the document saved in the previous instruction is stored in the record variable.
After executing these instructions it is possible to study the result using Futon.
CouchDB and Rails: integration with CouchRest and BasicModel
Integration with Rails
What has been shown up to now allows us to operate freely on documents and databases using Ruby, but it is far from the comfort that ActiveRecord has accustomed us to; to bring CouchRest closer to a real ORM we can use an updated version of the basic_model plugin, created by Geoffrey Grosenbach, which provides a base class ( BasicModel) from which we can derive all the models of our application (a bit ‘as it happens with ActiveRecord::Base).
I say ‘updated’ because the official plugin has not kept up with the latest version of CouchRest, to make some minor changes; so we will use my version of BasicModel.
Last prerequisite: the installation of couchapp, a small python script that will allow us to define the necessary map-reduce queries within the application. For all information on this procedure refer to the excellent official document.
Once the preparations are complete, we create the rails application :
<pre class=”brush: php; html-script: true”>
rails trip_archive
</pre>
So we add in his config/environment.rbthe following statements, necessary to benefit from couchrest:
<pre class=”brush: php; html-script: true”>
# inside the Rails :: Initializer.run block
config.gem “jchris-couchrest”,: lib => “couchrest”
config.gem “json”
config.frameworks – = [: active_record]
# at the end of the file, out of every block
require ‘json / add / core’
require ‘json / add / rails’
ENV [‘TZ’] = ‘UTC’
</pre>
Completed this part download and install the aforementioned plugin by typing in a prompt placed in the root of the application the following command:
<pre class=”brush: php; html-script: true”>
ruby script / plugin install git: //github.com/sandropaganotti/basic_model.git
</pre>
Now we just have to generate our first model, to do so we create a file travel.rbin the directory app/modelsand modify the content as follows:
<pre class=”brush: php; html-script: true”>
class Travel <BasicMode
def default_attributes
{
“destination” => nil,
“partecipants” => [],
“dates” => {
“from” => Time.now.strftime (“% d /% m /% Y”),
“to” => nil
}
}
end
end
</pre>
The method default_attributesserves, as the name underlines, to specify the starting structure to associate with a document of this object. Note that the BasicModel plugin will automatically add a keyed attribute typecontaining the class name that the document represents (so the objects created by the class Travelwill be stored with type = ‘Travel’).
Before opening the console and experimenting with some operations on the model just created, it is necessary to create a structure in which we will store the JavaScript files of the map-reduce constructs that we will implement shortly; so let’s take a shell into the root of the application and type:
<pre class=”brush: php; html-script: true”>
couchapp generate db / app / trip_archives
</pre>
where with trip_archiveswe are specifying the name of the database we will use on CouchDB. For now ignore the result of this command and launch script/consoleto verify the goodness of what has been developed so far:
<pre class=”brush: php; html-script: true”>
>> t1 = Travel.new (‘trip_archives’, {“destination” => “London”, “partecipants” => [“sandro”]})
=> # <Travel: 0x101d7cdf8 …
>> t1.save
…
=> # <Travel: 0x101e2c3e8 …
>> t1.partecipants << “francesca”
=> [“sandro”, “francesca”]
>> t1.save
=> # <Travel: 0x101e2c3e8 …
</pre>
To observe the results of these instructions we can connect to Futon and navigate within the database trip_archiveswhere there should be a document with the characteristics just specified.
CouchDB and Rails: query the database
Query the database
We conclude this little tutorial by introducing a search by destination, to do this we must first create the necessary map function and then invoke it from within our application.
The first step of this procedure is to create a folder within the db/app/trip_archivescall (the choice is arbitrary) by_destination(because the ‘query’ we are creating will allow us to filter the documents according to their destination). Within this folder we place a file with the name map.jscontaining:
<pre class=”brush: php; html-script: true”>
function (doc) {
if (doc.destination! = null) {
emit (doc.destination, doc);
}
};
</pre>
We can evaluate the operation of what we have just created by running in a command prompt placed in the root of the application:
<pre class=”brush: php; html-script: true”>
couchapp push db / app / trip_archives trip_archives
</pre>
and then using the select select viewwithin Futon to choose the ‘query’ by_destination(always after choosing the database trip_archives).
Now open the console of our application ( ruby script/console) and type:
<pre class=”brush: php; html-script: true”>
>> Travel.view (‘trip_archives’, ‘trip_archives / by_destination’,: key => ‘London’). Rows.size
=> 1
>> Travel.view (‘trip_archives’, ‘trip_archives / by_destination’,: key => ‘London’). Rows.first.partecipants
=> [“sandro”, “francesca”]
>> Travel.view (‘trip_archives’, ‘trip_archives / by_destination’,: key => ‘Nairobi’). Rows.size
=> 0
</pre>
The method view, not present in ActiveRecord, allows you to call a ‘query’ (whose formal name is, as you can already guess, ‘view’) specifying its name. The option: key server to filter the list of results on a specific value (ex: ‘London’); if omitted, the view will return the entire list of documents that meet the criteria of the invoked map function.
The application trip_archiveis available for download.
Conclusions
There is a lot in this article of meat to the fire; I tried to cover as many features as possible in order to propose a good overview, a vision that I consider necessary in order to better evaluate the strengths and weaknesses of this technology that, despite suffering all problems related to his youth (especially as regards the part of integration with Rails) nevertheless manages to propose an innovative and very attractive operating scheme.
CouchDB is a serious competitor to relational databases for all those applications that natively implement a document-oriented approach: catalogs, CMS and forums are among the types that best benefit from this structure. Not to mention that CouchDB offers good version control and, unlike many of its relational ‘cousins’, has no problem climbing if traffic requires it.