{"id":288,"date":"2010-05-13T07:09:38","date_gmt":"2010-05-13T14:09:38","guid":{"rendered":"http:\/\/blog.monnet-usa.com\/?p=288"},"modified":"2010-05-13T07:09:38","modified_gmt":"2010-05-13T14:09:38","slug":"camping-light-nosql-with-mongodb","status":"publish","type":"post","link":"https:\/\/blog.monnet-usa.com\/?p=288","title":{"rendered":"Camping light (nosql) with MongoDB"},"content":{"rendered":"<br \/>\n<h3>Intro <\/h3>\n<p>For the last two years or so I have been following the NoSQL &#8220;movement&#8221; from a far, i.e. the new alternatives to the traditional SQL relational model<br \/>\n\t\t\t\t\tsuch as SimpleDB, BigTable, Cassandra, CouchDB, and <a href='#mo'>MongoDB<\/a>.<br \/>\n\t\t\t\t\tI recommend a few excellent podcasts on <a href='#tzca'>Cassandra<\/a>, <a href='#socd'>CouchDB<\/a> and <a href='#tzmo'>MongoDB<\/a> to get a sense of what this is all about.<br \/>\n\t\t\t\t\tRecently, <a href='#mo'>MongoDB<\/a> has received a lot of attention due to the following factors:\n\t\t\t\t\t<\/p>\n<ol>\n<li>availability on many platforms<\/li>\n<li>rich language support: C, C++, C#, Java, Javascript, Perl, PHP, Python, Ruby<\/li>\n<li>binary json for efficient storage<\/li>\n<li>equivalent of Javascript &#8220;stored procedures&#8221;<\/li>\n<li>community development of higher-level framework like DataMapper<\/li>\n<\/ol>\n<p>Although I could have started earlier, Heroku&#8217;s recent announcement of support for MongoDB accelerated my plans.<br \/>\n\t\t\t\t\tSo it was time for me to start creating a small prototype with my favorite Ruby micro-framework: <a href='#rc'>Camping<\/a>.<\/p>\n<p>If you want to skip straight to the full source, click <a href='#motstsrc'>here<\/a><\/p>\n<h3>Getting Started <\/h3>\n<p>To get started with the prototype I followed these steps:<\/p>\n<ol>\n<li>I downloaded the latest version from <a href='http:\/\/www.mongodb.org\/display\/DOCS\/Downloads'>http:\/\/www.mongodb.org\/display\/DOCS\/Downloads<\/a>. <br \/>\n\t\t\t\t\t\t\tIn my case, I chose the 32 bit Windows version for my laptop.<\/li>\n<li>After installing it, I had to create the data folder under <b>\\data\\db<\/b>. Note that this is important since the folder does not get created automatically.<\/li>\n<li>Then I navigated to the bin folder of my MongoDB installation, opened up a console and started mongodb:\n<pre class=\"brush: ruby\">\r\nmongod\r\n<\/pre>\n<p>\t\t\t\t\t\t<img src='\/wp-content\/media\/mongodb-camping\/mongodexe.png' width='520px'\/>\n\t\t\t\t\t\t<\/li>\n<li>I installed the base ruby MongoDB driver:\n<pre class=\"brush: ruby\">\r\ngem install mongodb\r\n<\/pre>\n<\/li>\n<li>I installed MongoMapper\n<pre class=\"brush: ruby\">\r\ngem install mongo_mapper\r\n<\/pre>\n<\/li>\n<li>I installed the latest version of Camping:\n<pre class=\"brush: ruby\">\r\ngem install camping\r\n<\/pre>\n<\/li>\n<\/ol>\n<p>Et voil\u00e0! :-)<\/p>\n<h3>Spinning Up Mongo In IRB<\/h3>\n<p>I always like to prototype in a very incremental and dynamic manner so I first started with the Interactive Ruby console: IRB and entered the following:<br \/>\n\t\t\t\t\t<img src='\/wp-content\/media\/mongodb-camping\/irbmongo.png'><br \/>\n\t\t\t\t\tSince we&#8217;re just &#8220;playing&#8221;, we don&#8217;t need to worry about compiling the bson extension.<\/p>\n<p>Ok so now let&#8217;s open a connection on a new database we&#8217;ll call &#8220;test&#8221;<\/p>\n<pre class=\"brush: ruby\">\r\n\t\t\t\t\tm = Mongo::Connection.new.db('test')\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-cn.png'><br \/>\n\t\t\t\t\tAt this point Mongo has not yet created the physical database yet though so if you go to the \\data\\db folder no content has been created yet.\n\t\t\t\t\t<\/p>\n<p>Content is typically organized in <b>&#8220;collections&#8221;<\/b>, similarly to tables in the relational world,<br \/>\n\t\t\t\t\tbut with the important difference that a collection does not have schema and can store arbitrary objects of any shapes.<br \/>\n\t\t\t\t\tSo let&#8217;s create a collection of &#8220;widgets&#8221;<\/p>\n<pre class=\"brush: ruby\">\r\n\t\t\t\t\tw = m.collection('widgets') \r\n\t\t\t\t\t\r\n\t\t\t\t\t# m.collection(...) can also be abbreviated as m[...]\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-col.png'><br \/>\n\t\t\t\t\tNow let&#8217;s add some widgets. We&#8217;ll start with a simple Ruby <b>&#8220;hash&#8221;<\/b> object.\n\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\n\t\t\t\t\tw1 = { :code => 'A101', \r\n\t\t\t\t\t\t\t\t:description => '1\/2 in. brass nut', \r\n\t\t\t\t\t\t\t\t:price => 0.15 }\r\n\t\t\t\t\tw << w1 \r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-add1.png'><br \/>\n\t\t\t\t\tMongoDB returned the <b>object id<\/b> of the hash we just stored in the <b>widgets collection<\/b>.<br \/>\n\t\t\t\t\tNow we can see that data files were created in the <b>\\data\\db<\/b> folder:<br \/>\n\t\t\t\t\t<img src='\/wp-content\/media\/mongodb-camping\/irbmongo-db.png'>\n\t\t\t\t\t<\/p>\n<p>Ok now let's add one more widget so we can later do a query over more than one object!<\/p>\n<pre class=\"brush: ruby\">\r\n\t\t\t\t\tw2 = { :code => 'B201', \r\n\t\t\t\t\t\t\t\t:description => '1\/2 in. brass bolt', \r\n\t\t\t\t\t\t\t\t:price => 0.2 }\r\n\t\t\t\t\tw << w2\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-add2.png'><br \/>\n\t\t\t\t\tWe can now find and iterate over all objects in the collection using <b>find<\/b> and <b>each<\/b>:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\n\t w.find.each { |x| puts x.inspect }\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-each.png'><br \/>\n\t\t\t\t\tWe can write more simple queries on a given attribute like:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\n\t w.find({ :code => 'A101' }).each { |x| puts x.inspect }\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-q1.png'><br \/>\n\t\t\t\t\tOr we can use a comparison operator like:\n\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\n\t w.find({ :price => { '$gt' => 0.19 } })\r\n\t\t.each { |x| puts x.inspect }\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/irbmongo-q2.png'><br \/>\n\t\t\t\t\tOf course you can include multiple attributes in the criteria, limit the number of results, filter the list of attributes returned, etc.<br \/>\n\t\t\t\t\tCheck out the full description of the <b>find<\/b> method <a href='http:\/\/api.mongodb.org\/ruby\/0.20.2\/Mongo\/Collection.html#find-instance_method'>here<\/a><br \/>\n\t\t\t\t\tor review the full <a href='#morb'>Ruby Mongo API<\/a>.\n\t\t\t\t\t<\/p>\n<h3>Going Camping With MongoDB<\/h3>\n<p>Now that we have experimented with the basic features of MongoDB in IRB,<br \/>\n\t\t\t\t\twe can step up to a prototype web app using the Ruby <a href='#rc'>Camping framework<\/a>. We will also incorporate the <a href='#momp'>MongoMapper<\/a> data access layer.<\/p>\n<h5>A. Creating A Skeletal Camping App<\/h5>\n<p>I personally like using <a href='#rc'>Camping<\/a> as it allows me to structure my app with the model-view-controller (MVC) pattern<br \/>\n\t\t\t\t\tall while keep all source in one file (I can always split it up later). The Camping server will also automatically reload the app<br \/>\n\t\t\t\t\tas  I add features incrementally. <a href='#rc'>Camping<\/a> is also lightweight and much simpler than Rails. So let's get started:<\/p>\n<ol>\n<li>Open up your favorite text editor (e.g. NotePad++ or TextMate) <\/li>\n<li>Create a new Ruby file named <b>mongo-camping-test.rb<\/b><\/li>\n<li>Add the gem and require statements for camping and mongo\n\t\t\t\t\t\t<\/li>\n<\/ol>\n<pre class=\"brush: ruby\">\r\ngem 'camping' , '>= 2.0'\r\ngem 'mongo', '= 1.0' #Using 1.0 since MongoMapper explicitly needs 1.0\r\ngem 'mongo_mapper'\r\n\r\n%w(rack camping mongo mongo_mapper).each { | r | require r}\r\n<\/pre>\n<p>Our app will be contained in a module named <b>MongoTest<\/b>.<br \/>\n\t\t\t\t\t\t\tSo first, let's add the statement to tell <a href='#rc'>Camping<\/a> to start our MongoTest module.<br \/>\n\t\t\t\t\t\t\tThe main <b>MongoTest<\/b> module will have a <b>create<\/b> method used to initialize the application so this is where we will place<br \/>\n\t\t\t\t\t\t\tthe logic to configure MongoMapper in terms of <b>connection<\/b> and <b>database<\/b>.\n\t\t\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\nCamping.goes :MongoTest\r\n\r\nmodule MongoTest\r\n\tdef self.create\r\n\t\tMongoMapper.connection = Mongo::Connection.new\r\n\t\tMongoMapper.database = 'test'\r\n\tend\r\nend\t\r\n\r\nMongoTest.create\r\n<\/pre>\n<p>\n\t\t\t\t\t\t\tOur main MongoTest module will contain a submodule for each facet of our <b>MVC pattern<\/b>: <b>Models<\/b>, <b>Controllers<\/b>, and <b>Views<\/b>.<br \/>\n\t\t\t\t\t\t\tSo let's define these modules with the very basic minimum to allow a default route and view for the application:\n\t\t\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\nmodule MongoTest\r\n\t# ...\r\n\t\r\n\tmodule Models\r\n\tend\r\n\t\r\n\tmodule Controllers\r\n\t\tclass Index\r\n\t\t\tdef get \r\n\t\t\t\trender :index\r\n\t\t\tend\r\n\t\tend\r\n\tend\r\n\t\r\n\tmodule Views\r\n\t\tdef index\r\n\t\t\th1 'My MongoDB App'\r\n\t\t\tdiv 'To be continued ...'\r\n\t\tend\r\n\tend\r\nend\r\n<\/pre>\n<p>Now we have a skeletal <a href='#rc'>Camping<\/a> app that we can run as follows:<\/p>\n<pre class=\"brush: ruby\">\r\ncamping mongo-camping-test.rb\r\n<\/pre>\n<p>Access the app from the browser at the following url:<\/p>\n<pre class=\"brush: ruby\">\r\nhttp:\/\/localhost:3001\/\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/campingmongo-skn.png'><\/p>\n<h5>B. Creating The Model<\/h5>\n<p>In the skeleton we had created a Models module. This is where we will add our Widget class.<br \/>\n\t\t\t\t\tUnlike a traditional <a href='#rc'>Camping<\/a> or Rails model, we will not have this class inherit from an ActiveRecord descendant class.<br \/>\n\t\t\t\t\tInstead we will just <b>include<\/b> the <\/b>MongoMapper::Document<\/b> module.<br \/>\n\t\t\t\t\tThis module will turbo charge our model with all the needed Mongo features we need.<\/p>\n<pre class=\"brush: ruby\">\r\n\tmodule Models\r\n\t\tclass Widget\r\n\t\t\tinclude MongoMapper::Document\r\n\t\tend\r\n\tend\r\n<\/pre>\n<p>Now we will <b>define the attributes<\/b> of our Model: <b>code<\/b>, <b>description<\/b>, and <b>price<\/b>.<br \/>\n\t\t\t\t\tNote that unlike in <a href='#rc'>Camping<\/a> and Rails we don't need a migration!<br \/>\n\t\t\t\t\tMongoMapper also provides <b>validators<\/b> like ActiveRecord, so we'll define a numeric validator and a custom validator<br \/>\n\t\t\t\t\tfor the <b>price<\/b> attribute.<br \/>\n\t\t\t\t\tThe only \"gotcha\" we need to work-around is that if we don't override the collection name auto-generated for Widget, the table name will include the fully qualified model class name, containing the module hierarchy names! So we'll explicitly force the collection name.<\/p>\n<pre class=\"brush: ruby\">\r\n\tmodule Models\r\n\t\tclass Widget\r\n\t\t\tinclude MongoMapper::Document\r\n\t\t\t\r\n\t\t\tkey :code, String\r\n\t\t\tkey :description, String\r\n\t\t\tkey :price, Float\r\n\t\t\t\r\n\t\t\tvalidates_numericality_of :price\r\n\t\t\tvalidates_true_for :price, \r\n\t\t\t\t:logic=> lambda { | x | x.price >= 0.05 && x.price <= 9999.99 }, \r\n\t\t\t\t:message=>\"must be in the following range: 0.05 to 9999.99\"\r\n\t\t\t\r\n\t\t\tset_collection_name 'widgets'\r\n\t\tend\r\n\tend\r\n<\/pre>\n<\/p>\n<h5>C. Creating The \"Home\/Index\" Controller<\/h5>\n<p>Now that we have a model, we can flesh-out our first controller.<br \/>\n\t\t\t\t\tOur initial skeleton has an <b>Index<\/b> (i.e. home) controller and<br \/>\n\t\t\t\t\ta matching <b>index<\/b> view rendered when an HTTP GET is<br \/>\n\t\t\t\t\treceived by <a href='#rc'>Camping<\/a> on the default \"index\" route i.e. for the <b>\/<\/b> path.<br \/>\n\t\t\t\t\tLet's modify the <b>get<\/b> method of our <b>Index<\/b> controller so we can retrieve the list of widgets using <b>all<\/b> method on our <b>Widget model<\/b>:<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tclass Index\r\n\t\t\tdef get\r\n\t\t\t\t@widgets = Widget.all\r\n\r\n\t\t\t\trender :index\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<h5>D. Creating The \"Home\/Index\" View<\/h5>\n<p>By default, Camping uses a <b>Ruby-syntax<\/b> based <\/b>templating engine<\/b> called <a href='#mkby'>Markaby<\/a>.<br \/>\n\t\t\t\t\tIt allows declaration of HTML elements using Ruby statements:<\/p>\n<pre class=\"brush: ruby\">\r\n\th1 \"Welcome #{@your_name}\"\r\n<\/pre>\n<p>Nested HTML elements are represented by Ruby blocks:<\/p>\n<pre class=\"brush: ruby\">\r\n\ttable do\r\n\t\ttr { th 'First'; th 'Last'}\r\n\t\ttr { td 'John'; th 'Doe'}\r\n\tend\r\n<\/pre>\n<p>For more details on <a href='#mkby'>Markaby<\/a>, see <a href='#mkby'>here<\/a>.<br \/>\n\t\t\t\t\tNote: the upcoming 2.1 version of Camping uses <a href='#tilt'>Tilt<\/a> to let you choose your favorite templating engine (e.g. ERB, <a href='#haml'>Haml<\/a>, ...).<\/p>\n<p>So, let's flesh-out our <b>index<\/b> view method to render a table of widgets.\n\t\t\t\t\t<\/p>\n<pre class=\"brush: ruby\">\r\n\tmodule Views\r\n\t\tdef index\r\n\t\t\th1 \"Widgets\"\r\n\r\n\t\t\tif @widgets \r\n\t\t\t\ttable do\r\n\t\t\t\t\t\ttr { th 'id'; th 'Code'; th 'Description'; th 'Price'; }\r\n\t\t\t\t\t@widgets.each do | w |\r\n\t\t\t\t\t\ttr {  \ttd w._id; \r\n\t\t\t\t\t\t\t\ttd w.code; \r\n\t\t\t\t\t\t\t\ttd w.description; \r\n\t\t\t\t\t\t\t\ttd w.price;  }\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\telse\r\n\t\t\t\tp \"No widgets yets\"\r\n\t\t\tend\r\n\t\tend\r\n\tend\r\n<\/pre>\n<p>Let's refresh our page (since <a href='#rc'>Camping<\/a> will auto-reload our code on save in the editor):<\/p>\n<p><img src='\/wp-content\/media\/mongodb-camping\/campingmongo-idx.png'><\/p>\n<h5>E. Add Widget Feature<\/h5>\n<p>Now let's add the ability to create and save new widgets.<br \/>\n\t\t\t\t\tWe'll start by creating a new <b>AddWidget<\/b> controller in the <b>Controllers<\/b> module<br \/>\n\t\t\t\t\tand register a new route for <b>\/widget\/new<\/b>.<br \/>\n\t\t\t\t\tWe'll instance (not create) a new <b>Widget<\/b> model and render it in a <b>widget_form<\/b> (to be created soon):<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tclass AddWidget < R '\/widget\/new'\r\n\t\t\tdef get\r\n\t\t\t\t@widget = Widget.new(:code=>'', :description=>'', :price=>0)\r\n\t\t\t\trender :widget_form\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<p>Ok now we can create our <b>widget_form<\/b> and allow a POST submit<br \/>\n\t\t\t\t\tto the route - using the R() Camping function - for our new <b>AddWidget controller<\/b>.<br \/>\n\t\t\t\t\tNote: we will add an optional section at the top of the form to report any model validation errors:<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tdef widget_form\r\n\t\t\th2 \"New Widget\"\r\n\r\n\t\t\tif @errors\r\n\t\t\t\th3 \"Errors:\"\r\n\t\t\t\tol do\r\n\t\t\t\t\t@errors.each do | e |\r\n\t\t\t\t\t\tli { div \"#{e[0]}: #{e[1].join(' ')}\" }\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\t\r\n\t\t\tform :action => R(AddWidget), :method => 'post' do\r\n\t\t\t  label 'Code:', :for => 'widget_code'; br\r\n\t\t\t  input :name => 'widget_code', :type => 'text', :value => @widget.code; br\r\n\r\n\t\t\t  label 'Description:', :for => 'widget_description'; br\r\n\t\t\t  input :name => 'widget_description', :type => 'text', :value => @widget.description; br\r\n\r\n\t\t\t  label 'Price:', :for => 'widget_price'; br\r\n\t\t\t  input :name => 'widget_price', :type => 'text', :value => @widget.price.to_s; br\r\n\t\t\t  \r\n\t\t\t  input :type => 'submit', :value => 'Save'\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<p>Access the new widget form from the browser at the following url:<\/p>\n<pre class=\"brush: ruby\">\r\nhttp:\/\/localhost:3001\/widget\/new\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/campingmongo-new.png'><\/p>\n<p>Now we can add the handler for the HTTP POST to our <b>AddWidget controller<\/b>.<br \/>\n\t\t\t\t\tAfter validating the user entered some actual data, we'll create (not instantiate)<br \/>\n\t\t\t\t\ta new <b>Widget model instance<\/b> with the user's data and call the <b>save<\/b> method<br \/>\n\t\t\t\t\tto add it to the <b>widgets collection<\/b>.<br \/>\n\t\t\t\t\tWe'll redirect back to the form if the model did not pass validation rules or if the save failed.<br \/>\n\t\t\t\t\tThen we'll re-query the list of widgets and render the <b>index view<\/b> to show the new data.<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tclass AddWidget < R '\/widget\/new'\r\n\t\t\t# ...\r\n\t\t\t\r\n\t\t\tdef post\r\n\t\t\t\treturn(redirect('\/index')) unless input.widget_code &#038;&#038; input.widget_description &#038;&#038; input.widget_price\r\n\t\t\t\t\r\n\t\t\t\tw = Widget.create(:code => input.widget_code,\r\n\t\t\t\t\t\t\t\t\t\t\t\t:description => input.widget_description,\r\n\t\t\t\t\t\t\t\t\t\t\t\t:price => input.widget_price.to_f)\r\n\t\t\t\t\r\n\t\t\t\tbegin\r\n\t\t\t\t\tw.save!\r\n\t\t\t\t\t@widgets = Widget.all\r\n\t\t\t\t\treturn(render(:index))\r\n\t\t\t\trescue Exception => ex\r\n\t\t\t\t\t@errors = w.errors\r\n\t\t\t\t\treturn(render(:widget_form))\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<h5>F. Search Widget Feature<\/h5>\n<p>Finally let's add the ability to search for a specific widget based on a widget code.<br \/>\n\t\t\t\t\tWe'll start by creating a new <b>SearchWidgetByCode<\/b> controller in the <b>Controllers<\/b> module<br \/>\n\t\t\t\t\tand register a new route for <b>\/widget\/search<\/b>.<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tclass SearchWidgetByCode < R '\/widget\/search'\r\n\t\t\tdef get\r\n\t\t\t\trender :search_form\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<p>Ok now we can create our <b>search_form<\/b> and allow a POST submit<br \/>\n\t\t\t\t\tto the route - using the R() Camping function - for our new <b>SearchWidgetByCode controller<\/b>:<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tdef search_form\r\n\t\t\th2 \"Search\"\r\n\t\t\tp @info if @info\r\n\t\t\t\r\n\t\t\tform :action => R(SearchWidgetByCode), :method => 'post' do\r\n\t\t\t  label 'Code', :for => 'widget_code'; br\r\n\t\t\t  input :name => 'widget_code', :type => 'text'; br\r\n\t\t\t  input :type => 'submit', :value => 'Search'\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<p>Access the widget search form from the browser at the following url:<\/p>\n<pre class=\"brush: ruby\">\r\nhttp:\/\/localhost:3001\/widget\/search\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/campingmongo-srh.png'><\/p>\n<p>Now we can add the handler for the HTTP POST to our <b>SearchWidgetByCode<\/b>.<br \/>\n\t\t\t\t\tWe'll use the traditional dynamic finder method like in ActiveRecord: <b>find_by_code<\/b> method and pass a criteria based on the <b>code<\/b> attribute<br \/>\n\t\t\t\t\tThe results will be rendered using the <b>index view<\/b>.<\/p>\n<pre class=\"brush: ruby\">\r\n\t\tclass SearchWidgetByCode < R '\/widget\/search'\r\n\t\t\t# ...\r\n\t\t\t\r\n\t\t\tdef post\r\n\t\t\t\treturn(redirect('\/index')) unless input.widget_code\r\n\t\t\t\t\r\n\t\t\t\tw = Widget.find_by_code input.widget_code\r\n\t\t\t\t@widgets = [ ] \r\n\t\t\t\t@widgets << w if w\r\n\t\t\t\t\r\n\t\t\t\trender :index\r\n\t\t\tend\r\n\t\tend\r\n<\/pre>\n<p><img src='\/wp-content\/media\/mongodb-camping\/campingmongo-res.png'><\/p>\n<h5>G. Exercises Left For The Reader<\/h5>\n<p>Now that we have scratched the surface of the possibilities offered by MongoDB and MongoMapper,<br \/>\n\t\t\t\t\twe could extend our prototype to include the following features:\n\t\t\t\t\t<\/p>\n<ol>\n<li>Add indexes to the database<\/li>\n<li>Limit the main view to include only the first 12 items, and add paging<\/li>\n<li>Add model validation rules to Widget model<\/li>\n<li>Add more attributes to the Search criteria<\/li>\n<li>Add an Edit page to leverage the <b>save(object)<\/b> feature of a collection<\/li>\n<li>Add embedded models\/document to Widget (e.g. TechnicalSpecification, etc.)<\/li>\n<\/ol>\n<p>You can now leverage the power of <a href='#rc'>Camping<\/a>, Markaby and REPL to add these features on your own. :-).<br \/>\n\t\t\t\t\tNote: you can mix and match templating engine in Camping 2.1 using <a href='#tilt'>Tilt<\/a> - which allows you to use ERB, <a href='#haml'>Haml<\/a>, etc.<\/p>\n<h3>So What? <\/h3>\n<ol>\n<li>The combination of MongoDB and MongoMapper makes it very easy to implement <b>SQL-less persistence<\/b><\/li>\n<li>We could work in a <b>schema-less mode<\/b> while using using <b>hashes<\/b> and the base Mongo Ruby Driver<\/li>\n<li>But we can adopt <b>migration-less models<\/b> and have more structure<\/li>\n<li>MongoMapper draws a lot of features from ActiveRecord making the transition easy<\/li>\n<li>Since Camping is lightweight yet powerful, writing solid MongoDB apps can be done easily<\/li>\n<\/ol>\n<p>So hopefully this post will have given you a feel for what development with MongoDB\/Ruby and Camping feels like. <br \/>\n\t\t\t\t\tHappy experimentations!!!<\/p>\n<p>\t\t\t\t\t<a name=\"referencesandresources\" ><\/a><\/p>\n<h3>References and Resources<\/h3>\n<ul>\n<li><a name='arstech' href='http:\/\/arstechnica.com\/business\/data-centers\/2010\/02\/-since-the-rise-of.ars\/2'>Cloud storage in a post-SQL world<\/a><\/li>\n<li><a name='tzca' href='http:\/\/techzinglive.com\/?p=75'>Cassandra - \"Dude where is my database?\" Techzing Live podcast<\/a><\/li>\n<li><a name='socd' href='http:\/\/blog.stackoverflow.com\/2009\/06\/podcast-59\/'>CouchDB - Stack Overflow podcast<\/a><\/li>\n<li><a name='tzmo' href='http:\/\/techzinglive.com\/?p=192'>MongoDB - Mike Dirolf's Interview on Techzing Live podcast<\/a><\/li>\n<li><a name='mo' href='http:\/\/www.mongodb.org\/'>MongoDB - official site<\/a><\/li>\n<li><a name='momd' href='http:\/\/www.slideshare.net\/mdirolf\/mongodb-at-frozenrails'>MongoDB - Mike Dirolf's Intro Slides<\/a><\/li>\n<li><a name='mo' href='https:\/\/mongohq.com\/'>MongoHQ<\/a><\/li>\n<li><a name='morb' href='http:\/\/api.mongodb.org\/ruby'>MongoDB Ruby API<\/a><\/li>\n<li><a name='momp' href='http:\/\/www.slideshare.net\/mongosf\/ruby-development-and-mongomapper-john-nunemaker'>MongoMapper Overview Presentation by John Nunemaker<\/a><\/li>\n<li><a name='momp2' href='http:\/\/www.mongodb.org\/display\/DOCS\/Object+Mappers+for+Ruby+and+MongoDB#ObjectMappersforRubyandMongoDB-MongoMapper'>Mongo Mapper - Data Access Layer<\/a><\/li>\n<li><a name='rc' href='http:\/\/github.com\/camping\/camping'>Ruby Camping Framework<\/a><\/li>\n<li><a name='mkby' href='http:\/\/markaby.rubyforge.org\/'>Markaby - Ruby Templating Engine<\/a><\/li>\n<li><a name='mkbyc' href='http:\/\/cheat.errtheblog.com\/s\/markaby\/'>Markaby Cheat Sheet<\/a><\/li>\n<li><a name='tilt' href='http:\/\/bit.ly\/rubytilt'>Tilt - Templating Engine Adapter<\/a><\/li>\n<li><a name='haml' href='http:\/\/haml-lang.com\/'>Haml - Ruby Templating Engine<\/a><\/li>\n<li><a name='hu' href='http:\/\/heroku.com\/'>Heroku Cloud Hosting for Ruby<\/a><\/li>\n<li><a name='nosqlxr' href='http:\/\/nosql-database.org\/'>NoSQL catalog of products<\/a><\/li>\n<\/ul>\n<h5>My Other Related Posts:<\/h5>\n<ol name=\"myotherrelatedposts\">\n<li><a href='https:\/\/blog.monnet-usa.com\/?p=223'>Visualize Application Metrics on NewRelic for your Ruby Camping Application<\/a><\/li>\n<li><a href='https:\/\/blog.monnet-usa.com\/?p=166'>Running the Camping Microframework on IronRuby<\/a><\/li>\n<\/ol>\n<p>\t\t\t\t\t<a name='motstsrc'><\/a><\/p>\n<h5>Full Source Of Our MongoDB Camping Web App<\/h5>\n<p>Here is the full source (you can also download it from Gist <a href='http:\/\/bit.ly\/9tnlai '>here<\/a>)<\/p>\n<pre class=\"brush: ruby\">\r\ngem 'camping' , '>= 2.0'\r\ngem 'mongo', '= 1.0' #Using 1.0 since MongoMapper explicitly\r\n\r\n%w(rack camping mongo mongo_mapper).each { | r | require r}\r\n\r\nCamping.goes :MongoTest\r\n\r\nmodule MongoTest\r\n\tdef self.create\r\n\t\tMongoMapper.connection = Mongo::Connection.new\r\n\t\tMongoMapper.database = 'test'\r\n\tend\r\n\r\n\tmodule Models\r\n\t\tclass Widget\r\n\t\t\tinclude MongoMapper::Document\r\n\t\t\t\r\n\t\t\tkey :code, String\r\n\t\t\tkey :description, String\r\n\t\t\tkey :price, Float\r\n\t\t\t\r\n\t\t\tvalidates_numericality_of :price\r\n\t\t\tvalidates_true_for :price, \r\n\t\t\t\t:logic=> lambda { | x | (0.05..9999.99) === x.price }, \r\n\t\t\t\t:message=>\"must be in the following range: 0.05 to 9999.99\"\r\n\r\n\t\t\tset_collection_name 'widgets'\r\n\t\tend\r\n\tend\r\n\r\n\tmodule Controllers\r\n\t\tclass Index\r\n\t\t\tdef get\r\n\t\t\t\t@widgets = Widget.all\r\n\r\n\t\t\t\trender :index\r\n\t\t\tend\r\n\t\tend\r\n\t\t\r\n\t\tclass AddWidget < R \"\/widget\/new\"\r\n\t\t\tdef get\r\n\t\t\t\t@widget = Widget.new(:code=>'', :description=>'', :price=>0)\r\n\t\t\t\trender :widget_form\r\n\t\t\tend\r\n\t\t\t\r\n\t\t\tdef post\r\n\t\t\t\treturn(redirect('\/index')) unless input.widget_code && input.widget_description && input.widget_price\r\n\t\t\t\t\r\n\t\t\t\t@widget = Widget.create(:code => input.widget_code,\r\n\t\t\t\t\t\t\t\t\t\t\t\t:description => input.widget_description,\r\n\t\t\t\t\t\t\t\t\t\t\t\t:price => input.widget_price)\r\n\t\t\t\t\r\n\t\t\t\tbegin\r\n\t\t\t\t\t@widget.save!\r\n\t\t\t\t\t@widgets = Widget.all\r\n\t\t\t\t\treturn(render(:index))\r\n\t\t\t\trescue Exception => ex\r\n\t\t\t\t\t@errors = @widget.errors || [ ex.to_s ]\r\n\t\t\t\t\treturn(render(:widget_form))\r\n\t\t\t\tend\r\n\r\n\t\t\tend\r\n\t\tend\r\n\t\t\r\n\t\tclass SearchWidgetByCode < R '\/widget\/search'\r\n\t\t\tdef get\r\n\t\t\t\trender :search_form\r\n\t\t\tend\r\n\t\t\t\r\n\t\t\tdef post\r\n\t\t\t\treturn(redirect('\/index')) unless input.widget_code\r\n\t\t\t\t\r\n\t\t\t\tw = Widget.find_by_code input.widget_code\r\n\t\t\t\t@widgets = [ ] \r\n\t\t\t\t@widgets << w if w\r\n\t\t\t\t\r\n\t\t\t\trender :index\r\n\t\t\tend\r\n\t\tend\r\n\tend\r\n\t\r\n\tmodule Views\r\n\t\tdef layout\r\n\t\t\thtml do\r\n\t\t\t\thead do\t\t\t\t\t   \r\n\t\t\t\t\tstyle  :type => 'text\/css' do\r\n\t<<-STYLE\t\t\t\t\r\n\tbody {\r\n\t\tpadding:0 0 0 0;\r\n\t\tmargin:5px;\r\n\t\tfont-family:'Lucida Grande','Lucida Sans Unicode',sans-serif;\r\n\t\tfont-size: 0.8em;\r\n\t\tcolor:#303030;\r\n\t\tbackground-color: #fbf9b5;\r\n\t}\r\n\r\n\ta {\r\n\t\tcolor:#303030;\r\n\t\ttext-decoration:none;\r\n\t\tborder-bottom:1px dotted #505050;\r\n\t}\r\n\r\n\ta:hover {\r\n\t\tcolor:#303030;\r\n\t\tbackground: yellow;\r\n\t\ttext-decoration:none;\r\n\t\tborder-bottom:4px solid orange;\r\n\t}\r\n\r\n\th1 {\r\n\t\tfont-size: 14px;\r\n\t\tcolor: #cc3300;\r\n\t}\r\n\r\n\ttable {\r\n\t\tfont-size:0.9em;\r\n\t\twidth: 400px;\r\n\t\t}\r\n\r\n\ttr \r\n\t{\r\n\t\tbackground:lightgoldenRodYellow;\r\n\t\tvertical-align:top;\r\n\t}\r\n\r\n\tth\r\n\t{\r\n\t\tfont-size: 0.9em;\r\n\t\tfont-weight:bold;\r\n\t\tbackground:lightBlue none repeat scroll 0 0;\r\n\t\t\r\n\t\ttext-align:left;\t\r\n\t}\r\n\r\n\t.url\r\n\t{\r\n\t\twidth: 300px;\r\n\t}\r\n\t\t\r\n\tSTYLE\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\t\t\r\n\t\t\t\tbody do\r\n\t\t\t\t\tself << yield\r\n\r\n\t\t\t\t\ta \"Home\", :href=>\"\/\"\r\n\t\t\t\t\tdiv.footer! do\r\n\t\t\t\t\t\thr\r\n\t\t\t\t\t\tspan.copyright_notice! { \"Copyright &copy; 2010 &nbsp; -  #{ a('Philippe Monnet (@techarch)', :href => 'https:\/\/blog.monnet-usa.com\/') }  \" }\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\tend\r\n\t\r\n\t\tdef index\r\n\t\t\th1 \"Widgets\"\r\n\r\n\t\t\tif @widgets \r\n\t\t\t\ttable do\r\n\t\t\t\t\t\ttr { th 'id'; th 'Code'; th 'Description'; th 'Price'; }\r\n\t\t\t\t\t@widgets.each do | w |\r\n\t\t\t\t\t\ttr {  \ttd w._id; \r\n\t\t\t\t\t\t\t\ttd w.code; \r\n\t\t\t\t\t\t\t\ttd w.description; \r\n\t\t\t\t\t\t\t\ttd w.price;  }\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\telse\r\n\t\t\t\tp \"No widgets yets\"\r\n\t\t\tend\r\n\r\n\t\t\tul do\r\n\t\t\t\tli { a \"Search\", :href => \"\/widget\/search\" }\r\n\t\t\t\tli { a \"Create\", :href => \"\/widget\/new\" }\r\n\t\t\tend\r\n\t\tend\r\n\t\t\r\n\t\tdef widget_form\r\n\t\t\th2 \"New Widget\"\r\n\r\n\t\t\tif @errors\r\n\t\t\t\th3 \"Errors:\"\r\n\t\t\t\tol do\r\n\t\t\t\t\t@errors.each do | e |\r\n\t\t\t\t\t\tli { div \"#{e[0]}: #{e[1].join(' ')}\" }\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\t\r\n\t\t\tform :action => R(AddWidget), :method => 'post' do\r\n\t\t\t  label 'Code:', :for => 'widget_code'; br\r\n\t\t\t  input :name => 'widget_code', :type => 'text', :value => @widget.code; br\r\n\r\n\t\t\t  label 'Description:', :for => 'widget_description'; br\r\n\t\t\t  input :name => 'widget_description', :type => 'text', :value => @widget.description; br\r\n\r\n\t\t\t  label 'Price:', :for => 'widget_price'; br\r\n\t\t\t  input :name => 'widget_price', :type => 'text', :value => @widget.price.to_s; br\r\n\t\t\t  \r\n\t\t\t  input :type => 'submit', :value => 'Save'\r\n\t\t\tend\r\n\t\tend\r\n\t\t\r\n\t\tdef search_form\r\n\t\t\th2 \"Search\"\r\n\t\t\tp @info if @info\r\n\t\t\t\r\n\t\t\tform :action => R(SearchWidgetByCode), :method => 'post' do\r\n\t\t\t  label 'Code', :for => 'widget_code'; br\r\n\t\t\t  input :name => 'widget_code', :type => 'text'; br\r\n\t\t\t  input :type => 'submit', :value => 'Search'\r\n\t\t\tend\r\n\t\tend\r\n\t\t\r\n\tend\r\nend\r\n\r\nMongoTest.create\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Intro For the last two years or so I have been following the NoSQL &#8220;movement&#8221; from a far, i.e. the new alternatives to the traditional SQL relational model such as SimpleDB, BigTable, Cassandra, CouchDB, and MongoDB. I recommend a few excellent podcasts on Cassandra, CouchDB and MongoDB to get a sense of what this is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[33,28],"tags":[34,61],"class_list":["post-288","post","type-post","status-publish","format-standard","hentry","category-mongodb","category-ruby-camping","tag-camping-ruby","tag-mongodb"],"_links":{"self":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/288","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=288"}],"version-history":[{"count":4,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/288\/revisions"}],"predecessor-version":[{"id":292,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/288\/revisions\/292"}],"wp:attachment":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=288"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=288"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=288"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}