I wanted to export a MongoMapper document and it’s related documents as JSON — with embedded arrays for the collections. Invoking to_json did not seem to work perfectly, so I set about to discover what was going on.
Conclusion
If you use Embedded Documents for every associated document, the to_json method will work perfectly.
If you have normal Documents, you must override the as_json method to export the object “tree.”
Details
Here is a walk through of exporting mongo documents as JSON.
I created a simple Author class. And will use a simple test to show how to_json works:
describe "Author 1" do
before :all do
class Author
include MongoMapper::Document
key :name
key :pen_name
end
end
it "should output JSON" do
p1 = Author.create(:name => "Ben Franklin", :pen_name => "Poor Richard")
json = p1.to_json
puts json
json.should include "name"
json.should include "Poor Richard"
end
end
And we get what we expect:
{
"books":[],
"id":"4f316a4c8951a2eefe000001",
"name":"Ben Franklin",
"pen_name":"Poor Richard"
}
Now let’s add a new Book document of the Embedded variety. Here we will assert that the Author JSON should include a list of Books:
describe "Author 2" do
before :all do
class Book
include MongoMapper::EmbeddedDocument
key :title
end
class Author
include MongoMapper::Document
many :books
end
end
it "authors have books" do
p1 = Author.create(:name => "Ben Franklin", :pen_name => "Poor Richard",
:books => [Book.new(:title => "Poor Richard's Almanac")])
json = p1.to_json
puts json
json.should include "Poor Richard"
json.should include "Almanac"
end
end
And, sure enough, it works.
{
"books":[{
"id":"4f316a4c8951a2eefe000003",
"title":"Poor Richard's Almanac"}],
"id":"4f316a4c8951a2eefe000004",
"name":"Ben Franklin",
"pen_name":"Poor Richard"
}
Let’s add a list of Interests to the Author class, this time as a normal document type (not embedded). Now we can test that the Author JSON has the expected Interest:
describe "Author 3" do
before :all do
class Interest
include MongoMapper::Document
key :title, String
end
class Book
include MongoMapper::EmbeddedDocument
key :title
end
class Author
include MongoMapper::Document
many :books
many :interests
end
end
it "should have interests" do
p1 = Author.create(:name => "Ben Franklin", :pen_name => "Poor Richard",
:books => [Book.new(:title => "Poor Richard's Almanac")],
:interests => [Interest.create(:title => "Movies")])
json = p1.to_json
puts json
json.should include "Poor Richard"
json.should include "Almanac"
json.should include "Movies" # Fails
end
end
Whoa! No joy! Seems that the association to non-Embedded documents does not get automatically exported to the JSON.
{
"books":[{
"id":"4f316a4c8951a2eefe000006",
"title":"Poor Richard's Almanac"}],
"id":"4f316a4c8951a2eefe000007",
"name":"Ben Franklin",
"pen_name":"Poor Richard"
}
And we get a failed spec 🙁
# expected "{"books":[{"id":"4f316a4c8951a2eefe000006","title":"Poor Richard's Almanac"}],"id":"4f316a4c8951a2eefe000007","name":"Ben Franklin","pen_name":"Poor Richard"}" to include "Movies"
Turns out we can add a custom as_json implementation to the class that you want to export as JSON. The as_json is responsible for indicating which fields and collections should be included in the json.
describe "Author 4" do
before :all do
class Interest
include MongoMapper::Document
key :title, String
end
class Book
include MongoMapper::EmbeddedDocument
key :title
end
class Author
include MongoMapper::Document
many :books
many :interests
def as_json options={}
{
:name => self.name,
:pen_name => self.pen_name,
:books => self.books,
:interests => self.interests
}
end
end
end
it "should have interests in json" do
p1 = Author.create(:name => "Ben Franklin", :pen_name => "Poor Richard",
:books => [Book.new(:title => "Poor Richard's Almanac")],
:interests => [Interest.create(:title => "Movies")])
json = p1.to_json
puts json
json.should include "Poor Richard"
json.should include "Almanac"
json.should include "Movies"
end
end
And we have Books and Interests. Success!
{
"name":"Ben Franklin",
"pen_name":"Poor Richard",
"books":[{
"id":"4f31782a8951a2f267000002",
"title":"Poor Richard's Almanac"}],
"interests":[{
"author_id":"4f31782a8951a2f267000003",
"id":"4f31782a8951a2f267000001",
"title":"Movies"}]
}
