On MongoMapper, what is the difference between a Document and an EmbeddedDocument? - ruby

This example makes it seem like both are used (included) in order to make a class a persistent model, but it is not clear when I should use one or the other.

A MongoMapper::Document is saved to the database as a top-level record. A MongoMapper::EmbeddedDocument is saved within another document. For instance, let's say I have a blogging application. I have a Post and Comment model. They might look something like this:
require 'mongo_mapper'
MongoMapper.database = 'test'
class Post
include MongoMapper::Document
key :title
key :body
key :comments
many :comments
end
class Comment
include MongoMapper::EmbeddedDocument
key :author
key :body
end
p = Post.new(:title => 'Some post', :body => 'About something')
p.comments << Comment.new(:author => 'Emily', :body => 'A comment')
p.save
puts Post.all.map(&:inspect)
Will produce a document in your mongo database that looks like this:
{ "_id" : ObjectId("4c4dcf4b712337464e000001"),
"title" : "Some post",
"body" : "About something",
"comments" : [
{
"body" : "A comment",
"author" : "Emily",
"_id" : ObjectId("4c4dcf4b712337464e000002")
}
] }
In terms of interacting with them through MongoMapper, only a MongoMapper::Document response to methods like find and save. A MongoMapper::EmbeddedDocument can only be accessed through its parent document. The implication of this is that you should only use MongoMapper::EmbeddedDocument for models that are clearly subsidiary to their parent models and will only be used in the context of that parent.

Related

Batch insert multiple records with Mongoid?

I am reading through this Stackoverflow answer about how to insert multiple documents in Mongoid in one query. From the answer I read:
batch = [{:name => "mongodb"}, {:name => "mongoid"}]
Article.collection.insert(batch)
I need an example to understand how this works. Say we have the Article class:
class Article
include Mongoid::Document
include Mongoid::Timestamps
field :subject, type: String
field :body, type: String
field :remote_id, type: String
validates_uniqueness_of :remote_id
belongs_to :news_paper, :inverse_of => :articles
end
And the I e.g. create an array of articles:
[ {subject: "Mongoid rocks", body: "It really does", remote_id: "1234", news_paper_id: "abc"},
{subject: "Ruby rocks", body: "It really does", remote_id: "1234", news_paper_id: "abc"},
{subject: "Rails rocks", body: "It really does", remote_id: "5678", news_paper_id: "abc"} ]
How do I create them, and at the same time make sure the validation catches that I have 2 remote_id's that are the same?
If you add a unique indexing for remote_id field, MongoDB will take care the uniqueness of this field
index({ remote_id: 1 }, { unique: true })
Don't forget to run create_indexes: rake db:mongoid:create_indexes
After that, you are free to use Article.collection.insert(batch).

Mongoid saving only ids and leaving out other fields

I am using Nokogiri and mongoid in a test project.
Here's my code:
urls = Array[
'http://www.example.com/cairo',
'http://www.example.com/alexandria',
]
urls.each do |url|
doc = Nokogiri::HTML(open(url))
#Theater and location details here
#movies scrapper starts here
movies = doc.css('.itemContainer')
movies.each do |movie|
#movie title
title = movie.css("h3.catItemTitle a").text
#More code here
#movie synopsis
synopsis = movie.css(".catItemIntroText p").text
Movie.create! {
{title: title}
{synopsis: synopsis }
#{movielength: movielength }
#embedded theater collection
{theaters: {theater: theater,
address: address
}
}
}
end
end
My mongoid models look like this:
require 'mongoid'
class Movie
include Mongoid::Document
field :title, :type => String
field :synopsis, :type => String
attr_accessible :title, :synopsis
embeds_many :theaters
end
When I run the Nokogiri script only the mongodb objectid gets saved and the field details are not created or saved.
Here's a sample of my database:
] }
{ "_id" : ObjectId("52be9b3c4cfad19f0c000011") }
{ "_id" : ObjectId("52be9b3c4cfad19f0c000012") }
{ "_id" : ObjectId("52be9b3c4cfad19f0c000013") }
has more
Everything works well using Ruby puts to output but saving in mongodb is a hassle. What am I doing wrong? I am using Mongoid (3.1.6) with Ruby 1.9.3p448 on Mac OS Mavericks.
You're nesting the hashes. Do this instead for a starter:
Movie.create!(title: title, synopsis: synopsis, theaters: [theater])

How to stop DataMapper from double query when limiting columns/fields?

I'm not sure if I'm at fault here or if my approach is wrong with this.
I want to fetch a user (limiting columns/fields only to name, email, id):
#user = User.first(:api_key => request.env["HTTP_API_KEY"], :fields => [:id, :name, :email])
The output in the command line is correct as follows:
SELECT "id", "name", "email" FROM "users" WHERE "api_key" = '90e20c4838ba3e1772ace705c2f51d4146656cc5' ORDER BY "id" LIMIT 1
Directly after the above query, I have this code:
render_json({
:success => true,
:code => 200,
:user => #user
})
render_json() looks like this, nothing special:
def render_json(p)
status p[:code] if p.has_key?(:code)
p.to_json
end
The problem at this point is that the #user variable contains the full user object (all other fields included) and DataMapper has made an additional query to the database to fetch the fields not included in the :fields constraint, from the logs:
SELECT "id", "password", "api_key", "premium", "timezone", "verified", "notify_me", "company", "updated_at" FROM "users" WHERE "id" = 1 ORDER BY "id"
My question is this: how do I stop DM from performing the additional query? I know it has to do with it's lazy loading architecture and that returning the #user variable in JSON assumes that I want the whole user object. I particularly don't want the password field to be visible in any output representation of the user object.
The same behaviour can be seen when using DM's own serialisation module.
I think you should use an intermediate object for json rendering.
First, query the user from database :
db_user = User.first(:api_key => request.env["HTTP_API_KEY"], :fields => [:id, :name, :email])
Then, create a "json object" to manipulate this user :
#user = { id: db_user.id, name: db_user.name, email: db_user.email }

How to build a json object of a Mongoid object with children?

I have two simple classes
class Band
include Mongoid::Document
field :name, type:String
has_many :members
end
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
end
After I have created two object for test purposes
Band.create(title: 'New Band')
Band.members.create(name: 'New Member')
I got next db state:
> db.bands.find()
{ "_id" : ObjectId("..."), "title" : "New Band" }
> db.members.find()
{ "_id" : ObjectId("..."), "name" : "New Member", "band_id" : ObjectId("...") }
When I try to build json object of Band object I get data without children:
{"_id":"...","title":"New Band"}
But I need something like that:
{"_id":"...","title":"New Band", "members" : {"_id":"...","title":"New Member"}}
How to build json with children??
You can override serializable_hash:
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
def serializable_hash(options={})
{
id: id,
name: name
}
end
end
class Band
include Mongoid::Document
field :title, type: String
has_many :members
def serializable_hash(options={})
{
id: id,
title: title,
members: members.inject([]) { |acc, m| acc << m.serializable_hash; acc }
}
end
end
Suppose you have a band with a member:
band = Band.create(title: 'New Band')
band.members.create(name: 'New Member')
In that case band.to_json will return you something like that:
"{\"id\":...,\"title\":\"New Band\",\"members\":[{\"id\":...,\"name\":\"New Member\"}]}"
Try this:
a_band = Band.last
a_band.as_json(methods: [:members])
Mongoid auto-generates helper methods for your relations, and you can include these methods when you build your JSON object. You can use a_band.members to fetch the band's members out of the db, so you can include that method in your JSON object, like any other method on the model.

Mongoid - getting all attributes including embedded documents

Is there an easy way to get all attributes of a Mongoid document, including those of embedded documents?
For example, if I have the following documents:
class Person
include Mongoid::Document
embeds_many :phone_numbers
field :name
end
class PhoneNumner
include Mongoid::Document
embedded_in :person, :inverse_of => :phone_numbers
field :number
end
I would like to get a Person's attributes and phone numbers like this:
{ :name => "Jenny", :phone_numbers => [{ :number => '867-5309' }, { :number => '867-5309' }] }
Since embedded documents are really just other attributes on the parent document, you can get to them like so:
person = Person.create
person.phone_numbers.create(:number => "123-456-7890")
person.attributes
# => {"_id"=>"4c48ff26f7e2da3704000001",
# "phone_numbers"=>
# [{"number"=>"123-456-7890", "_id"=>"4c48ff26f7e2da3704000002"}]}

Resources