Saturday, January 2, 2010

Inheritance in Mongoid

Version 0.11.0 introduces inheritance support in Mongoid, and here's how it all fits together...

Let's start with a simple domain model without any associations:


class Canvas
include Mongoid::Document
field :name
end

class Browser < Canvas
field :version, :type => Integer
end


Given the above model, my Canvas class would be stored by default in a "canvases" collection in the database. All subclasses of Canvas will share this collection, similar to the way Single Table Inheritance works in ActiveRecord. The underlying implementation is simple: a "_type" field is stored on the document in order for Mongoid to know what class the document is when it performs queries to retrieve them from the database.

As with traditional inheritance, properties of the parent class are available on the child class, but not vise-versa. In the above case Browser would have a field "name", but Canvas would not have a "version" field.

Querying for either is as expected:


Canvas.where(:name => "paper") # Will only return Canvas documents
Browser.where(:name => "firefox") # Will only return Browser documents


Let's get into associations, and expand the above domain model to include some shapes:


class Canvas
include Mongoid::Document
field :name
has_many :shapes

def render
shapes.each { |shape| shape.render }
end
end

class Browser < Canvas
field :version, :type => Integer
end

class Shape
include Mongoid::Document
field :x, :type => Float
field :y, :type => Float
belongs_to :canvas, :inverse_of => :shapes

def render; end
end

class Circle < Shape
field :radius, :type => Float
end

class Rectangle < Shape
field :width, :type => Float
field :height, :type => Float
end


Now we have an embedded collection of shapes that will be stored in the canvas or browser documents. We can add shapes in the normal manner:


browser = Browser.new(:name => "firefox")
circle = Circle.new(:x => 0, :y => 0, :radius => 20)
rectangle = Rectangle.new(:x => 5, :y => 10, :width => 100, :height => 50)
browser.shapes << [circle, rectangle]


The resulting document when saved would resemble the following hash:


{
"_type" => "Browser",
"name" => "firefox",
"shapes" => [
{
"_type" => "Circle",
"x" => 0,
"y" => 0,
"radius" => 20
},
{
"_type" => "Rectangle",
"x" => 5,
"y" => 10,
"width" => 100,
"height" => 50
}
]
}


The methods #create and #build on has many and has one associations now have an additional optional parameter, the type of class to create. If not provided it will create a class that is determined by the association name:


browser = Browser.new(:name => "firefox")
browser.shapes.build({ :x => 0, :y => 0, :radius => 50 }, Circle) # Creates a Circle
browser.shapes.build({ :x => 0, :y => 0 }) # Creates a Shape


And that's it, nice and simple.

Monday, August 24, 2009

Getting MongoDB running on EngineYard Cloud with Master/Slave Replication

Just recently we got MongoDB up and running on EngineYard cloud, and thought we'd share the steps to get it up and running in an automated fashion using Chef. For more on getting Chef scripts running on your instance in general, please see:

https://cloud-support.engineyard.com/faqs/chef/using-chef-to-customize-your-environment

Special thanks to John Hornbeck for the starter scripts here.

Our environment currently runs MongoDB v1.0.0 64bit for Linux. The following steps will get you up and running with master/slave replication - master will run on port 27017, the slave will run on 27018. Master data will save in /data/masterdb, and slave data will save in /data/slavedb:

1. Login to your cloud dashboard at (http://cloud.engineyard.com).

2. Click the "Extras" tab on the left side under "Server Management".

3. Download your "ey-cloud.yml" API credentials.

4. Copy the ey-could.yml into your local home directory.

5. Install the rest-client and aws-s3 Ruby gems:
    $sudo gem install rest-client aws-s3

6. Install the ey-flex Ruby gem:
    $sudo gem install ey-flex --source http://gems.engineyard.com

7. Fork the durran ey-cloud-recipes repository at (http://github.com/durran/ey-cloud-recipes/tree/master)

8. Clone the forked repo:
    $git clone git@github.com:[github username]/ey-cloud-recipes.git

9. Run the following command locally:
    $ey-recipes

Current Environments:
env: hashrocket running instances: 1
instance_ids: ["i-863bd6ee"]
env: mongo running instances: 1
instance_ids: ["i-9a7c92f2"]

10. The output of the command will give you your application information, including the name of your environments, you'll want to run the following next to upload the recipes to the appropriate env, where "envname" is what appeared after "env:":
    $ey-recipes --upload envname

11. In your EY cloud console click on the "Deploy" link for your environment - you are good to go. Please note if your deploy has any dependency on Mongo running you will need to uncheck all boxes in the deploy modal dialog on the first deploy so only the chef scripts get run. Then each subsequent deploy can run normally.

Friday, April 10, 2009

Unit-Testing jQuery AJAX Calls With Screw-Unit and JSMock

The more web development I do, the more client-side heavy web applications seem to get. Over the past few years I have found myself almost exclusively using jQuery as a client-side Javascript framework, and have needed to do more unit-testing of my Javascript more and more as time progresses. One thing I have noticed though, is that unit-testing seems to get left out when dealing with jQuery AJAX calls. Well I decided this should go no further.

Note these examples are using screw-unit as the testing framework, and my updated implementation of JSMock to handle equivalence matching of Javascript Objects

Lets say for example I have an Object or class that I want to delegate to jQuery for some AJAX functionality, in this case I want it to be a sort of Javascript Controller that interacts via AJAX with my Controller on the server. For simplicity let's call it an AddressController, which (thank you captain obvious) provides me with addresses given an ID. My spec will look something like this:

Screw.Unit(function() {

describe("AddressesController", function() {

var placeholder;
var control;

before(function() {
placeholder = jQuery;
control = new MockControl();
jQuery = control.createMock(jQuery);
});

after(function() {
jQuery = placeholder;
});

describe("#show", function() {
describe("when an id is provided", function() {
it("should delegate to jQuery.ajax()", function() {
var params = { url: "/addresses/15", success: renderAddress };
jQuery.expects().ajax(params);
AddressesController.show(15);
control.verify();
});
});
});

});

});

My test is asserting that when I call show() on my AddressesController, I'll make a RESTful AJAX call to my controller on the server, and on success will call the renderAddress() function. The before and after setup is to restore jQuery back after I have mocked it out, so future tests that use jQuery will not break.

Now for my AddressesController:

AddressesController = {
show: function(id) {
jQuery.ajax({
url: "/addresses/" + id,
success: renderAddress
});
},
};


And that's all there is to it. Note that after I did my modifications on JSMock I discovered amok, which as a nicer syntax that JSMock, and is much closer to EasyMock 2/Mocha syntax without the need of MockControl objects... I'll be getting into that in the next post.