How to find a document by looking for a match in an embedded document array? - mongodb

This seems so simple but apparently I'm missing something. I've got a WebSite model:
class WebSite
include Mongoid::Document
has_many :domains, inverse_of: :web_site
field :name, type: String
end
The WebSite model has an embedded array of Domains:
class Domain
include Mongoid::Document
belongs_to :web_site, inverse_of: :domains
field :name, type: String
end
All I'm trying to do is find a WebSite with a domain of "test.com". Nothing I've tried seems to work, either with Mongoid or with the MongoDB console. For example, if I have one WebSite, with one Domain, with the name "test.com":
2.0.0p0 :001 > WebSite.count
=> 1
2.0.0p0 :002 > WebSite.first.domains
=> [#<Domain _id: 5148d9b76a3b8b1fe6000003, web_site_id: "5148d9a96a3b8b1fe6000002", name: "test.com">]
...then shouldn't this work?
2.0.0p0 :003 > WebSite.elem_match(domains: { name: "test.com" }).count
=> 0
2.0.0p0 :004 > WebSite.elem_match('domains' => { 'name' => "test.com" }).count
=> 0
I get zero. I should get one.
I also got the impression from the O'Reilly book that this should work:
2.0.0p0 :005 > WebSite.where('domains.name' => "test.com").count
=> 0
...same with any_in:
.0.0p0 :006 > WebSite.any_in('domains' => { 'name' => "test.com" }).count
=> 0
I've also tried those same queries from the MongoDB console with the same results:
> db.web_sites.find({"domains" : {"$elemMatch" : {"name" : "test.com"}}}).size();
0
> db.web_sites.find({"domains.name" : "test.com"}).size();
0
> db.web_sites.find({"domains" : {$in : {"name" : "test.com"}}}).size();
0
I must be missing something?
Update:
Here is some more information about the schema, from the MongoDB console:
> db.web_sites.find().pretty()
{ "_id" : ObjectId("5148d9a96a3b8b1fe6000002"), "name" : "test" }

Thanks to uldall's help with debugging (that awesome pretty() technique) I realized that my problem was that I had two separate collections side by side, instead of a Domain collection embedded in my WebSite collection. This is how my models should have been set up, with "embedded_in" and "embeds_many":
class WebSite
include Mongoid::Document
embeds_many :domains, inverse_of: :web_site
field :name, type: String
end
class Domain
include Mongoid::Document
embedded_in :web_site, inverse_of: :domains
field :name, type: String
end
Now the document looks like this from the MongoDB console:
> db.web_sites.find().pretty()
{
"_id" : ObjectId("5148e63f6a3b8b8ffa000001"),
"domains" : [
{
"_id" : ObjectId("5148e6706a3b8b8ffa000002"),
"name" : "test.com"
}
],
"name" : "test"
}
With that schema, this works:
2.0.0p0 :008 > WebSite.where('domains.name' => 'test.com').count
=> 1
[CHEER]

Related

How to customize configureDatagridFilters in Sonata Admin to use non related mongodb documents

In my Mongodb I got a passenger document, this is a typical item:
{
"_id" : ObjectId("51efdf818d6b408449000002"),
"createdAt" : 1374674817,
"phone" : "222222",
..
}
I also have a device document that references a passenger document, here is an example:
{
"_id" : ObjectId("51efdf818d6b408449000001"),
"os" : "android.gcm",
"passenger" : ObjectId("51efdf818d6b408449000002"),
..
}
so in other words.. there is no way I can find out the device belonging to a passenger by running a query on passenger.. it must be a query on device.
In my PassengerAdmin.php I got this configure list field definition:
public function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name', 'text', array('label' => 'Name'))
->addIdentifier('phone', 'text', array('label' => 'Phone #'))
->addIdentifier('createdAt', 'datetime', array('label' => 'Created At'))
->addIdentifier('device.os', 'text', array('label' => 'Device OS Type'))
..
;
}
which works fine (I have no idea how sonata managed to map device.os to passengers.. but oh well).
Inside my configureDataGridFilters this will return an error:
protected function configureDatagridFilters(DatagridMapper $datagrid)
{
$datagrid->add('device.os');
}
error:
Notice: Undefined index: device.os in
../vendor/sonata-project/doctrine-mongodb-admin-bundle/Sonata/DoctrineMongoDBAdminBundle/Builder/DatagridBuilder.php
line 60
which i guess makes sense.. and even if i created that index nothing will be returned.. (by the way I got that idea from here: see displaying subentity properties
question:
how can I customize the filter regarding the device OS version so that it incorporates info related to the Device document. lemme show what I want to get done using this example (mix of code and pseudocode):
->add('osVersion', 'doctrine_mongo_callback', [
'callback' => function ($queryBuilder, $alias, $field, $params) {
if ($params['value'] === null) {
return;
}
// for each passengers as passenger
// get passenger.id = %passengerID%
// grab device that has passenger = %passengerID%
// filter so that device.os == $params['value']
'field_type' => 'choice',
'field_options' => ['choices'=> ['android.gcm'=> "Android", "os.ios"=>"iOS"]]
]);
I noticed that the createQuery method of the superclass Admin can be overridden.. but it seems that that is globally so, and it wouldn't help me in this specific case.

Mongodb : find documents including references to sub-documents [node-mongodb-native]

I have the following Mongodb Documents :
Users : {'_id' : '12345', 'fullname' : 'ABC'}
and
Files :
{'_id' : 'File001', 'path' : 'pathA', 'Users_id' : '12345'}
{'_id' : 'File002', 'path' : 'pathB', 'Users_id' : '12345'}
How do i query or find all 'Files' documents in a way that the 'Users_id' which is referencing to the 'Users' document has the full 'Users' Document?
Expected Output :
{'_id' : 'File001', 'path' : 'pathA', 'Users_id' : '12345', 'user' : {'_id' : '12345', 'fullname' : 'ABC'}}
{'_id' : 'File002', 'path' : 'pathB', 'Users_id' : '12345', 'user' : {'_id' : '12345', 'fullname' : 'ABC'}}
In this way, i could access the file owner's fullname as such : file.user.fullname
I Appreciate any help from you guys. Thank you.
--- EDIT
I am using node-mongodb-native to access the db.
Below is my code to retrieve it:
var files = db.collection ('Files');
files.find ({}, {}).toArray (function ( err, filesDoc){
for ( var index in filesDoc) {
var fileDoc = filesDoc [ index ];
var users = db.collection ('Users');
users.findOne ({'_id' : fileDoc.Users_id}, {}, function (errUser, userDoc){
if ( ! errUser ) {
fileDoc.user = userDoc;
}
});
}
});
but this code is not assigning the user to all files doc and only assigns to the last element of the filesDoc array. Any suggestions?
MongoDB doesn't support joins so you have to query for the User details separately. If you're using node.js, Mongoose provides a populate feature to help simplify this so you can do something like the following to pull in the user details:
Files.find().populate('Users_id')
I managed to find the cause of the issue. The reason why my code returns a null user is because I saved File.Users_id as String and I am retrieving it using ObjectId.
I fixed my codes by converting Users_id String to ObjectId before saving the File.

How to rename a column in a collection in Mongodb using MongoMapper?

Mongodb gives an option for renaming a column name as follows
db.collection.update({},{'$rename'=> {'old_name' => 'new_name'}}, false,true)
Is it possible for using MongoMapper to do the same? The documentation doesn't specify anything.
I also tried getting the Mongodb connection handle from MongoMapper as
connection = MongoMapper.connection
db = MongoMapper.database
collection = db.collection('collection_name')
collection .update(....)
and doing the same query but it doesn't work.
MongoMapper uses the 10gen Ruby driver, and MongoMapper::Document provides access to the underlying driver objects.
The following working test shows that you can use Model.collection.update to do what you want, to rename a field for the model 'Model'. Make sure to use the :multi => true option to update if you want to update/rename more than a single document.
app/models/model.rb
class Model
include MongoMapper::Document
end
test/unit/model_test.rb
require 'test_helper'
class ModelTest < ActiveSupport::TestCase
def setup
Model.remove
end
test "rename" do
puts "Model.collection.class: #{Model.collection.class}"
puts "Model.database.class: #{Model.database.class}"
Model.create( 'old_name' => 'name value 0', 'another_key' => 'another value 0' )
Model.create( 'old_name' => 'name value 1', 'another_key' => 'another value 1' )
assert_equal(2, Model.where( 'old_name' => { '$exists' => true } ).count)
Model.collection.update( {}, { '$rename' => { 'old_name' => 'new_name' } }, :multi => true )
assert_equal(0, Model.where( 'old_name' => { '$exists' => true } ).count)
p Model.all
end
end
$ rake test
Rack::File headers parameter replaces cache_control after Rack 1.5.
Run options:
# Running tests:
Model.collection.class: Mongo::Collection
Model.database.class: Mongo::DB
[DEPRECATED] The 'safe' write concern option has been deprecated in favor of 'w'.
[#<Model _id: BSON::ObjectId('5101809d7f11ba1256000001'), another_key: "another value 0", new_name: "name value 0">, #<Model _id: BSON::ObjectId('5101809d7f11ba1256000002'), another_key: "another value 1", new_name: "name value 1">]
.
Finished tests in 0.012344s, 81.0110 tests/s, 162.0220 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

Why $pull operator doesn't work on MongoDB when using empty criteria?

I have nodes like this:
[_id] => MongoId Object (
[$id] => 4e90cb3cd68417740c000017
)
[label] => mystery
[owner] => me
[parents] => Array (
[0] => Array (
[id] => MongoId Object (
[$id] => 4e8c6bb6d68417340e0004ca
)
[owner] => userid
[timestamp] => 1318112522
)
)
[timestamp] => 1318112060
[translabel] => mystery
[type] => 0
What I am trying to do is to remove parents with id : 4e8c6bb6d68417340e0004ca , wherever they are.
For example this should have been working (latest Mongo):
db.nodes.update({},{$pull : {"parents": { "id" : ObjectId("4e8c6bb6d68417340e0004ca") }}});
or equaly in PHP (latest driver etc):
$mongodb->nodes->update(array(),array('$pull'=> array('parents'=>array('id'=> new MongoId("4e8c6bb6d68417340e0004ca")))));
both don't do anything!
on the other hand:
db.nodes.update({"_id": ObjectId("4e90cb3cd68417740c000017")},{$pull : {"parents": { "id" : ObjectId("4e8c6bb6d68417340e0004ca") }}});
or equaly in PHP:
$mongodb->nodes->update(array('_id' => new MongoId("4e90cb3cd68417740c000017"),array('$pull'=> array('parents'=>array('id'=> new MongoId("4e8c6bb6d68417340e0004ca")))));
work perfectly well! Is this a bug? Is it a problem that I use "id" instead of "_id" with MongoID objects in my subdocuments? Thanks in advance for any help!
Have you tried enable multi flag on update command
db.collection.update( criteria, objNew, upsert, multi )
multi - indicates if all documents matching criteria should be updated rather than just one. Can be useful with the $ operators below.
and change your query to
db.nodes.update({},{$pull : {"parents": { "id" : ObjectId("4e8c6bb6d68417340e0004ca") }}},false,true);
or
db.nodes.update({ "_id" : { $exists : true } },{$pull : {"parents": { "id" : ObjectId("4e8c6bb6d68417340e0004ca") }}},false,true);
i haven't tested the code myself, but am sure that any one of the above will work..

Parent association for embedded document using MongoMapper

If I have:
class Post
include MongoMapper::Document
has_many :comments
end
If I do:
class Comment
include MongoMapper::EmbeddedDocument
belongs_to :post # relevant part
end
Does that create an association using _root_document/_parent_document, or do I have to add the (redundant) key :post_id?
You do not need post_id or belongs_to :post. Instead, you can use embedded_in :post. This will make a read method for _parent_reference named post so you can say comment.post instead of comment._parent_reference.
class Comment
include MongoMapper::EmbeddedDocument
embedded_in :post
end
You do need the post_id key.
Here's how I tested this (with the classes as in the question):
> post = Post.new
=> #<Post _id: BSON::ObjectId('4cc5955ec2f79d4c84000001')>
> comment = Comment.new
=> #<Comment _id: BSON::ObjectId('4cc59563c2f79d4c84000002')>
> post.comments << comment
=> [#<Comment _id: BSON::ObjectId('4cc59563c2f79d4c84000002')>]
> post.save
=> true
> post.reload
=> #<Post _id: BSON::ObjectId('4cc5955ec2f79d4c84000001')>
> comment = post.comments.first
=> #<Comment _id: BSON::ObjectId('4cc59563c2f79d4c84000002')>
> comment.post
=> nil
> class Comment
?> key :post_id
?> end
=> #<MongoMapper::Plugins::Keys::Key:0xb5ab0328 #name="post_id", #type=nil, #default_value=nil, #options={}>
> comment
=> #<Comment post_id: nil, _id: BSON::ObjectId('4cc59563c2f79d4c84000002')>
> comment.post
=> nil
> comment.post = post
=> #<Post _id: BSON::ObjectId('4cc5955ec2f79d4c84000001')>
> comment.save
=> true
> comment.post
=> #<Post _id: BSON::ObjectId('4cc5955ec2f79d4c84000001')>