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