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"}] }